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  factory,
18  isComputedPropertyName,
19  isConstructorDeclaration,
20  isElementAccessExpression,
21  isIdentifier,
22  isNumericLiteral,
23  isPrivateIdentifier,
24  isStringLiteralLike,
25  setParentRecursive,
26  visitEachChild,
27  isSourceFile
28} from 'typescript';
29
30import type {
31  ComputedPropertyName,
32  Expression,
33  Identifier,
34  Node,
35  TransformationContext,
36  Transformer,
37  TransformerFactory,
38} from 'typescript';
39
40import type {IOptions} from '../../configs/IOptions';
41import type { INameObfuscationOption } from '../../configs/INameObfuscationOption';
42import type {INameGenerator, NameGeneratorOptions} from '../../generator/INameGenerator';
43import {getNameGenerator, NameGeneratorType} from '../../generator/NameFactory';
44import type {TransformPlugin} from '../TransformPlugin';
45import {TransformerOrder} from '../TransformPlugin';
46import {NodeUtils} from '../../utils/NodeUtils';
47import { ArkObfuscator, performancePrinter } from '../../ArkObfuscator';
48import { EventList } from '../../utils/PrinterUtils';
49import {
50  isInPropertyWhitelist,
51  isReservedProperty,
52  needToRecordProperty
53} from '../../utils/TransformUtil';
54import {
55  classInfoInMemberMethodCache,
56  nameCache
57} from './RenameIdentifierTransformer';
58import { UpdateMemberMethodName } from '../../utils/NameCacheUtil';
59import { PropCollections, UnobfuscationCollections } from '../../utils/CommonCollections';
60
61namespace secharmony {
62  /**
63   * Rename Properties Transformer
64   *
65   * @param option obfuscation options
66   */
67  const createRenamePropertiesFactory = function (option: IOptions): TransformerFactory<Node> {
68    let profile: INameObfuscationOption | undefined = option?.mNameObfuscation;
69
70    if (!profile || !profile.mEnable || !profile.mRenameProperties) {
71      return null;
72    }
73
74    return renamePropertiesFactory;
75
76    function renamePropertiesFactory(context: TransformationContext): Transformer<Node> {
77      let options: NameGeneratorOptions = {};
78      let generator: INameGenerator = getNameGenerator(profile.mNameGeneratorType, options);
79      let currentConstructorParams: Set<string> = new Set<string>();
80
81      return renamePropertiesTransformer;
82
83      function renamePropertiesTransformer(node: Node): Node {
84        if (isSourceFile(node) && ArkObfuscator.isKeptCurrentFile) {
85          return node;
86        }
87
88        performancePrinter?.singleFilePrinter?.startEvent(EventList.PROPERTY_OBFUSCATION, performancePrinter.timeSumPrinter);
89        let ret: Node = renameProperties(node);
90        UpdateMemberMethodName(nameCache, PropCollections.globalMangledTable, classInfoInMemberMethodCache);
91        let parentNodes = setParentRecursive(ret, true);
92        performancePrinter?.singleFilePrinter?.endEvent(EventList.PROPERTY_OBFUSCATION, performancePrinter.timeSumPrinter);
93        return parentNodes;
94      }
95
96      function renameProperties(node: Node): Node {
97        if (isConstructorDeclaration(node)) {
98          currentConstructorParams.clear();
99        }
100
101        if (NodeUtils.isClassPropertyInConstructorParams(node)) {
102          currentConstructorParams.add((node as Identifier).escapedText.toString());
103          return renameProperty(node, false);
104        }
105
106        if (NodeUtils.isClassPropertyInConstructorBody(node, currentConstructorParams)) {
107          if (currentConstructorParams.has((node as Identifier).escapedText.toString())) {
108            return renameProperty(node, false);
109          }
110        }
111
112        if (!NodeUtils.isPropertyNode(node)) {
113          return visitEachChild(node, renameProperties, context);
114        }
115
116        if (isElementAccessExpression(node.parent)) {
117          return renameElementAccessProperty(node);
118        }
119
120        if (isComputedPropertyName(node)) {
121          return renameComputedProperty(node);
122        }
123
124        return renameProperty(node, false);
125      }
126
127      function renameElementAccessProperty(node: Node): Node {
128        if (isStringLiteralLike(node)) {
129          return renameProperty(node, false);
130        }
131        return visitEachChild(node, renameProperties, context);
132      }
133
134      function renameComputedProperty(node: ComputedPropertyName): ComputedPropertyName {
135        if (isStringLiteralLike(node.expression) || isNumericLiteral(node.expression)) {
136          let prop: Node = renameProperty(node.expression, true);
137          if (prop !== node.expression) {
138            return factory.createComputedPropertyName(prop as Expression);
139          }
140        }
141
142        if (isIdentifier(node.expression)) {
143          return node;
144        }
145
146        return visitEachChild(node, renameProperties, context);
147      }
148
149      function renameProperty(node: Node, computeName: boolean): Node {
150        if (!isStringLiteralLike(node) && !isIdentifier(node) && !isPrivateIdentifier(node) && !isNumericLiteral(node)) {
151          return visitEachChild(node, renameProperties, context);
152        }
153
154        if (isStringLiteralLike(node) && profile?.mKeepStringProperty) {
155          if (UnobfuscationCollections.printKeptName) {
156            needToRecordProperty(node.text, UnobfuscationCollections.unobfuscatedPropMap);
157          }
158          return node;
159        }
160
161        let original: string = node.text;
162        if (isInPropertyWhitelist(original, UnobfuscationCollections.unobfuscatedPropMap)) {
163          return node;
164        }
165
166        let mangledName: string = getPropertyName(original);
167
168        if (isStringLiteralLike(node)) {
169          return factory.createStringLiteral(mangledName);
170        }
171
172        /**
173         * source demo:
174         * class A {
175         *   123 = 1; // it is NumericLiteral
176         *   [456] = 2; // it is NumericLiteral within ComputedPropertyName
177         * }
178         * obfuscation result:
179         * class A {
180         *   a = 1;
181         *   ['b'] = 2;
182         * }
183         */
184        if (isNumericLiteral(node)) {
185          return computeName ? factory.createStringLiteral(mangledName) : factory.createIdentifier(mangledName);
186        }
187
188        if (isIdentifier(node) || isNumericLiteral(node)) {
189          return factory.createIdentifier(mangledName);
190        }
191
192        return factory.createPrivateIdentifier('#' + mangledName);
193      }
194
195      function getPropertyName(original: string): string {
196        const historyName: string = PropCollections.historyMangledTable?.get(original);
197        let mangledName: string = historyName ? historyName : PropCollections.globalMangledTable.get(original);
198        while (!mangledName) {
199          let tmpName = generator.getName();
200          if (isReservedProperty(tmpName) ||
201            tmpName === original) {
202            continue;
203          }
204
205          if (PropCollections.newlyOccupiedMangledProps.has(tmpName) || PropCollections.mangledPropsInNameCache.has(tmpName)) {
206            continue;
207          }
208
209          mangledName = tmpName;
210        }
211        PropCollections.globalMangledTable.set(original, mangledName);
212        PropCollections.newlyOccupiedMangledProps.add(mangledName);
213        return mangledName;
214      }
215    }
216  };
217
218  export let transformerPlugin: TransformPlugin = {
219    'name': 'renamePropertiesPlugin',
220    'order': TransformerOrder.RENAME_PROPERTIES_TRANSFORMER,
221    'createTransformerFactory': createRenamePropertiesFactory
222  };
223}
224
225export = secharmony;
226