• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (c) 2023-2024 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  isPropertyDeclaration,
32  isMethodDeclaration,
33  isGetAccessor,
34  isSetAccessor,
35  isClassDeclaration,
36  isFunctionExpression,
37  isArrowFunction,
38  isVariableDeclaration,
39  isPropertyAssignment,
40  isPrivateIdentifier,
41  isParameter,
42  isPropertyAccessExpression
43} from 'typescript';
44
45import type {
46  ClassElement,
47  Declaration,
48  Identifier,
49  Node,
50  SourceFile,
51  StructDeclaration,
52  Symbol,
53  TransformationContext,
54  Transformer,
55  TransformerFactory,
56  TypeChecker
57} from 'typescript';
58
59import {
60  createScopeManager,
61  exportElementsWithoutSymbol,
62  exportSymbolAliasMap,
63  getNameWithScopeLoc,
64  isClassScope,
65  isGlobalScope,
66  isEnumScope,
67  isInterfaceScope,
68  isObjectLiteralScope,
69  nodeSymbolMap
70} from '../../utils/ScopeAnalyzer';
71
72import type {
73  Label,
74  Scope,
75  ScopeManager
76} from '../../utils/ScopeAnalyzer';
77
78import {
79  IDENTIFIER_CACHE,
80  MEM_METHOD_CACHE
81} from '../../utils/NameCacheUtil';
82
83import type {INameGenerator, NameGeneratorOptions} from '../../generator/INameGenerator';
84import type {IOptions} from '../../configs/IOptions';
85import type { INameObfuscationOption, IUnobfuscationOption } from '../../configs/INameObfuscationOption';
86import type {TransformPlugin} from '../TransformPlugin';
87import type { MangledSymbolInfo } from '../../common/type';
88import {TransformerOrder} from '../TransformPlugin';
89import {getNameGenerator, NameGeneratorType} from '../../generator/NameFactory';
90import {TypeUtils} from '../../utils/TypeUtils';
91import {
92  isInTopLevelWhitelist,
93  isInLocalWhitelist,
94  isReservedLocalVariable,
95  isReservedTopLevel,
96  recordHistoryUnobfuscatedNames,
97  isInPropertyWhitelist,
98  isReservedProperty
99} from '../../utils/TransformUtil';
100import {NodeUtils} from '../../utils/NodeUtils';
101import {ApiExtractor} from '../../common/ApiExtractor';
102import {performancePrinter, ArkObfuscator, cleanFileMangledNames} from '../../ArkObfuscator';
103import { EventList, endSingleFileEvent, startSingleFileEvent } from '../../utils/PrinterUtils';
104import { isViewPUBasedClass } from '../../utils/OhsUtil';
105import {
106  AtKeepCollections,
107  LocalVariableCollections,
108  PropCollections,
109  UnobfuscationCollections
110} from '../../utils/CommonCollections';
111import { MemoryDottingDefine } from '../../utils/MemoryDottingDefine';
112import { shouldKeepCurFileParamerters, shouldKeepParameter } from '../../utils/KeepParameterUtils';
113import { addToSet } from '../../utils/ProjectCollections';
114
115namespace secharmony {
116  /**
117   * Rename Identifiers, including:
118   * 1. variable name
119   * 2. function name
120   * 3. label name
121   * 4. class name/interface name/ label name
122   * we need implement some features:
123   * 1. rename identifiers
124   * 2. store/restore name to/from nameCache file.
125   * 3. do scope analysis for identifier obfuscations
126   *
127   * @param option
128   */
129  const createRenameIdentifierFactory = function (option: IOptions): TransformerFactory<Node> | null {
130    const profile: INameObfuscationOption | undefined = option?.mNameObfuscation;
131    const shouldPrintKeptNames: boolean = !!(option.mUnobfuscationOption?.mPrintKeptNames);
132    const enablePropertyObf: boolean = !!(profile?.mRenameProperties);
133
134    if (!profile || !profile.mEnable) {
135      return null;
136    }
137
138    let options: NameGeneratorOptions = {};
139    globalGenerator = getNameGenerator(profile.mNameGeneratorType, options);
140
141    const enableToplevel: boolean = option?.mNameObfuscation?.mTopLevel;
142    const exportObfuscation: boolean = option?.mExportObfuscation;
143    let isInitializedReservedList = false;
144    return renameIdentifierFactory;
145
146    function renameIdentifierFactory(context: TransformationContext): Transformer<Node> {
147      initWhitelist();
148      let mangledSymbolNames: Map<Symbol, MangledSymbolInfo> = new Map<Symbol, MangledSymbolInfo>();
149      let mangledPropertyParameterSymbolNames: Map<Declaration, MangledSymbolInfo> = new Map<Declaration, MangledSymbolInfo>();
150      let mangledLabelNames: Map<Label, string> = new Map<Label, string>();
151      let fileExportNames: Set<string> = undefined;
152      let fileImportNames: Set<string> = undefined;
153      exportElementsWithoutSymbol.clear();
154      exportSymbolAliasMap.clear();
155      nodeSymbolMap.clear();
156
157      let historyMangledNames: Set<string> = undefined;
158      if (historyNameCache && historyNameCache.size > 0) {
159        historyMangledNames = new Set<string>(Array.from(historyNameCache.values()));
160      }
161
162      let checker: TypeChecker = undefined;
163      let manager: ScopeManager = createScopeManager();
164      let isCurFileParamertersKept: boolean = false;
165      return renameTransformer;
166
167      /**
168       * Transformer to rename identifiers
169       *
170       * @param node ast node of a file.
171       */
172      function renameTransformer(node: Node): Node {
173        if (nameCache.size === 0) {
174          nameCache.set(IDENTIFIER_CACHE, new Map<string, string>());
175          nameCache.set(MEM_METHOD_CACHE, new Map<string, string>());
176        }
177
178        if (!isSourceFile(node) || ArkObfuscator.isKeptCurrentFile) {
179          return node;
180        }
181        isCurFileParamertersKept = shouldKeepCurFileParamerters(node, profile);
182
183        const checkRecordInfo = ArkObfuscator.recordStage(MemoryDottingDefine.CREATE_CHECKER);
184        startSingleFileEvent(EventList.CREATE_CHECKER, performancePrinter.timeSumPrinter);
185        checker = TypeUtils.createChecker(node);
186        endSingleFileEvent(EventList.CREATE_CHECKER, performancePrinter.timeSumPrinter);
187        ArkObfuscator.stopRecordStage(checkRecordInfo);
188
189        const scopeRecordInfo = ArkObfuscator.recordStage(MemoryDottingDefine.SCOPE_ANALYZE);
190        startSingleFileEvent(EventList.SCOPE_ANALYZE, performancePrinter.timeSumPrinter);
191        manager.analyze(node, checker, exportObfuscation);
192        endSingleFileEvent(EventList.SCOPE_ANALYZE, performancePrinter.timeSumPrinter);
193        ArkObfuscator.stopRecordStage(scopeRecordInfo);
194
195        let rootScope: Scope = manager.getRootScope();
196        fileExportNames = rootScope.fileExportNames;
197        fileImportNames = rootScope.fileImportNames;
198        let renameProcessors: ((scope: Scope) => void)[] = [renameLabelsInScope, renameNamesInScope];
199        if (profile.mRenameProperties) {
200          renameProcessors.push(renamePropertyParametersInScope);
201        }
202
203        const obfuscateNamesRecordInfo = ArkObfuscator.recordStage(MemoryDottingDefine.OBFUSCATE_NAMES);
204        startSingleFileEvent(EventList.CREATE_OBFUSCATED_NAMES, performancePrinter.timeSumPrinter);
205        getMangledNamesInScope(rootScope, renameProcessors);
206        endSingleFileEvent(EventList.CREATE_OBFUSCATED_NAMES, performancePrinter.timeSumPrinter);
207        ArkObfuscator.stopRecordStage(obfuscateNamesRecordInfo);
208
209        rootScope = undefined;
210
211        const obfuscateNodesRecordInfo = ArkObfuscator.recordStage(MemoryDottingDefine.OBFUSCATE_NODES);
212        startSingleFileEvent(EventList.OBFUSCATE_NODES, performancePrinter.timeSumPrinter);
213        let updatedNode: Node = renameIdentifiers(node);
214
215        // obfuscate property parameter declaration
216        if (profile.mRenameProperties) {
217          updatedNode = visitPropertyParameter(updatedNode);
218        }
219
220        let parentNodes = setParentRecursive(updatedNode, true);
221        endSingleFileEvent(EventList.OBFUSCATE_NODES, performancePrinter.timeSumPrinter);
222        ArkObfuscator.stopRecordStage(obfuscateNodesRecordInfo);
223        return parentNodes;
224      }
225
226      /**
227       * get mangled names of symbols stored in scopes.
228       *
229       * @param scope scope, such as global, module, function, block
230       * @param processors processors to get mangled names
231       */
232      function getMangledNamesInScope(scope: Scope, processors: ((scope: Scope) => void)[]): void {
233        for (const process of processors) {
234          process(scope);
235        }
236
237        let subScope = undefined;
238        while (scope.children.length > 0) {
239          subScope = scope.children.pop();
240          getMangledNamesInScope(subScope, processors);
241          subScope = undefined;
242        }
243      }
244
245      // process symbols in scope, exclude property name.
246      function renameNamesInScope(scope: Scope): void {
247        if (isExcludeScope(scope)) {
248          return;
249        }
250
251        if (!exportObfuscation) {
252          scope.defs.forEach((def) => {
253            let parentScope = scope;
254            while (parentScope) {
255              if (parentScope.importNames && parentScope.importNames.has(def.name)) {
256                scope.defs.delete(def);
257                scope.mangledNames.add(def.name);
258              }
259              parentScope = parentScope.parent;
260            }
261          });
262        }
263
264        renames(scope, scope.defs, globalGenerator);
265      }
266
267      // process property parameters symbols in class scope
268      function renamePropertyParametersInScope(scope: Scope): void {
269        if (!isClassScope(scope)) {
270          return;
271        }
272
273        renamePropertyParameters(scope, scope.defs, globalGenerator);
274      }
275
276      function renames(scope: Scope, defs: Set<Symbol>, generator: INameGenerator): void {
277        defs.forEach((def) => {
278          const original: string = def.name;
279          let mangled: string = original;
280          const path: string = getNameWithScopeLoc(scope, original);
281          // No allow to rename reserved names.
282          if (!Reflect.has(def, 'obfuscateAsProperty') &&
283            isInLocalWhitelist(original, UnobfuscationCollections.unobfuscatedNamesMap, path, shouldPrintKeptNames) ||
284            (!exportObfuscation && scope.exportNames.has(def.name)) ||
285            isSkippedGlobal(enableToplevel, scope)) {
286            scope.mangledNames.add(mangled);
287            mangledSymbolNames.set(def, {mangledName: mangled, originalNameWithScope: path});
288            return;
289          }
290
291          if (mangledSymbolNames.has(def)) {
292            return;
293          }
294
295          const declarationOfSymbol: Declaration | undefined = def.declarations?.[0];
296          if (isCurFileParamertersKept && shouldKeepParameter(declarationOfSymbol, profile, mangledSymbolNames, checker)) {
297            mangledSymbolNames.set(def, {mangledName: mangled, originalNameWithScope: path});
298            return;
299          }
300
301          const historyName: string = historyNameCache?.get(path);
302          if (historyName) {
303            recordHistoryUnobfuscatedNames(path); // For incremental build
304            mangled = historyName;
305          } else if (Reflect.has(def, 'obfuscateAsProperty')) {
306            // obfuscate toplevel, export
307            mangled = getPropertyMangledName(original, path);
308          } else {
309            // obfuscate local variable
310            mangled = getMangled(scope, generator);
311          }
312          // add new names to name cache
313          let identifierCache = nameCache?.get(IDENTIFIER_CACHE);
314          (identifierCache as Map<string, string>).set(path, mangled);
315          let symbolInfo: MangledSymbolInfo = {
316            mangledName: mangled,
317            originalNameWithScope: path
318          };
319          scope.mangledNames.add(mangled);
320          mangledSymbolNames.set(def, symbolInfo);
321        });
322      }
323
324      function renamePropertyParameters(scope: Scope, defs: Set<Symbol>, generator: INameGenerator): void {
325        defs.forEach((def) => {
326          //only rename property parameters
327          if (!def.valueDeclaration || !isParameter(def.valueDeclaration)) {
328            return;
329          }
330          const originalName: string = def.name;
331          const path: string = getNameWithScopeLoc(scope, originalName);
332          let mangledName: string;
333          if (isInPropertyWhitelist(originalName, UnobfuscationCollections.unobfuscatedPropMap, shouldPrintKeptNames)) {
334            mangledName = originalName;
335          } else {
336            mangledName = getMangledPropertyParameters(scope, generator, originalName);
337          }
338          scope.mangledNames.add(mangledName);
339          let symbolInfo: MangledSymbolInfo = {
340            mangledName: mangledName,
341            originalNameWithScope: path
342          };
343          mangledPropertyParameterSymbolNames.set(def.valueDeclaration, symbolInfo);
344        });
345      }
346
347      function getMangledPropertyParameters(scope: Scope, localGenerator: INameGenerator, originalName: string): string {
348        const historyName: string = PropCollections.historyMangledTable?.get(originalName);
349        let mangledName: string = historyName ? historyName : PropCollections.globalMangledTable.get(originalName);
350        while (!mangledName) {
351          let tmpName = localGenerator.getName();
352          if (isReservedLocalVariable(tmpName)) {
353            continue;
354          }
355          if (isReservedProperty(tmpName) || tmpName === originalName) {
356            continue;
357          }
358          if (historyMangledNames && historyMangledNames.has(tmpName)) {
359            continue;
360          }
361
362          // For incremental compilation, preventing generated names from conflicting with existing global name.
363          if (PropCollections.globalMangledNamesInCache.has(tmpName)) {
364            continue;
365          }
366          if (searchMangledInParent(scope, tmpName)) {
367            continue;
368          }
369          mangledName = tmpName;
370        }
371        PropCollections.globalMangledTable.set(originalName, mangledName);
372        return mangledName;
373      }
374
375      function getPropertyMangledName(original: string, nameWithScope: string): string {
376        if (isInTopLevelWhitelist(original, UnobfuscationCollections.unobfuscatedNamesMap, nameWithScope, enablePropertyObf, shouldPrintKeptNames)) {
377          return original;
378        }
379
380        const historyName: string = PropCollections.historyMangledTable?.get(original);
381        let mangledName: string = historyName ? historyName : PropCollections.globalMangledTable.get(original);
382        while (!mangledName) {
383          let tmpName = globalGenerator.getName();
384          if (isReservedTopLevel(tmpName, enablePropertyObf) ||
385            tmpName === original) {
386            continue;
387          }
388
389          /**
390           * In case b is obfuscated as a when only enable toplevel obfuscation:
391           * let b = 1;
392           * export let a = 1;
393           */
394          if (cleanFileMangledNames && fileExportNames && fileExportNames.has(tmpName)) {
395            continue;
396          }
397
398          /**
399           * In case b is obfuscated as a when only enable toplevel obfuscation:
400           * import {a} from 'filePath';
401           * let b = 1;
402           */
403          if (cleanFileMangledNames && fileImportNames.has(tmpName)) {
404            continue;
405          }
406
407          /**
408           * In case a newly added variable get an obfuscated name that is already in history namecache
409           */
410          if (historyMangledNames && historyMangledNames.has(tmpName)) {
411            continue;
412          }
413
414          /**
415           * For incremental compilation, preventing generated names from conflicting with existing global name.
416           */
417          if (PropCollections.globalMangledNamesInCache.has(tmpName)) {
418            continue;
419          }
420
421          if (ApiExtractor.mConstructorPropertySet.has(tmpName)) {
422            continue;
423          }
424
425          if (ApiExtractor.mEnumMemberSet?.has(tmpName)) {
426            continue;
427          }
428
429          mangledName = tmpName;
430        }
431
432        PropCollections.globalMangledTable.set(original, mangledName);
433        return mangledName;
434      }
435
436      function isExcludeScope(scope: Scope): boolean {
437        if (isClassScope(scope)) {
438          return true;
439        }
440
441        if (isInterfaceScope(scope)) {
442          return true;
443        }
444
445        if (isEnumScope(scope)) {
446          return true;
447        }
448
449        return isObjectLiteralScope(scope);
450      }
451
452      function searchMangledInParent(scope: Scope, name: string): boolean {
453        let found: boolean = false;
454        let parentScope = scope;
455        while (parentScope) {
456          if (parentScope.mangledNames.has(name)) {
457            found = true;
458            break;
459          }
460
461          parentScope = parentScope.parent;
462        }
463
464        return found;
465      }
466
467      function getMangled(scope: Scope, localGenerator: INameGenerator): string {
468        let mangled: string = '';
469        do {
470          mangled = localGenerator.getName()!;
471          // if it is a globally reserved name, it needs to be regenerated
472          if (isReservedLocalVariable(mangled)) {
473            mangled = '';
474            continue;
475          }
476
477          if (fileExportNames && fileExportNames.has(mangled)) {
478            mangled = '';
479            continue;
480          }
481
482          if (historyMangledNames && historyMangledNames.has(mangled)) {
483            mangled = '';
484            continue;
485          }
486
487          /**
488           * For incremental compilation, preventing generated names from conflicting with existing global name.
489           */
490          if (PropCollections.globalMangledNamesInCache.has(mangled)) {
491            mangled = '';
492            continue;
493          }
494
495          if (searchMangledInParent(scope, mangled)) {
496            mangled = '';
497            continue;
498          }
499
500          if (ApiExtractor.mConstructorPropertySet.has(mangled)) {
501            mangled = '';
502          }
503
504          if (ApiExtractor.mEnumMemberSet?.has(mangled)) {
505            mangled = '';
506          }
507        } while (mangled === '');
508
509        return mangled;
510      }
511
512      // process labels in scope, the label can't rename as the name of top labels.
513      function renameLabelsInScope(scope: Scope): void {
514        const labels: Label[] = scope.labels;
515        if (labels.length > 0) {
516          let upperMangledLabels = getUpperMangledLabelNames(labels[0]);
517          for (const label of labels) {
518            let mangledLabel = getMangledLabel(label, upperMangledLabels);
519            mangledLabelNames.set(label, mangledLabel);
520          }
521        }
522      }
523
524      function getMangledLabel(label: Label, mangledLabels: string[]): string {
525        let mangledLabel: string = '';
526        do {
527          mangledLabel = globalGenerator.getName();
528          if (mangledLabel === label.name) {
529            mangledLabel = '';
530          }
531
532          if (mangledLabels.includes(mangledLabel)) {
533            mangledLabel = '';
534          }
535        } while (mangledLabel === '');
536
537        return mangledLabel;
538      }
539
540      function getUpperMangledLabelNames(label: Label): string[] {
541        const results: string[] = [];
542        let parent: Label = label.parent;
543        while (parent) {
544          let mangledLabelName: string = mangledLabelNames.get(parent);
545          if (mangledLabelName) {
546            results.push(mangledLabelName);
547          }
548          parent = parent.parent;
549        }
550
551        return results;
552      }
553
554      function isFunctionLike(node: Node): boolean {
555        switch (node.kind) {
556          case SyntaxKind.FunctionDeclaration:
557          case SyntaxKind.MethodDeclaration:
558          case SyntaxKind.GetAccessor:
559          case SyntaxKind.SetAccessor:
560          case SyntaxKind.Constructor:
561          case SyntaxKind.FunctionExpression:
562          case SyntaxKind.ArrowFunction:
563            return true;
564        }
565        return false;
566      }
567
568      function nodeHasFunctionLikeChild(node: Node): boolean {
569        let hasFunctionLikeChild: boolean = false;
570        let childVisitor: (child: Node) => Node = (child: Node): Node => {
571          if (!hasFunctionLikeChild && child && isFunctionLike(child)) {
572            hasFunctionLikeChild = true;
573          }
574          return child;
575        };
576        visitEachChild(node, childVisitor, context);
577        return hasFunctionLikeChild;
578      }
579
580      /**
581       * visit each node to change identifier name to mangled name
582       *  - calculate shadow name index to find shadow node
583       * @param node
584       */
585      function renameIdentifiers(node: Node): Node {
586        let needHandlePositionInfo: boolean = isFunctionLike(node) || nodeHasFunctionLikeChild(node);
587        if (needHandlePositionInfo) {
588          // Obtain line info for nameCache.
589          handlePositionInfo(node);
590        }
591
592        if (!isIdentifier(node) || !node.parent) {
593          return visitEachChild(node, renameIdentifiers, context);
594        }
595
596        if (isLabeledStatement(node.parent) || isBreakOrContinueStatement(node.parent)) {
597          return updateLabelNode(node);
598        }
599
600        return updateNameNode(node);
601      }
602
603      /**
604       * visit each property parameter to change identifier name to mangled name
605       *  - calculate shadow name index to find shadow node
606       * @param node
607       */
608      function visitPropertyParameter(node: Node): Node {
609        if (isConstructorDeclaration(node)) {
610          return visitPropertyParameterInConstructor(node);
611        }
612
613        return visitEachChild(node, visitPropertyParameter, context);
614
615        function visitPropertyParameterInConstructor(node: Node): Node {
616          if (!isIdentifier(node) || !node.parent) {
617            return visitEachChild(node, visitPropertyParameterInConstructor, context);
618          }
619
620          // we do not obfuscate the identifier of property access expression, like "a" in "this.a",
621          // since it will be obfuscated in renamePropertiesTransformer
622          if (NodeUtils.isPropertyNode(node)) {
623            return node;
624          }
625
626          return updatePropertyParameterNameNode(node);
627        }
628      }
629
630      function handlePositionInfo(node: Node): void {
631        const sourceFile = NodeUtils.getSourceFileOfNode(node);
632        if (node && node.pos < 0 && node.end < 0) {
633          // Node must have a real position for following operations.
634          // Adapting to the situation that the node does not have a real postion.
635          return;
636        }
637        const startPosition = sourceFile.getLineAndCharacterOfPosition(node.getStart());
638        const endPosition = sourceFile.getLineAndCharacterOfPosition(node.getEnd());
639        // 1: The line number in sourceFile starts from 0 while in IDE starts from 1.
640        const startLine = startPosition.line + 1;
641        const startCharacter = startPosition.character + 1; // 1: Same as above.
642        const endLine = endPosition.line + 1; // 1: Same as above.
643        const endCharacter = endPosition.character + 1; // 1: Same as above.
644        const lineAndColum: string = ':' + startLine + ':' + startCharacter + ':' + endLine + ':' + endCharacter;
645
646        let isProperty: boolean = isMethodDeclaration(node) || isGetAccessor(node) ||
647                                  isSetAccessor(node) || (isConstructorDeclaration(node) &&
648                                  !(isClassDeclaration(node.parent) && isViewPUBasedClass(node.parent)));
649        // Arrow functions are anoymous, only function expressions are considered.
650        let isPropertyParent: boolean = isFunctionExpression(node) &&
651                                        (isPropertyDeclaration(node.parent) || isPropertyAssignment(node.parent));
652        let isMemberMethod: boolean = isProperty || isPropertyParent;
653        if (isMemberMethod) {
654          writeMemberMethodCache(node, lineAndColum);
655          return;
656        }
657
658        let name = Reflect.get(node, 'name') as Identifier;
659        if (name?.kind === SyntaxKind.Identifier) {
660          identifierLineMap.set(name, lineAndColum);
661        } else if ((isFunctionExpression(node) || isArrowFunction(node)) && isVariableDeclaration(node.parent) &&
662          node.parent.name?.kind === SyntaxKind.Identifier) {
663          // The node is anonymous, and we need to find its parent node.
664          // e.g.: let foo = function() {};
665          identifierLineMap.set(node.parent.name, lineAndColum);
666        }
667      }
668
669      function writeMemberMethodCache(node: Node, lineAndColum: string): void {
670        let gotNode;
671        if (node.kind === SyntaxKind.Constructor) {
672          gotNode = node.parent;
673        } else if ((node.kind === SyntaxKind.FunctionExpression &&
674          (isPropertyDeclaration(node.parent) || isPropertyAssignment(node.parent)))) {
675          gotNode = node.parent.initializer ?? node.parent;
676        } else {
677          gotNode = node;
678        }
679
680        let isIdentifierNode: boolean = gotNode.name && (isIdentifier(gotNode.name) || isPrivateIdentifier(gotNode.name));
681        let valueName: string = '';
682
683        if (isIdentifierNode) {
684          // The original method for retrieving method names used gotNode.name.escapedText. This approach limited the collection
685          // of method records in MemberMethodCache to cases where gotNode.name was an Identifier or PrivateIdentifier.
686          // To address the issue where method names starting with double underscores were transformed to start with triple underscores,
687          // we changed the retrieval method to use gotNode.name.text instead of escapedText. However, this change introduced the possibility
688          // of collecting method records when gotNode.name is a NumericLiteral or StringLiteral, which is not desired.
689          // To avoid altering the collection specifications of MemberMethodCache, we restricted the collection scenarios
690          // to match the original cases where only identifiers and private identifiers are collected.
691          valueName = gotNode.name.text;
692        }
693
694        if (valueName === '') {
695          return;
696        }
697
698        let originalName: string = valueName;
699        let keyName = originalName + lineAndColum;
700        if (node.kind === SyntaxKind.Constructor && classMangledName.has(gotNode.name)) {
701          valueName = classMangledName.get(gotNode.name);
702          classInfoInMemberMethodCache.add(keyName);
703        }
704        let memberMethodCache = nameCache?.get(MEM_METHOD_CACHE);
705        if (memberMethodCache) {
706          (memberMethodCache as Map<string, string>).set(keyName, valueName);
707        }
708      }
709
710      function updateNameNode(node: Identifier): Node {
711        // skip property in property access expression
712        if (NodeUtils.isPropertyAccessNode(node)) {
713          return node;
714        }
715
716        if (NodeUtils.isNewTargetNode(node)) {
717          return node;
718        }
719
720        let sym: Symbol | undefined = NodeUtils.findSymbolOfIdentifier(checker, node, nodeSymbolMap);
721        let mangledPropertyNameOfNoSymbolImportExport = '';
722        if (!sym) {
723          if (shouldObfuscateNodeWithoutSymbol(node)) {
724            mangledPropertyNameOfNoSymbolImportExport = mangleNoSymbolImportExportPropertyName(node.text);
725          } else {
726            return node;
727          }
728        }
729
730        if (exportSymbolAliasMap.has(sym)) {
731          sym = exportSymbolAliasMap.get(sym);
732        }
733
734        // Add new names to name cache
735        const symbolInfo: MangledSymbolInfo = mangledSymbolNames.get(sym);
736        const identifierCache = nameCache?.get(IDENTIFIER_CACHE);
737        const lineAndColumn = identifierLineMap?.get(node);
738        // We only save the line info of FunctionLike.
739        const isFunction: boolean = sym ? Reflect.has(sym, 'isFunction') : false;
740        if (isFunction && symbolInfo && lineAndColumn) {
741          const originalName = symbolInfo.originalNameWithScope;
742          const pathWithLine: string = originalName + lineAndColumn;
743          (identifierCache as Map<string, string>).set(pathWithLine, symbolInfo.mangledName);
744          (identifierCache as Map<string, string>).delete(originalName);
745        }
746
747        let mangledName: string = mangledSymbolNames.get(sym)?.mangledName;
748        if (node?.parent.kind === SyntaxKind.ClassDeclaration) {
749          classMangledName.set(node, mangledName);
750        }
751        if (!mangledName && mangledPropertyNameOfNoSymbolImportExport !== '') {
752          mangledName = mangledPropertyNameOfNoSymbolImportExport;
753        }
754
755        if (!mangledName || mangledName === sym?.name) {
756          return node;
757        }
758
759        return factory.createIdentifier(mangledName);
760      }
761
762      function updatePropertyParameterNameNode(node: Identifier): Node {
763        let sym: Symbol | undefined = NodeUtils.findSymbolOfIdentifier(checker, node, nodeSymbolMap);
764        if (!sym || sym.valueDeclaration?.kind !== SyntaxKind.Parameter) {
765          return node;
766        }
767
768        let mangledName: string | undefined = mangledPropertyParameterSymbolNames.get(sym.valueDeclaration)?.mangledName;
769        if (!mangledName || mangledName === sym?.name) {
770          return node;
771        }
772
773        return factory.createIdentifier(mangledName);
774      }
775
776      function updateLabelNode(node: Identifier): Node {
777        let label: Label | undefined;
778        let labelName: string = '';
779
780        mangledLabelNames.forEach((value, key) => {
781          if (key.refs.includes(node)) {
782            label = key;
783            labelName = value;
784          }
785        });
786
787        return label ? factory.createIdentifier(labelName) : node;
788      }
789
790      /**
791       * import {A as B} from 'modulename';
792       * import {C as D} from 'modulename';
793       * above A、C have no symbol, so deal with them specially.
794       */
795      function mangleNoSymbolImportExportPropertyName(original: string): string {
796        const path: string = '#' + original;
797        const historyName: string = historyNameCache?.get(path);
798        let mangled = historyName ?? getPropertyMangledName(original, path);
799        if (nameCache && nameCache.get(IDENTIFIER_CACHE)) {
800          (nameCache.get(IDENTIFIER_CACHE) as Map<string, string>).set(path, mangled);
801        }
802        return mangled;
803      }
804
805      function trySearchImportExportSpecifier(node: Node): boolean {
806        while (node.parent) {
807          node = node.parent;
808          if ((isImportSpecifier(node) || isExportSpecifier(node)) && node.propertyName && isIdentifier(node.propertyName)) {
809            return true;
810          }
811        }
812        return false;
813      }
814
815      function shouldObfuscateNodeWithoutSymbol(node: Identifier): boolean {
816        if (exportObfuscation && exportElementsWithoutSymbol.has(node) && trySearchImportExportSpecifier(node)) {
817          let isGlobalNode: boolean = exportElementsWithoutSymbol.get(node);
818          if ((isGlobalNode && enableToplevel) || !isGlobalNode) {
819            return true;
820          }
821        }
822        return false;
823      }
824    }
825
826    function initWhitelist(): void {
827      if (isInitializedReservedList) {
828        return;
829      }
830      if (enablePropertyObf) {
831        const tmpReservedProps: string[] = profile?.mReservedProperties ?? [];
832        tmpReservedProps.forEach(item => {
833          PropCollections.reservedProperties.add(item);
834        });
835        PropCollections.globalMangledNamesInCache = new Set(PropCollections.historyMangledTable?.values());
836        addToSet(PropCollections.reservedProperties, AtKeepCollections.keepSymbol.propertyNames);
837        addToSet(PropCollections.reservedProperties, AtKeepCollections.keepAsConsumer.propertyNames);
838
839        if (profile?.mUniversalReservedProperties) {
840          PropCollections.universalReservedProperties = [...profile.mUniversalReservedProperties];
841        }
842        UnobfuscationCollections.reservedLangForTopLevel.forEach(element => {
843          UnobfuscationCollections.reservedLangForProperty.add(element);
844        });
845        UnobfuscationCollections.reservedExportName.forEach(element => {
846          UnobfuscationCollections.reservedExportNameAndProp.add(element);
847        });
848        UnobfuscationCollections.reservedSdkApiForGlobal.forEach(element => {
849          UnobfuscationCollections.reservedSdkApiForProp.add(element);
850        });
851      }
852      LocalVariableCollections.reservedConfig = new Set(profile?.mReservedNames ?? []);
853      profile?.mReservedToplevelNames?.forEach(item => PropCollections.reservedProperties.add(item));
854      addToSet(PropCollections.reservedProperties, AtKeepCollections.keepSymbol.globalNames);
855      addToSet(PropCollections.reservedProperties, AtKeepCollections.keepAsConsumer.globalNames);
856      profile?.mUniversalReservedToplevelNames?.forEach(item => PropCollections.universalReservedProperties.push(item));
857      isInitializedReservedList = true;
858    }
859  };
860
861  function isSkippedGlobal(enableTopLevel: boolean, scope: Scope): boolean {
862    return !enableTopLevel && isGlobalScope(scope);
863  }
864
865  export let transformerPlugin: TransformPlugin = {
866    'name': 'renameIdentifierPlugin',
867    'order': TransformerOrder.RENAME_IDENTIFIER_TRANSFORMER,
868    'createTransformerFactory': createRenameIdentifierFactory
869  };
870
871  export let nameCache: Map<string, string | Map<string, string>> = new Map();
872  export let historyNameCache: Map<string, string> = undefined;
873  export let historyUnobfuscatedNamesMap: Map<string, string[]> = undefined;
874  export let identifierLineMap: Map<Identifier, string> = new Map();
875  export let classMangledName: Map<Node, string> = new Map();
876  // Record the original class name and line number range to distinguish between class names and member method names.
877  export let classInfoInMemberMethodCache: Set<string> = new Set();
878  // Generate obfuscated names for all property and variable names.
879  export let globalGenerator: INameGenerator;
880
881  export function clearCaches(): void {
882    nameCache.clear();
883    historyNameCache = undefined;
884    historyUnobfuscatedNamesMap = undefined;
885    identifierLineMap.clear();
886    classMangledName.clear();
887    classInfoInMemberMethodCache.clear();
888    UnobfuscationCollections.unobfuscatedNamesMap.clear();
889  }
890}
891
892export = secharmony;
893