• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (c) 2023 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 *     http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16import {
17  SyntaxKind,
18  factory,
19  forEachChild,
20  isBreakOrContinueStatement,
21  isConstructorDeclaration,
22  isExportSpecifier,
23  isIdentifier,
24  isImportSpecifier,
25  isLabeledStatement,
26  isMetaProperty,
27  isSourceFile,
28  isStructDeclaration,
29  setParentRecursive,
30  visitEachChild,
31} from 'typescript';
32
33import type {
34  ClassElement,
35  Identifier,
36  Node,
37  SourceFile,
38  StructDeclaration,
39  Symbol,
40  TransformationContext,
41  Transformer,
42  TransformerFactory,
43  TypeChecker
44} from 'typescript';
45
46import {
47  createScopeManager,
48  isClassScope,
49  isGlobalScope,
50  isEnumScope,
51  isInterfaceScope,
52  isObjectLiteralScope,
53  noSymbolIdentifier,
54} from '../../utils/ScopeAnalyzer';
55
56import type {
57  Label,
58  Scope,
59  ScopeManager
60} from '../../utils/ScopeAnalyzer';
61
62import type {INameGenerator, NameGeneratorOptions} from '../../generator/INameGenerator';
63import type {IOptions} from '../../configs/IOptions';
64import type {INameObfuscationOption} from '../../configs/INameObfuscationOption';
65import type {TransformPlugin} from '../TransformPlugin';
66import {TransformerOrder} from '../TransformPlugin';
67import {getNameGenerator, NameGeneratorType} from '../../generator/NameFactory';
68import {TypeUtils} from '../../utils/TypeUtils';
69import {collectIdentifiersAndStructs} from '../../utils/TransformUtil';
70import {NodeUtils} from '../../utils/NodeUtils';
71import {ApiExtractor} from '../../common/ApiExtractor';
72import { globalMangledTable, historyMangledTable, reservedProperties } from './RenamePropertiesTransformer';
73
74namespace secharmony {
75  /**
76   * Rename Identifiers, including:
77   * 1. variable name
78   * 2. function name
79   * 3. label name
80   * 4. class name/interface name/ label name
81   * we need implement some features:
82   * 1. rename identifiers
83   * 2. store/restore name to/from nameCache file.
84   * 3. do scope analysis for identifier obfuscations
85   *
86   * @param option
87   */
88  const createRenameIdentifierFactory = function (option: IOptions): TransformerFactory<Node> {
89    const profile: INameObfuscationOption | undefined = option?.mNameObfuscation;
90    if (!profile || !profile.mEnable) {
91      return null;
92    }
93
94    let options: NameGeneratorOptions = {};
95    if (profile.mNameGeneratorType === NameGeneratorType.HEX) {
96      options.hexWithPrefixSuffix = true;
97    }
98    let generator: INameGenerator = getNameGenerator(profile.mNameGeneratorType, options);
99
100    const openTopLevel: boolean = option?.mNameObfuscation?.mTopLevel;
101    const exportObfuscation: boolean = option?.mExportObfuscation;
102    return renameIdentifierFactory;
103
104    function renameIdentifierFactory(context: TransformationContext): Transformer<Node> {
105      let reservedNames: string[] = [...(profile?.mReservedNames ?? []), 'this', '__global'];
106      profile?.mReservedToplevelNames?.forEach(item => reservedProperties.add(item));
107      let mangledSymbolNames: Map<Symbol, string> = new Map<Symbol, string>();
108      let mangledLabelNames: Map<Label, string> = new Map<Label, string>();
109      noSymbolIdentifier.clear();
110
111      let historyMangledNames: Set<string> = undefined;
112      if (historyNameCache && historyNameCache.size > 0) {
113        historyMangledNames = new Set<string>(Array.from(historyNameCache.values()));
114      }
115
116      let checker: TypeChecker = undefined;
117      let manager: ScopeManager = createScopeManager();
118      let shadowIdentifiers: Identifier[] = undefined;
119      let shadowStructs: StructDeclaration[] = undefined;
120
121      let identifierIndex: number = 0;
122      let structIndex: number = 0;
123      return renameTransformer;
124
125      /**
126       * Transformer to rename identifiers
127       *
128       * @param node ast node of a file.
129       */
130      function renameTransformer(node: Node): Node {
131        if (!isSourceFile(node)) {
132          return node;
133        }
134
135        const shadowSourceAst: SourceFile = TypeUtils.createNewSourceFile(node);
136        checker = TypeUtils.createChecker(shadowSourceAst);
137        manager.analyze(shadowSourceAst, checker, exportObfuscation);
138
139        // the reservedNames of manager contain the struct name.
140        if (!exportObfuscation) {
141          manager.getReservedNames().forEach((name) => {
142            reservedNames.push(name);
143          });
144        }
145
146        if (nameCache === undefined) {
147          nameCache = new Map<string, string>();
148        }
149
150        let root: Scope = manager.getRootScope();
151        renameInScope(root);
152        root = undefined;
153        // collect all identifiers of shadow sourceFile
154        const identifiersAndStructs = collectIdentifiersAndStructs(shadowSourceAst, context);
155        shadowIdentifiers = identifiersAndStructs.shadowIdentifiers;
156        shadowStructs = identifiersAndStructs.shadowStructs;
157
158        let ret: Node = visit(node);
159        ret = tryRemoveVirtualConstructor(ret);
160        return setParentRecursive(ret, true);
161      }
162
163      /**
164       * rename symbol table store in scopes...
165       *
166       * @param scope scope, such as global, module, function, block
167       */
168      function renameInScope(scope: Scope): void {
169        // process labels in scope, the label can't rename as the name of top labels.
170        renameLabelsInScope(scope);
171        // process symbols in scope, exclude property name.
172        renameNamesInScope(scope);
173
174        let subScope = undefined;
175        while (scope.children.length > 0) {
176          subScope = scope.children.pop();
177          renameInScope(subScope);
178          subScope = undefined;
179        }
180      }
181
182      function renameNamesInScope(scope: Scope): void {
183        if (isExcludeScope(scope)) {
184          return;
185        }
186
187        if (!exportObfuscation) {
188          scope.defs.forEach((def) => {
189            let parentScope = scope;
190            while (parentScope) {
191              if (parentScope.importNames && parentScope.importNames.has(def.name)) {
192                scope.defs.delete(def);
193                scope.mangledNames.add(def.name);
194              }
195              parentScope = parentScope.parent;
196            }
197          });
198        }
199
200        renames(scope, scope.defs, generator);
201      }
202
203      function renames(scope: Scope, defs: Set<Symbol>, generator: INameGenerator): void {
204        defs.forEach((def) => {
205          const original: string = def.name;
206          let mangled: string = original;
207          // No allow to rename reserved names.
208          if ((!Reflect.has(def, 'obfuscateAsProperty') && reservedNames.includes(original)) ||
209            (!exportObfuscation && scope.exportNames.has(def.name)) ||
210            isSkippedGlobal(openTopLevel, scope)) {
211            scope.mangledNames.add(mangled);
212            return;
213          }
214
215          if (mangledSymbolNames.has(def)) {
216            return;
217          }
218
219          const path: string = scope.loc + '#' + original;
220          const historyName: string = historyNameCache?.get(path);
221
222          if (historyName) {
223            mangled = historyName;
224          } else if (Reflect.has(def, 'obfuscateAsProperty')) {
225            mangled = getPropertyMangledName(original);
226          } else {
227            mangled = getMangled(scope, generator);
228          }
229
230          // add new names to name cache
231          nameCache.set(path, mangled);
232          scope.mangledNames.add(mangled);
233          mangledSymbolNames.set(def, mangled);
234        });
235      }
236
237      function getPropertyMangledName(original: string): string {
238        if (reservedProperties.has(original)) {
239          return original;
240        }
241
242        const historyName: string = historyMangledTable?.get(original);
243        let mangledName: string = historyName ? historyName : globalMangledTable.get(original);
244
245        while (!mangledName) {
246          let tmpName = generator.getName();
247          if (reservedProperties.has(tmpName) || tmpName === original) {
248            continue;
249          }
250
251          let isInGlobalMangledTable = false;
252          for (const value of globalMangledTable.values()) {
253            if (value === tmpName) {
254              isInGlobalMangledTable = true;
255              break;
256            }
257          }
258
259          if (isInGlobalMangledTable) {
260            continue;
261          }
262
263          let isInHistoryMangledTable = false;
264          if (historyMangledTable) {
265            for (const value of historyMangledTable.values()) {
266              if (value === tmpName) {
267                isInHistoryMangledTable = true;
268                break;
269              }
270            }
271          }
272
273          if (!isInHistoryMangledTable) {
274            mangledName = tmpName;
275            break;
276          }
277        }
278
279        globalMangledTable.set(original, mangledName);
280        return mangledName;
281      }
282
283      function isExcludeScope(scope: Scope): boolean {
284        if (isClassScope(scope)) {
285          return true;
286        }
287
288        if (isInterfaceScope(scope)) {
289          return true;
290        }
291
292        if (isEnumScope(scope)) {
293          return true;
294        }
295
296        return isObjectLiteralScope(scope);
297      }
298
299      function searchMangledInParent(scope: Scope, name: string): boolean {
300        let found: boolean = false;
301        let parentScope = scope;
302        while (parentScope) {
303          if (parentScope.mangledNames.has(name)) {
304            found = true;
305            break;
306          }
307
308          parentScope = parentScope.parent;
309        }
310
311        return found;
312      }
313
314      function getMangled(scope: Scope, localGenerator: INameGenerator): string {
315        let mangled: string = '';
316        do {
317          mangled = localGenerator.getName()!;
318          // if it is a globally reserved name, it needs to be regenerated
319          if (reservedNames.includes(mangled)) {
320            mangled = '';
321            continue;
322          }
323
324          if (scope.exportNames && scope.exportNames.has(mangled)) {
325            mangled = '';
326            continue;
327          }
328
329          if (historyMangledNames && historyMangledNames.has(mangled)) {
330            mangled = '';
331            continue;
332          }
333
334          if (searchMangledInParent(scope, mangled)) {
335            mangled = '';
336            continue;
337          }
338
339          if ((profile.mRenameProperties && manager.getRootScope().constructorReservedParams.has(mangled)) ||
340            ApiExtractor.mConstructorPropertySet?.has(mangled)) {
341            mangled = '';
342          }
343        } while (mangled === '');
344
345        return mangled;
346      }
347
348      function renameLabelsInScope(scope: Scope): void {
349        const labels: Label[] = scope.labels;
350        if (labels.length > 0) {
351          let upperMangledLabels = getUpperMangledLabelNames(labels[0]);
352          for (const label of labels) {
353            let mangledLabel = getMangledLabel(label, upperMangledLabels);
354            mangledLabelNames.set(label, mangledLabel);
355          }
356        }
357      }
358
359      function getMangledLabel(label: Label, mangledLabels: string[]): string {
360        let mangledLabel: string = '';
361        do {
362          mangledLabel = generator.getName();
363          if (mangledLabel === label.name) {
364            mangledLabel = '';
365          }
366
367          if (mangledLabels.includes(mangledLabel)) {
368            mangledLabel = '';
369          }
370        } while (mangledLabel === '');
371
372        return mangledLabel;
373      }
374
375      function getUpperMangledLabelNames(label: Label): string[] {
376        const results: string[] = [];
377        let parent: Label = label.parent;
378        while (parent) {
379          let mangledLabelName: string = mangledLabelNames.get(parent);
380          if (mangledLabelName) {
381            results.push(mangledLabelName);
382          }
383          parent = parent.parent;
384        }
385
386        return results;
387      }
388
389      /**
390       * visit each node to change identifier name to mangled name
391       *  - calculate shadow name index to find shadow node
392       * @param node
393       */
394      function visit(node: Node): Node {
395        if (!isIdentifier(node) || !node.parent) {
396          return visitEachChild(node, visit, context);
397        }
398
399        if (isLabeledStatement(node.parent) || isBreakOrContinueStatement(node.parent)) {
400          identifierIndex += 1;
401          return updateLabelNode(node);
402        }
403
404        const shadowNode: Identifier = shadowIdentifiers[identifierIndex];
405        identifierIndex += 1;
406        return updateNameNode(node, shadowNode);
407      }
408
409      function tryRemoveVirtualConstructor(node: Node): Node {
410        if (isStructDeclaration(node)) {
411          const shadowNode: StructDeclaration = shadowStructs[structIndex];
412          structIndex++;
413          const sourceFile = NodeUtils.getSourceFileOfNode(shadowNode);
414          const tempStructMembers: ClassElement[] = [];
415          if (sourceFile && sourceFile.isDeclarationFile) {
416            for (let index = 0; index < node.members.length; index++) {
417              const member = node.members[index];
418              // @ts-ignore
419              if (isConstructorDeclaration(member) && shadowNode.members[index].virtual) {
420                continue;
421              }
422              tempStructMembers.push(member);
423            }
424            const structMembersWithVirtualConstructor = factory.createNodeArray(tempStructMembers);
425            return factory.updateStructDeclaration(node, node.modifiers, node.name, node.typeParameters, node.heritageClauses,
426              structMembersWithVirtualConstructor);
427          }
428        }
429        return visitEachChild(node, tryRemoveVirtualConstructor, context);
430      }
431
432      function updateNameNode(node: Identifier, shadowNode: Identifier): Node {
433        // skip property in property access expression
434        if (NodeUtils.isPropertyAccessNode(node)) {
435          return node;
436        }
437
438        if (NodeUtils.isNewTargetNode(node)) {
439          return node;
440        }
441
442        let sym: Symbol | undefined = checker.getSymbolAtLocation(shadowNode);
443        let mangledPropertyNameOfNoSymbolImportExport = '';
444        if ((!sym || sym.name === 'default')) {
445          if (exportObfuscation && noSymbolIdentifier.has(shadowNode.escapedText as string) && trySearchImportExportSpecifier(shadowNode)) {
446            mangledPropertyNameOfNoSymbolImportExport = mangleNoSymbolImportExportPropertyName(shadowNode.escapedText as string);
447          } else {
448            return node;
449          }
450        }
451
452        let mangledName: string = mangledSymbolNames.get(sym);
453        if (!mangledName && mangledPropertyNameOfNoSymbolImportExport !== '') {
454          mangledName = mangledPropertyNameOfNoSymbolImportExport;
455        }
456
457        if (!mangledName || mangledName === sym?.name) {
458          return node;
459        }
460
461        return factory.createIdentifier(mangledName);
462      }
463
464      function updateLabelNode(node: Identifier): Node {
465        let label: Label | undefined;
466        let labelName: string = '';
467
468        mangledLabelNames.forEach((value, key) => {
469          if (key.refs.includes(node)) {
470            label = key;
471            labelName = value;
472          }
473        });
474
475        return label ? factory.createIdentifier(labelName) : node;
476      }
477
478      /**
479       * import {A as B} from 'modulename';
480       * import {C as D} from 'modulename';
481       * export {E as F};
482       * above A、C、F have no symbol, so deal with them specially.
483       */
484      function mangleNoSymbolImportExportPropertyName(original: string): string {
485        const path: string = '#' + original;
486        const historyName: string = historyNameCache?.get(path);
487        let mangled = historyName ?? getPropertyMangledName(original);
488        nameCache.set(path, mangled);
489        return mangled;
490      }
491
492      function trySearchImportExportSpecifier(node: Node): boolean {
493        while (node.parent) {
494          node = node.parent;
495          if ((isImportSpecifier(node) || isExportSpecifier(node)) && node.propertyName && isIdentifier(node.propertyName)) {
496            return true;
497          }
498        }
499        return false;
500      }
501    }
502  };
503
504  function isSkippedGlobal(enableTopLevel: boolean, scope: Scope): boolean {
505    return !enableTopLevel && isGlobalScope(scope);
506  }
507
508  export let transformerPlugin: TransformPlugin = {
509    'name': 'renameIdentifierPlugin',
510    'order': (1 << TransformerOrder.RENAME_IDENTIFIER_TRANSFORMER),
511    'createTransformerFactory': createRenameIdentifierFactory
512  };
513
514  export let nameCache: Map<string, string> = undefined;
515  export let historyNameCache: Map<string, string> = undefined;
516  export let globalNameCache: Map<string, string> = new Map();
517}
518
519export = secharmony;