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