• 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  forEachChild,
18  isBinaryExpression,
19  isCallExpression,
20  isClassDeclaration,
21  isComputedPropertyName,
22  isConstructorDeclaration,
23  isEnumDeclaration,
24  isIdentifier,
25  isObjectLiteralExpression,
26  isParameter,
27  isPropertyAccessExpression,
28  isPropertyAssignment,
29  isPropertyDeclaration,
30  isStructDeclaration,
31  isStringLiteral,
32  isTypeLiteralNode,
33  isVariableStatement,
34  SyntaxKind,
35  isExpressionStatement,
36  isClassExpression,
37  getModifiers,
38  isGetAccessor,
39  isSetAccessor,
40  isShorthandPropertyAssignment,
41  isSpreadAssignment,
42  isMethodDeclaration,
43  isGetAccessorDeclaration,
44  isAccessor,
45  isTypeNode,
46  isLiteralTypeNode,
47  isUnionTypeNode,
48} from 'typescript';
49
50import type {
51  ClassDeclaration,
52  ClassExpression,
53  ElementAccessExpression,
54  EnumDeclaration,
55  Expression,
56  GetAccessorDeclaration,
57  HeritageClause,
58  Identifier,
59  IndexedAccessTypeNode,
60  InterfaceDeclaration,
61  LiteralTypeNode,
62  MethodDeclaration,
63  Modifier,
64  Node,
65  NodeArray,
66  ObjectLiteralExpression,
67  PropertyAssignment,
68  PropertyName,
69  SetAccessorDeclaration,
70  ShorthandPropertyAssignment,
71  Statement,
72  StringLiteral,
73  StructDeclaration,
74  TypeAliasDeclaration
75} from 'typescript';
76
77import { ApiExtractor } from '../common/ApiExtractor';
78import { UnobfuscationCollections } from './CommonCollections';
79import { addToSet, FileWhiteList, projectWhiteListManager } from './ProjectCollections';
80import { NodeUtils } from './NodeUtils';
81
82export const stringPropsSet: Set<string> = new Set();
83/**
84 * The struct properties may be initialized in other files, but the properties in the struct definition are not obfuscated.
85 * So the whitelist of struct properties is collected during the project scanning process.
86 */
87export const structPropsSet: Set<string> = new Set();
88
89/**
90 * Add enum elements into whitelist when compiling har module to avoid obfuscating enum elements
91 * since enum elements in js file cannot be obfuscated properly.
92 */
93export const enumPropsSet: Set<string> = new Set();
94
95/**
96 * Collect the original name of export elements to ensure we can collect their properties
97 */
98export const exportOriginalNameSet: Set<string> = new Set();
99
100function containViewPU(heritageClauses: NodeArray<HeritageClause>): boolean {
101  if (!heritageClauses) {
102    return false;
103  }
104
105  let hasViewPU: boolean = false;
106  heritageClauses.forEach(
107    (heritageClause) => {
108      if (!heritageClause || !heritageClause.types) {
109        return;
110      }
111
112      const types = heritageClause.types;
113      types.forEach((typeExpression) => {
114        if (!typeExpression || !typeExpression.expression) {
115          return;
116        }
117
118        const expression = typeExpression.expression;
119        if (isIdentifier(expression) && expression.text === 'ViewPU') {
120          hasViewPU = true;
121        }
122      });
123    });
124
125  return hasViewPU;
126}
127
128/**
129 * used to ignore user defined ui component class property name
130 * @param classNode
131 */
132export function isViewPUBasedClass(classNode: ClassDeclaration | undefined): boolean {
133  if (!classNode) {
134    return false;
135  }
136
137  if (!isClassDeclaration(classNode)) {
138    return false;
139  }
140
141  const heritageClause = classNode.heritageClauses;
142  return containViewPU(heritageClause);
143}
144
145// Collect properties of ViewPU class as reserved names
146export function collectReservedStruct(memberName: PropertyName, reservedStruct: Set<string>): void {
147  const structPropertiesTemp: Set<string> | undefined = projectWhiteListManager?.fileWhiteListInfo?.fileKeepInfo.structProperties;
148  if (isIdentifier(memberName) || isStringLiteral(memberName)) {
149    reservedStruct.add(memberName.text);
150    structPropertiesTemp?.add(memberName.text);
151  }
152
153  if (isComputedPropertyName(memberName) && isStringLiteral(memberName.expression)) {
154    reservedStruct.add(memberName.expression.text);
155    structPropertiesTemp?.add(memberName.expression.text);
156  }
157}
158
159export function collectPropertyNamesAndStrings(memberName: PropertyName, propertySet: Set<string>): void {
160  const stringPropertiesTemp: Set<string> | undefined = projectWhiteListManager?.fileWhiteListInfo?.fileKeepInfo.stringProperties;
161  if (isIdentifier(memberName)) {
162    propertySet.add(memberName.text);
163  }
164
165  if (isStringLiteral(memberName)) {
166    propertySet.add(memberName.text);
167    stringPropsSet.add(memberName.text);
168    stringPropertiesTemp?.add(memberName.text);
169  }
170
171  if (isComputedPropertyName(memberName) && isStringLiteral(memberName.expression)) {
172    propertySet.add(memberName.expression.text);
173    stringPropsSet.add(memberName.expression.text);
174    stringPropertiesTemp?.add(memberName.expression.text);
175  }
176}
177
178export function getElementAccessExpressionProperties(elementAccessExpressionNode: ElementAccessExpression): void {
179  if (!elementAccessExpressionNode || !elementAccessExpressionNode.argumentExpression) {
180    return;
181  }
182
183  if (isStringLiteral(elementAccessExpressionNode.argumentExpression)) {
184    stringPropsSet.add(elementAccessExpressionNode.argumentExpression.text);
185    projectWhiteListManager?.fileWhiteListInfo?.fileKeepInfo.stringProperties.add(elementAccessExpressionNode.argumentExpression.text);
186  }
187}
188
189function addStringLiteralToSet(node: Node, stringSet: Set<string>): void {
190  if (NodeUtils.isStringLiteralTypeNode(node)) {
191    const indexType = node as LiteralTypeNode;
192    const stringLiteral = indexType.literal as StringLiteral;
193    stringSet.add(stringLiteral.text);
194    projectWhiteListManager?.fileWhiteListInfo?.fileKeepInfo.stringProperties.add(stringLiteral.text);
195  }
196}
197
198/**
199 * Process the IndexedAccessTypeNode and add the stringLiteral in its indexType to the stringPropsSet
200 * @param indexedAccessTypeNode
201 */
202export function getIndexedAccessTypeProperties(indexedAccessTypeNode: IndexedAccessTypeNode): void {
203  if (!indexedAccessTypeNode || !indexedAccessTypeNode.indexType) {
204    return;
205  }
206
207  addStringLiteralToSet(indexedAccessTypeNode.indexType, stringPropsSet);
208
209  if (isUnionTypeNode(indexedAccessTypeNode.indexType)) {
210    indexedAccessTypeNode.indexType.types.forEach((elemType) => {
211      if (!elemType) {
212        return;
213      }
214      addStringLiteralToSet(elemType, stringPropsSet);
215    });
216  }
217}
218
219export function getTypeAliasProperties(typeAliasNode: TypeAliasDeclaration, propertySet: Set<string>): void {
220  if (!typeAliasNode || !typeAliasNode.type || !isTypeLiteralNode(typeAliasNode.type)) {
221    return;
222  }
223
224  typeAliasNode.type.members.forEach((member) => {
225    if (!member || !member.name) {
226      return;
227    }
228    let memberName: PropertyName = member.name;
229    collectPropertyNamesAndStrings(memberName, propertySet);
230  });
231}
232
233/**
234 * export interface interfaceName {
235 *  a1: number;
236 *  "a2": number;
237 *  ["a3"]: number;
238 * }
239 */
240
241export function getInterfaceProperties(interfaceNode: InterfaceDeclaration, propertySet: Set<string>): void {
242  if (!interfaceNode || !interfaceNode.members) {
243    return;
244  }
245
246  interfaceNode.members.forEach((member) => {
247    if (!member || !member.name) {
248      return;
249    }
250
251    let memberName: PropertyName = member.name;
252    collectPropertyNamesAndStrings(memberName, propertySet);
253  });
254}
255
256export function isParameterPropertyModifier(modifier: Modifier): boolean {
257  if (modifier.kind === SyntaxKind.PublicKeyword ||
258    modifier.kind === SyntaxKind.PrivateKeyword ||
259    modifier.kind === SyntaxKind.ProtectedKeyword ||
260    modifier.kind === SyntaxKind.ReadonlyKeyword) {
261    return true;
262  }
263  return false;
264}
265
266export function getClassProperties(classNode: ClassDeclaration | ClassExpression | StructDeclaration, propertySet: Set<string>): void {
267  if (!classNode || !classNode.members) {
268    return;
269  }
270
271  if (isStructDeclaration(classNode)) {
272    getStructProperties(classNode, structPropsSet);
273  }
274  traverseMembersOfClass(classNode, propertySet);
275  return;
276}
277
278function traverseMembersOfClass(classNode: ClassDeclaration | ClassExpression | StructDeclaration, propertySet: Set<string>): void {
279  classNode.members.forEach((member) => {
280    if (!member) {
281      return;
282    }
283
284    const memberName: PropertyName = member.name;
285    if (memberName) {
286      collectPropertyNamesAndStrings(memberName, propertySet);
287    }
288
289    if (isConstructorDeclaration(member) && member.parameters) {
290      member.parameters.forEach((parameter) => {
291        const modifiers = getModifiers(parameter);
292        if (isParameter(parameter) && modifiers && modifiers.length > 0) {
293          if (parameter.name && isIdentifier(parameter.name)) {
294            let hasParameterPropertyModifier = modifiers.find(modifier => isParameterPropertyModifier(modifier)) !== undefined;
295            if (hasParameterPropertyModifier) {
296              propertySet.add(parameter.name.text);
297              ApiExtractor.mConstructorPropertySet.add(parameter.name.text);
298              projectWhiteListManager?.fileWhiteListInfo?.fileReservedInfo.propertyParams.add(parameter.name.text);
299            }
300          }
301          processMemberInitializer(parameter.initializer, propertySet);
302        }
303      });
304
305      if (member.body) {
306        member.body.statements.forEach((statement) => {
307          if (isExpressionStatement(statement) && isBinaryExpression(statement.expression) &&
308            statement.expression.operatorToken.kind === SyntaxKind.EqualsToken) {
309            processMemberInitializer(statement.expression.right, propertySet);
310          }
311        });
312      }
313    }
314
315    if (!isPropertyDeclaration(member) || !member.initializer) {
316      return;
317    }
318    processMemberInitializer(member.initializer, propertySet);
319  });
320  return;
321}
322
323function processMemberInitializer(memberInitializer: Expression | undefined, propertySet: Set<string>): void {
324  if (!memberInitializer) {
325    return;
326  }
327
328  if (isObjectLiteralExpression(memberInitializer)) {
329    getObjectProperties(memberInitializer, propertySet);
330    return;
331  }
332
333  if (isClassDeclaration(memberInitializer) || isClassExpression(memberInitializer) || isStructDeclaration(memberInitializer)) {
334    getClassProperties(memberInitializer, propertySet);
335    return;
336  }
337
338  if (isEnumDeclaration(memberInitializer)) {
339    getEnumProperties(memberInitializer, propertySet);
340    return;
341  }
342}
343
344export function getEnumProperties(enumNode: EnumDeclaration, propertySet: Set<string>): void {
345  if (!enumNode || !enumNode.members) {
346    return;
347  }
348
349  enumNode.members.forEach((member) => {
350    if (!member || !member.name) {
351      return;
352    }
353
354    const memberName: PropertyName = member.name;
355    collectPropertyNamesAndStrings(memberName, propertySet);
356    //other kind ignore
357  });
358
359  return;
360}
361
362export function getObjectProperties(objNode: ObjectLiteralExpression, propertySet: Set<string>): void {
363  if (!objNode || !objNode.properties) {
364    return;
365  }
366
367  objNode.properties.forEach((propertyElement) => {
368    if (!propertyElement || !propertyElement.name) {
369      return;
370    }
371
372    const propertyName: PropertyName = propertyElement.name;
373    collectPropertyNamesAndStrings(propertyName, propertySet);
374
375    //extract class element's property, example: export const hello = {info={read: {}}}
376    if (!isPropertyAssignment(propertyElement) || !propertyElement.initializer) {
377      return;
378    }
379
380    if (isObjectLiteralExpression(propertyElement.initializer)) {
381      getObjectProperties(propertyElement.initializer, propertySet);
382      return;
383    }
384
385    if (isClassDeclaration(propertyElement.initializer)) {
386      getClassProperties(propertyElement.initializer, propertySet);
387      return;
388    }
389
390    if (isEnumDeclaration(propertyElement.initializer)) {
391      getEnumProperties(propertyElement.initializer, propertySet);
392      return;
393    }
394  });
395
396  return;
397}
398
399export function getStructProperties(structNode: StructDeclaration, propertySet: Set<string>): void {
400  const fileWhiteLists: FileWhiteList | undefined = projectWhiteListManager?.fileWhiteListInfo;
401  structNode?.members?.forEach((member) => {
402    const memberName: PropertyName = member?.name;
403    if (!memberName) {
404      return;
405    }
406    if (fileWhiteLists) {
407      collectPropertyNamesAndStrings(memberName, fileWhiteLists.fileKeepInfo.structProperties);
408      addToSet(propertySet, fileWhiteLists.fileKeepInfo.structProperties);
409    } else {
410      collectPropertyNamesAndStrings(memberName, propertySet);
411    }
412  });
413}
414
415/**
416 * collect elements into export whitelist for module.exports = {A, B, C, D}
417 * since these elements can be import by `const {A, B, C, D} = require("./filePath");`
418 */
419export function getObjectExportNames(objNode: ObjectLiteralExpression, exportNames: Set<string>): void {
420  if (!objNode || !objNode.properties) {
421    return;
422  }
423
424  objNode.properties.forEach((propertyElement) => {
425    if (isPropertyAssignment(propertyElement)) {
426      /**
427       * { prop1: 123 } // collect prop1
428       * { 'prop2': 123 } // collect prop2
429       * { ['prop3']: 123 } // collect prop3
430       */
431      addExportPropertyName(propertyElement, exportNames);
432
433      let initializer = propertyElement.initializer;
434      if (isIdentifier(initializer)) {
435        /**
436         * { prop: testObj } // collect testObj into exportOriginalNameSet so that its properties can be collected
437         */
438        exportOriginalNameSet.add(initializer.text);
439      }
440      return;
441    }
442
443    if (isShorthandPropertyAssignment(propertyElement)) {
444      /**
445       * let shorthandNode = {prop1: 123};
446       * module.exports = { shorthandNode } // collect shorthandNode
447       */
448      exportNames.add(propertyElement.name.text);
449      return;
450    }
451
452    if (isMethodDeclaration(propertyElement) || isGetAccessor(propertyElement) || isSetAccessor(propertyElement)) {
453      /**
454       * { method() {} } // collect method
455       * { 'method'() {} } // collect method
456       * { ['method']() {} } // collect method
457       * { get getProp() {} } // collect getProp
458       * { get 'getProp'() {} } // collect getProp
459       * { get ['getProp']() {}} // collect getProp
460       */
461      addExportPropertyName(propertyElement, exportNames);
462      return;
463    }
464  });
465
466  return;
467}
468
469/**
470 * Collect property names in ObjectLiteralExpression
471 */
472export function addExportPropertyName(propertyElement: PropertyAssignment | MethodDeclaration |
473   GetAccessorDeclaration | SetAccessorDeclaration, exportNames: Set<string>): void {
474    let nameNode = propertyElement.name;
475
476    if (isIdentifier(nameNode) || isStringLiteral(nameNode)) {
477      exportNames.add(nameNode.text);
478    }
479
480    if (isComputedPropertyName(nameNode) && isStringLiteral(nameNode.expression)) {
481      exportNames.add(nameNode.expression.text);
482    }
483}
484
485/**
486 * Collect reserved names in enum
487 * e.g.
488 * enum H {
489 *   A,
490 *   B = A + 1
491 * }
492 * A is reserved
493 */
494export function visitEnumInitializer(childNode: Node): void {
495  if (NodeUtils.isPropertyNode(childNode)) {
496    return;
497  }
498
499  if (isTypeNode(childNode)) {
500    return;
501  }
502
503  if (!isIdentifier(childNode)) {
504    forEachChild(childNode, visitEnumInitializer);
505    return;
506  }
507
508  UnobfuscationCollections.reservedEnum.add(childNode.text);
509  projectWhiteListManager?.fileWhiteListInfo?.fileKeepInfo.enumProperties.add(childNode.text);
510}
511
512/**
513 * collect properties of ViewPU class as reserved names
514 */
515export function getViewPUClassProperties(classNode: ClassDeclaration | ClassExpression): void {
516  if (!classNode || !classNode.members) {
517    return;
518  }
519
520  classNode.members.forEach((member) => {
521    const memberName: PropertyName = member.name;
522    if (!memberName) {
523      return;
524    }
525    collectReservedStruct(memberName, UnobfuscationCollections.reservedStruct);
526  });
527}