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