• 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  factory,
18  isComputedPropertyName,
19  isConstructorDeclaration,
20  isElementAccessExpression,
21  isIdentifier,
22  isNumericLiteral,
23  isPrivateIdentifier,
24  isStringLiteralLike,
25  setParentRecursive,
26  visitEachChild,
27  isSourceFile,
28  isIndexedAccessTypeNode,
29  isLiteralTypeNode,
30  isUnionTypeNode,
31} from 'typescript';
32
33import type {
34  ComputedPropertyName,
35  Expression,
36  Identifier,
37  Node,
38  TransformationContext,
39  Transformer,
40  TransformerFactory,
41  ClassDeclaration,
42  ClassExpression,
43  StructDeclaration,
44  PropertyName,
45  StringLiteral,
46  LiteralTypeNode,
47  TypeNode
48} from 'typescript';
49
50import { annotationPrefix } from '../../common/type';
51import type {IOptions} from '../../configs/IOptions';
52import type { INameObfuscationOption } from '../../configs/INameObfuscationOption';
53import type {TransformPlugin} from '../TransformPlugin';
54import {TransformerOrder} from '../TransformPlugin';
55import {NodeUtils} from '../../utils/NodeUtils';
56import { ArkObfuscator, performancePrinter } from '../../ArkObfuscator';
57import { endSingleFileEvent, startSingleFileEvent } from '../../utils/PrinterUtils';
58import { EventList } from '../../utils/PrinterTimeAndMemUtils';
59import {
60  isInPropertyWhitelist,
61  isReservedProperty,
62  needToRecordProperty
63} from '../../utils/TransformUtil';
64import {
65  classInfoInMemberMethodCache,
66  globalGenerator,
67  nameCache
68} from './RenameIdentifierTransformer';
69import { FileUtils } from '../../ArkObfuscator';
70import { UpdateMemberMethodName } from '../../utils/NameCacheUtil';
71import { PropCollections, UnobfuscationCollections } from '../../utils/CommonCollections';
72import { MemoryDottingDefine } from '../../utils/MemoryDottingDefine';
73
74namespace secharmony {
75  /**
76   * Rename Properties Transformer
77   *
78   * @param option obfuscation options
79   */
80  const createRenamePropertiesFactory = function (option: IOptions): TransformerFactory<Node> | null {
81    let profile: INameObfuscationOption | undefined = option?.mNameObfuscation;
82    let shouldPrintKeptNames: boolean = !!(option.mUnobfuscationOption?.mPrintKeptNames);
83
84    if (!profile || !profile.mEnable || !profile.mRenameProperties) {
85      return null;
86    }
87
88    return renamePropertiesFactory;
89
90    function renamePropertiesFactory(context: TransformationContext): Transformer<Node> {
91      let currentFileType: string | undefined = undefined;
92      return renamePropertiesTransformer;
93
94      function renamePropertiesTransformer(node: Node): Node {
95        if (isSourceFile(node) && ArkObfuscator.isKeptCurrentFile) {
96          return node;
97        }
98        currentFileType = isSourceFile(node) ? FileUtils.getFileSuffix(node.fileName).ext : undefined;
99        const recordInfo = ArkObfuscator.recordStage(MemoryDottingDefine.PROPERTY_OBFUSCATION);
100        startSingleFileEvent(EventList.PROPERTY_OBFUSCATION, performancePrinter.timeSumPrinter);
101
102        startSingleFileEvent(EventList.RENAME_PROPERTIES);
103        let ret: Node = renameProperties(node);
104        endSingleFileEvent(EventList.RENAME_PROPERTIES);
105
106        startSingleFileEvent(EventList.UPDATE_MEMBER_METHOD_NAME);
107        UpdateMemberMethodName(nameCache, PropCollections.globalMangledTable, classInfoInMemberMethodCache);
108        endSingleFileEvent(EventList.UPDATE_MEMBER_METHOD_NAME);
109
110        startSingleFileEvent(EventList.SET_PARENT_RECURSIVE);
111        let parentNodes = setParentRecursive(ret, true);
112        endSingleFileEvent(EventList.SET_PARENT_RECURSIVE);
113
114        endSingleFileEvent(EventList.PROPERTY_OBFUSCATION, performancePrinter.timeSumPrinter);
115        ArkObfuscator.stopRecordStage(recordInfo);
116        return parentNodes;
117      }
118
119      function renameProperties(node: Node): Node {
120        if (NodeUtils.isObjectLiteralInAnnotation(node, currentFileType)) {
121          return node;
122        }
123
124        if (!NodeUtils.isPropertyNode(node)) {
125          return visitEachChild(node, renameProperties, context);
126        }
127
128        if (isElementAccessExpression(node.parent)) {
129          return renameElementAccessProperty(node);
130        }
131
132        if (isIndexedAccessTypeNode(node.parent)) {
133          return renameIndexedAccessProperty(node);
134        }
135
136        if (isComputedPropertyName(node)) {
137          return renameComputedProperty(node);
138        }
139
140        return renameProperty(node, false);
141      }
142
143      function renameElementAccessProperty(node: Node): Node {
144        if (isStringLiteralLike(node)) {
145          return renameProperty(node, false);
146        }
147        return visitEachChild(node, renameProperties, context);
148      }
149
150      function renameIndexedAccessProperty(node: Node): Node {
151        if (NodeUtils.isStringLiteralTypeNode(node)) {
152          let prop = renameProperty((node as LiteralTypeNode).literal, false);
153          if (prop !== (node as LiteralTypeNode).literal) {
154            return factory.createLiteralTypeNode(prop as StringLiteral);
155          }
156          return visitEachChild(node, renameProperties, context);
157        }
158
159        if (!isUnionTypeNode(node)) {
160          return visitEachChild(node, renameProperties, context);
161        }
162
163        let isChanged: boolean = false;
164        const elemTypes = node.types.map((elemType) => {
165          if (!elemType || !NodeUtils.isStringLiteralTypeNode(elemType)) {
166            return elemType;
167          }
168          const prop = renameProperty((elemType as LiteralTypeNode).literal, false);
169          if (prop !== (elemType as LiteralTypeNode).literal) {
170            isChanged = true;
171            return factory.createLiteralTypeNode(prop as StringLiteral);
172          }
173          return elemType;
174        });
175        if (isChanged) {
176          return factory.createUnionTypeNode(elemTypes);
177        }
178        return visitEachChild(node, renameProperties, context);
179      }
180
181      function renameComputedProperty(node: ComputedPropertyName): ComputedPropertyName {
182        if (isStringLiteralLike(node.expression) || isNumericLiteral(node.expression)) {
183          let prop: Node = renameProperty(node.expression, true);
184          if (prop !== node.expression) {
185            return factory.createComputedPropertyName(prop as Expression);
186          }
187        }
188
189        if (isIdentifier(node.expression)) {
190          return node;
191        }
192
193        return visitEachChild(node, renameProperties, context);
194      }
195
196      function renameProperty(node: Node, computeName: boolean): Node {
197        if (!NodeUtils.isPropertyNameType(node)) {
198          return visitEachChild(node, renameProperties, context);
199        }
200
201        if (isStringLiteralLike(node) && profile?.mKeepStringProperty) {
202          if (shouldPrintKeptNames) {
203            needToRecordProperty(node.text, UnobfuscationCollections.unobfuscatedPropMap);
204          }
205          return node;
206        }
207
208        let original: string = node.text;
209        if (isInPropertyWhitelist(original, UnobfuscationCollections.unobfuscatedPropMap, shouldPrintKeptNames)) {
210          return node;
211        }
212
213        let mangledName: string = original;
214        let name: string | undefined = original.startsWith(annotationPrefix) ? original.substring(annotationPrefix.length) : undefined;
215        if (name) {
216          mangledName = getAnnotationMangledNameWithPrefix(name);
217        } else {
218          mangledName = getPropertyMangledName(original);
219        }
220
221        if (isStringLiteralLike(node)) {
222          return factory.createStringLiteral(mangledName);
223        }
224
225        /**
226         * source demo:
227         * class A {
228         *   123 = 1; // it is NumericLiteral
229         *   [456] = 2; // it is NumericLiteral within ComputedPropertyName
230         * }
231         * obfuscation result:
232         * class A {
233         *   a = 1;
234         *   ['b'] = 2;
235         * }
236         */
237        if (isNumericLiteral(node)) {
238          return computeName ? factory.createStringLiteral(mangledName) : factory.createIdentifier(mangledName);
239        }
240
241        if (isIdentifier(node) || isNumericLiteral(node)) {
242          return factory.createIdentifier(mangledName);
243        }
244
245        return factory.createPrivateIdentifier('#' + mangledName);
246      }
247
248      function getPropertyMangledName(original: string): string {
249        let mangledName: string = getMangledName(original);
250        return mangledName;
251      }
252
253      function getAnnotationMangledNameWithPrefix(original: string): string {
254        let mangledName: string = `${annotationPrefix}${getMangledName(original)}`;
255        return mangledName;
256      }
257
258      function getMangledName(original: string): string {
259        const historyName: string = PropCollections.historyMangledTable?.get(original);
260        let mangledName: string = historyName ? historyName : PropCollections.globalMangledTable.get(original);
261        while (!mangledName) {
262          let tmpName = globalGenerator.getName();
263          if (isReservedProperty(tmpName) ||
264            tmpName === original) {
265            continue;
266          }
267
268          // For incremental compilation, preventing generated names from conflicting with existing global name.
269          if (PropCollections.globalMangledNamesInCache.has(tmpName)) {
270            continue;
271          }
272
273          mangledName = tmpName;
274        }
275        PropCollections.globalMangledTable.set(original, mangledName);
276        return mangledName;
277      }
278    }
279  };
280
281  export let transformerPlugin: TransformPlugin = {
282    'name': 'renamePropertiesPlugin',
283    'order': TransformerOrder.RENAME_PROPERTIES_TRANSFORMER,
284    'createTransformerFactory': createRenamePropertiesFactory
285  };
286}
287
288export = secharmony;
289