• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (c) 2023 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  forEachChild,
19  isComputedPropertyName,
20  isConstructorDeclaration,
21  isElementAccessExpression,
22  isEnumMember,
23  isIdentifier,
24  isClassDeclaration,
25  isNumericLiteral,
26  isPrivateIdentifier,
27  isStringLiteralLike,
28  isTypeNode,
29  setParentRecursive,
30  visitEachChild,
31  isStringLiteral
32} from 'typescript';
33
34import type {
35  ComputedPropertyName,
36  Expression,
37  Identifier,
38  Node,
39  TransformationContext,
40  Transformer,
41  TransformerFactory,
42  ClassDeclaration,
43  ClassExpression,
44  StructDeclaration,
45  PropertyName
46} from 'typescript';
47
48import type {IOptions} from '../../configs/IOptions';
49import type {INameObfuscationOption} from '../../configs/INameObfuscationOption';
50import type {INameGenerator, NameGeneratorOptions} from '../../generator/INameGenerator';
51import {getNameGenerator, NameGeneratorType} from '../../generator/NameFactory';
52import type {TransformPlugin} from '../TransformPlugin';
53import {NodeUtils} from '../../utils/NodeUtils';
54import {collectPropertyNamesAndStrings, isViewPUBasedClass} from '../../utils/OhsUtil';
55
56namespace secharmony {
57  /**
58   * global mangled properties table used by all files in a project
59   */
60  export let globalMangledTable: Map<string, string> = undefined;
61
62  // used for property cache
63  export let historyMangledTable: Map<string, string> = undefined;
64
65  /**
66   * Rename Properties Transformer
67   *
68   * @param option obfuscation options
69   */
70  const createRenamePropertiesFactory = function (option: IOptions): TransformerFactory<Node> {
71    let profile: INameObfuscationOption | undefined = option?.mNameObfuscation;
72
73    if (!profile || !profile.mEnable || !profile.mRenameProperties) {
74      return null;
75    }
76
77    return renamePropertiesFactory;
78
79    function renamePropertiesFactory(context: TransformationContext): Transformer<Node> {
80      let options: NameGeneratorOptions = {};
81      if (profile.mNameGeneratorType === NameGeneratorType.HEX) {
82        options.hexWithPrefixSuffix = true;
83      }
84
85      let generator: INameGenerator = getNameGenerator(profile.mNameGeneratorType, options);
86
87      let tmpReservedProps: string[] = profile?.mReservedProperties ?? [];
88      let reservedProperties: Set<string> = new Set<string>(tmpReservedProps);
89
90      let currentConstructorParams: Set<string> = new Set<string>();
91
92      return renamePropertiesTransformer;
93
94      function renamePropertiesTransformer(node: Node): Node {
95        collectReservedNames(node);
96        if (globalMangledTable === undefined) {
97          globalMangledTable = new Map<string, string>();
98        }
99
100        let ret: Node = renameProperties(node);
101        return setParentRecursive(ret, true);
102      }
103
104      function renameProperties(node: Node): Node {
105        if (isConstructorDeclaration(node)) {
106          currentConstructorParams.clear();
107        }
108
109        if (NodeUtils.isClassPropertyInConstructorParams(node)) {
110          currentConstructorParams.add((node as Identifier).escapedText.toString());
111          return renameProperty(node, false);
112        }
113
114        if (NodeUtils.isClassPropertyInConstructorBody(node, currentConstructorParams)) {
115          if (currentConstructorParams.has((node as Identifier).escapedText.toString())) {
116            return renameProperty(node, false);
117          }
118        }
119
120        if (!NodeUtils.isPropertyNode(node)) {
121          return visitEachChild(node, renameProperties, context);
122        }
123
124        if (isElementAccessExpression(node.parent)) {
125          return renameElementAccessProperty(node);
126        }
127
128        if (isComputedPropertyName(node)) {
129          return renameComputedProperty(node);
130        }
131
132        return renameProperty(node, false);
133      }
134
135      function renameElementAccessProperty(node: Node): Node {
136        if (isStringLiteralLike(node)) {
137          return renameProperty(node, false);
138        }
139        return visitEachChild(node, renameProperties, context);
140      }
141
142      function renameComputedProperty(node: ComputedPropertyName): ComputedPropertyName {
143        if (isStringLiteralLike(node.expression) || isNumericLiteral(node.expression)) {
144          let prop: Node = renameProperty(node.expression, true);
145          if (prop !== node.expression) {
146            return factory.createComputedPropertyName(prop as Expression);
147          }
148        }
149
150        if (isIdentifier(node.expression)) {
151          return node;
152        }
153
154        return visitEachChild(node, renameProperties, context);
155      }
156
157      function renameProperty(node: Node, computeName: boolean): Node {
158        if (!isStringLiteralLike(node) && !isIdentifier(node) && !isPrivateIdentifier(node) && !isNumericLiteral(node)) {
159          return visitEachChild(node, renameProperties, context);
160        }
161
162        let original: string = node.text;
163        if (reservedProperties.has(original)) {
164          return node;
165        }
166
167        let mangledName: string = getPropertyName(original);
168
169        if (isStringLiteralLike(node)) {
170          return factory.createStringLiteral(mangledName);
171        }
172
173        if (isNumericLiteral(node)) {
174          return computeName ? factory.createStringLiteral(mangledName) : factory.createIdentifier(mangledName);
175
176        }
177
178        if (isIdentifier(node) || isNumericLiteral(node)) {
179          return factory.createIdentifier(mangledName);
180        }
181
182        return factory.createPrivateIdentifier('#' + mangledName);
183      }
184
185      function getPropertyName(original: string): string {
186        if (reservedProperties.has(original)) {
187          return original;
188        }
189
190        const historyName: string = historyMangledTable?.get(original);
191        let mangledName: string = historyName ? historyName : globalMangledTable.get(original);
192
193        while (!mangledName) {
194          mangledName = generator.getName();
195          if (mangledName === original || reservedProperties.has(mangledName)) {
196            mangledName = null;
197            continue;
198          }
199
200          let reserved: string[] = [...globalMangledTable.values()];
201          if (historyMangledTable) {
202            reserved = [...reserved, ...historyMangledTable.values()];
203          }
204
205          if (reserved.includes(mangledName)) {
206            mangledName = null;
207          }
208        }
209        globalMangledTable.set(original, mangledName);
210        return mangledName;
211      }
212
213      function visitEnumInitializer(childNode: Node): void {
214        if (!isIdentifier(childNode)) {
215          forEachChild(childNode, visitEnumInitializer);
216          return;
217        }
218
219        if (NodeUtils.isPropertyNode(childNode)) {
220          return;
221        }
222
223        if (isTypeNode(childNode)) {
224          return;
225        }
226
227        reservedProperties.add(childNode.text);
228      }
229
230      // enum syntax has special scenarios
231      function collectReservedNames(node: Node): void {
232        // collect ViewPU class properties
233        if (isClassDeclaration(node) && isViewPUBasedClass(node)) {
234          getViewPUClassProperties(node, reservedProperties);
235          return;
236        }
237
238        // collect reserved name of enum
239        // example: enum H {A, B = A + 1}, enum H = {A, B= 1 + (A + 1)}; A is reserved
240        if (isEnumMember(node) && node.initializer) {
241          // collect enum properties
242          node.initializer.forEachChild(visitEnumInitializer);
243          return;
244        }
245
246        forEachChild(node, collectReservedNames);
247      }
248
249      function getViewPUClassProperties(classNode: ClassDeclaration | ClassExpression | StructDeclaration, propertySet: Set<string>): void {
250        if (!classNode || !classNode.members) {
251          return;
252        }
253
254        classNode.members.forEach((member) => {
255          const memberName: PropertyName = member.name;
256          if (!member || !memberName) {
257            return;
258          }
259          collectPropertyNamesAndStrings(memberName, propertySet);
260        });
261      }
262    }
263  };
264
265  const TRANSFORMER_ORDER: number = 6;
266  export let transformerPlugin: TransformPlugin = {
267    'name': 'renamePropertiesPlugin',
268    'order': (1 << TRANSFORMER_ORDER),
269    'createTransformerFactory': createRenamePropertiesFactory
270  };
271}
272
273export = secharmony;
274