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