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}