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