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