• 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 type {
17  Expression,
18  Identifier,
19  Node,
20  ObjectBindingPattern,
21  SourceFile,
22  TypeChecker,
23  TransformationContext,
24  TransformerFactory,
25  StringLiteralLike,
26  NumericLiteral,
27  PrivateIdentifier
28} from 'typescript';
29import {
30  canHaveModifiers,
31  Symbol,
32  SyntaxKind,
33  getModifiers,
34  isBinaryExpression,
35  isBindingElement,
36  isCallExpression,
37  isClassDeclaration,
38  isClassExpression,
39  isComputedPropertyName,
40  isConstructorDeclaration,
41  isElementAccessExpression,
42  isEnumMember,
43  isGetAccessor,
44  isIdentifier,
45  isIndexedAccessTypeNode,
46  isMetaProperty,
47  isMethodDeclaration,
48  isMethodSignature,
49  isModifier,
50  isNumericLiteral,
51  isParameter,
52  isPrivateIdentifier,
53  isPropertyAccessExpression,
54  isPropertyAssignment,
55  isPropertyDeclaration,
56  isPropertySignature,
57  isQualifiedName,
58  isSetAccessor,
59  isVariableDeclaration,
60  visitEachChild,
61  isLiteralTypeNode,
62  isStringLiteralLike
63} from 'typescript';
64import {
65  getViewPUClassProperties,
66  isParameterPropertyModifier,
67  isViewPUBasedClass,
68  visitEnumInitializer
69} from './OhsUtil';
70import { Extension } from '../common/type';
71import { MergedConfig } from '../initialization/ConfigResolver';
72
73export class NodeUtils {
74  public static isPropertyDeclarationNode(node: Node): boolean {
75    let parent: Node | undefined = node.parent;
76    if (!parent) {
77      return false;
78    }
79
80    /** eg: { 'name'' : 'akira' }, pass */
81    if (isPropertyAssignment(parent)) {
82      return parent.name === node;
83    }
84
85    if (isComputedPropertyName(parent) && parent.expression === node) {
86      return true;
87    }
88
89    /** object binding pattern */
90    if (isBindingElement(parent) && parent.propertyName === node) {
91      return true;
92    }
93
94    /** eg: interface/type inf { 'name' : string}, pass */
95    if (isPropertySignature(parent) && parent.name === node) {
96      return true;
97    }
98
99    /** eg: interface/type T1 { func(arg: string): number;} */
100    if (isMethodSignature(parent) && parent.name === node) {
101      return true;
102    }
103
104    /** eg: enum { xxx = 1}; */
105    if (isEnumMember(parent) && parent.name === node) {
106      return true;
107    }
108
109    /** class { private name= 1}; */
110    if (isPropertyDeclaration(parent) && parent.name === node) {
111      return true;
112    }
113
114    /** class {'getName': function() {}} let _ = { getName() [}} */
115    if (isMethodDeclaration(parent) && parent.name === node) {
116      return true;
117    }
118
119    if (isSetAccessor(parent) && parent.name === node) {
120      return true;
121    }
122
123    return isGetAccessor(parent) && parent.name === node;
124  }
125
126  public static isPropertyOrElementAccessNode(node: Node): boolean {
127    return this.isPropertyAccessNode(node) || this.isElementAccessNode(node) || false;
128  }
129
130  public static isPropertyAccessNode(node: Node): boolean {
131    let parent: Node | undefined = node.parent;
132    if (!parent) {
133      return false;
134    }
135
136    /** eg: a.b = 1 */
137    if (isPropertyAccessExpression(parent) && parent.name === node) {
138      return true;
139    }
140    if (isPrivateIdentifier(node) && NodeUtils.isInClassDeclaration(parent)) {
141      return NodeUtils.isInExpression(parent);
142    }
143    return isQualifiedName(parent) && parent.right === node;
144  }
145
146  private static isInClassDeclaration(node: Node | undefined): boolean {
147    if (!node) {
148      return false;
149    }
150
151    if (isClassDeclaration(node) || isClassExpression(node)) {
152      return true;
153    }
154
155    return NodeUtils.isInClassDeclaration(node.parent);
156  }
157
158  public static isInClassDeclarationForTest(node: Node | undefined): boolean {
159    return NodeUtils.isInClassDeclaration(node);
160  }
161
162  private static isInExpression(node: Node | undefined): boolean {
163    return !!node && NodeUtils.isInOperator(node);
164  }
165
166  public static isInExpressionForTest(node: Node | undefined): boolean {
167    return NodeUtils.isInExpression(node);
168  }
169
170  private static isInOperator(node: Node): boolean {
171    return isBinaryExpression(node) && node.operatorToken.kind === SyntaxKind.InKeyword;
172  }
173
174  public static isInOperatorForTest(node: Node | undefined): boolean {
175    return NodeUtils.isInOperator(node);
176  }
177
178  public static isElementAccessNode(node: Node): boolean {
179    let parent: Node | undefined = node.parent;
180    if (!parent) {
181      return false;
182    }
183
184    return isElementAccessExpression(parent) && parent.argumentExpression === node;
185  }
186
187  public static isIndexedAccessNode(node: Node): boolean {
188    let parent: Node | undefined = node.parent;
189    if (!parent) {
190      return false;
191    }
192
193    return isIndexedAccessTypeNode(parent) && parent.indexType === node;
194  }
195
196  public static isStringLiteralTypeNode(node: Node): boolean {
197    return isLiteralTypeNode(node) && isStringLiteralLike(node.literal);
198  }
199
200  public static isClassPropertyInConstructorParams(node: Node): boolean {
201    if (!isIdentifier(node)) {
202      return false;
203    }
204
205    if (!node.parent || !isParameter(node.parent)) {
206      return false;
207    }
208
209    const modifiers = getModifiers(node.parent);
210    if (!modifiers || modifiers.length === 0 || !modifiers.find(modifier => isParameterPropertyModifier(modifier))) {
211      return false;
212    }
213
214    return node.parent.parent && isConstructorDeclaration(node.parent.parent);
215  }
216
217  public static isClassPropertyInConstructorBody(node: Node, constructorParams: Set<string>): boolean {
218    if (!isIdentifier(node)) {
219      return false;
220    }
221
222    const id: string = node.escapedText.toString();
223    let curNode: Node = node.parent;
224    while (curNode) {
225      if (isConstructorDeclaration(curNode) && constructorParams.has(id)) {
226        return true;
227      }
228
229      curNode = curNode.parent;
230    }
231
232    return false;
233  }
234
235  public static isPropertyNode(node: Node): boolean {
236    if (this.isPropertyOrElementAccessNode(node)) {
237      return true;
238    }
239
240    if (this.isIndexedAccessNode(node)) {
241      return true;
242    }
243
244    return this.isPropertyDeclarationNode(node);
245  }
246
247  public static isObjectBindingPatternAssignment(node: ObjectBindingPattern): boolean {
248    if (!node || !node.parent || !isVariableDeclaration(node.parent)) {
249      return false;
250    }
251
252    const initializer: Expression = node.parent.initializer;
253    return initializer && isCallExpression(initializer);
254  }
255
256  public static isDeclarationFile(node: SourceFile): boolean {
257    return node.isDeclarationFile;
258  }
259
260  public static getSourceFileOfNode(node: Node): SourceFile {
261    while (node && node.kind !== SyntaxKind.SourceFile) {
262      node = node.parent;
263    }
264    return <SourceFile>node;
265  }
266
267  public static isDETSFile(node: Node | undefined): boolean {
268    return !!node && NodeUtils.getSourceFileOfNode(node).fileName.endsWith(Extension.DETS);
269  }
270
271  public static isNewTargetNode(node: Identifier): boolean {
272    if (isMetaProperty(node.parent) && node.parent.keywordToken === SyntaxKind.NewKeyword && node.escapedText === 'target') {
273      return true;
274    }
275    return false;
276  }
277
278  public static findSymbolOfIdentifier(
279    checker: TypeChecker,
280    node: Identifier,
281    nodeSymbolMap: Map<Node, Symbol>
282  ): Symbol | undefined {
283    let sym: Symbol | undefined = nodeSymbolMap.get(node) ?? checker.getSymbolAtLocation(node);
284    if (!sym || (sym && sym.name !== 'default')) {
285      return sym;
286    }
287    /* Handle default exports, eg. export default class Ability {};
288       The expected symbol we want to find to obfuscate is named "Ability",
289       but `getSymbolAtLocation` will return the symbol named "default", so we need to continue to search.
290    */
291    let localSyms: Symbol[] = checker.getSymbolsInScope(node, sym.flags);
292    for (let i = 0; i < localSyms.length; i++) {
293      const localSym = localSyms[i];
294      // `localSym` named "Ability" has property `exportSymbol` named "default" that we find by `getSymbolAtLocation`,
295      // So the `localSym` is what we want to obfuscate.
296      if (localSym && localSym.name === node.text && localSym.exportSymbol === sym) {
297        sym = localSym;
298        break;
299      }
300    }
301    return sym;
302  }
303
304  public static isPropertyNameType(node: Node): node is StringLiteralLike | Identifier | PrivateIdentifier | NumericLiteral {
305    return isStringLiteralLike(node) || isIdentifier(node) || isPrivateIdentifier(node) || isNumericLiteral(node);
306  }
307}
308
309/**
310 * When enabling property obfuscation, collect the properties of struct.
311 * When enabling property obfuscation and the compilation output is a TS file,
312 * collect the Identifier names in the initialization expressions of enum members.
313 */
314export function collectReservedNameForObf(obfuscationConfig: MergedConfig | undefined, shouldTransformToJs: boolean): TransformerFactory<SourceFile> {
315  const disableObf = obfuscationConfig?.options === undefined || obfuscationConfig.options.disableObfuscation;
316  const enablePropertyObf = obfuscationConfig?.options.enablePropertyObfuscation;
317  // process.env.compiler === 'on': indicates that during the Webpack packaging process,
318  // the code is executed here for the first time.
319  // During the Webpack packaging process, this step will be executed twice,
320  // but only the first time will it perform subsequent operations to prevent repetition.
321  const shouldCollect = (process.env.compiler === 'on' || process.env.compileTool === 'rollup') &&
322                        !disableObf && enablePropertyObf;
323
324  return (context: TransformationContext) => {
325    return (node: SourceFile) => {
326      if (shouldCollect) {
327        node = visitEachChild(node, collectReservedNames, context);
328      }
329      return node;
330    };
331
332    function collectReservedNames(node: Node): Node {
333      // collect properties of struct
334      if (isClassDeclaration(node) && isViewPUBasedClass(node)) {
335        getViewPUClassProperties(node);
336      }
337
338      // collect enum properties
339      if (!shouldTransformToJs && isEnumMember(node) && node.initializer) {
340        node.initializer.forEachChild(visitEnumInitializer);
341        return node;
342      }
343
344      return visitEachChild(node, collectReservedNames, context);
345    }
346  };
347}
348
349// if node has export modifier
350export function hasExportModifier(node: Node): boolean {
351  // get modifiers of node
352  const modifiers = canHaveModifiers(node) ? getModifiers(node) : undefined;
353
354  if (!modifiers) {
355    return false;
356  }
357
358  for (const modifier of modifiers) {
359    if (isModifier(modifier) && modifier.kind === SyntaxKind.ExportKeyword) {
360      return true;
361    }
362  }
363
364  return false;
365}
366