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