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