1/* 2 * Copyright (c) 2023-2024 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 SyntaxKind, 18 factory, 19 forEachChild, 20 isBreakOrContinueStatement, 21 isConstructorDeclaration, 22 isExportSpecifier, 23 isIdentifier, 24 isImportSpecifier, 25 isLabeledStatement, 26 isMetaProperty, 27 isSourceFile, 28 isStructDeclaration, 29 setParentRecursive, 30 visitEachChild, 31 isPropertyDeclaration, 32 isMethodDeclaration, 33 isGetAccessor, 34 isSetAccessor, 35 isClassDeclaration, 36 isFunctionExpression, 37 isArrowFunction, 38 isVariableDeclaration, 39 isPropertyAssignment, 40 isPrivateIdentifier, 41 isParameter, 42 isPropertyAccessExpression, 43 isAnnotationDeclaration 44} from 'typescript'; 45 46import type { 47 ClassElement, 48 Declaration, 49 Identifier, 50 Node, 51 SourceFile, 52 StructDeclaration, 53 Symbol, 54 TransformationContext, 55 Transformer, 56 TransformerFactory, 57 TypeChecker 58} from 'typescript'; 59 60import { 61 createScopeManager, 62 exportElementsWithoutSymbol, 63 exportSymbolAliasMap, 64 getNameWithScopeLoc, 65 isClassScope, 66 isGlobalScope, 67 isEnumScope, 68 isInterfaceScope, 69 isObjectLiteralScope, 70 nodeSymbolMap 71} from '../../utils/ScopeAnalyzer'; 72 73import type { 74 Label, 75 Scope, 76 ScopeManager 77} from '../../utils/ScopeAnalyzer'; 78 79import { 80 IDENTIFIER_CACHE, 81 MEM_METHOD_CACHE 82} from '../../utils/NameCacheUtil'; 83 84import type {INameGenerator, NameGeneratorOptions} from '../../generator/INameGenerator'; 85import type {IOptions} from '../../configs/IOptions'; 86import type { INameObfuscationOption, IUnobfuscationOption } from '../../configs/INameObfuscationOption'; 87import type {TransformPlugin} from '../TransformPlugin'; 88import { annotationPrefix, type MangledSymbolInfo } from '../../common/type'; 89import {TransformerOrder} from '../TransformPlugin'; 90import {getNameGenerator, NameGeneratorType} from '../../generator/NameFactory'; 91import {TypeUtils} from '../../utils/TypeUtils'; 92import { 93 isInTopLevelWhitelist, 94 isInLocalWhitelist, 95 isReservedLocalVariable, 96 isReservedTopLevel, 97 recordHistoryUnobfuscatedNames, 98 isInPropertyWhitelist, 99 isReservedProperty 100} from '../../utils/TransformUtil'; 101import {NodeUtils} from '../../utils/NodeUtils'; 102import {ApiExtractor} from '../../common/ApiExtractor'; 103import {performancePrinter, ArkObfuscator, cleanFileMangledNames, FileUtils} from '../../ArkObfuscator'; 104import { endSingleFileEvent, startSingleFileEvent } from '../../utils/PrinterUtils'; 105import { EventList, endSingleFileForMoreTimeEvent, startSingleFileForMoreTimeEvent } from '../../utils/PrinterTimeAndMemUtils'; 106import { isViewPUBasedClass } from '../../utils/OhsUtil'; 107import { 108 AtIntentCollections, 109 AtKeepCollections, 110 BytecodeObfuscationCollections, 111 LocalVariableCollections, 112 PropCollections, 113 UnobfuscationCollections 114} from '../../utils/CommonCollections'; 115import { MemoryDottingDefine } from '../../utils/MemoryDottingDefine'; 116import { shouldKeepCurFileParamerters, shouldKeepParameter } from '../../utils/KeepParameterUtils'; 117import { addToSet } from '../../utils/ProjectCollections'; 118 119namespace secharmony { 120 /** 121 * Rename Identifiers, including: 122 * 1. variable name 123 * 2. function name 124 * 3. label name 125 * 4. class name/interface name/ label name 126 * we need implement some features: 127 * 1. rename identifiers 128 * 2. store/restore name to/from nameCache file. 129 * 3. do scope analysis for identifier obfuscations 130 * 131 * @param option 132 */ 133 const createRenameIdentifierFactory = function (option: IOptions): TransformerFactory<Node> | null { 134 const profile: INameObfuscationOption | undefined = option?.mNameObfuscation; 135 const shouldPrintKeptNames: boolean = !!(option.mUnobfuscationOption?.mPrintKeptNames); 136 const enablePropertyObf: boolean = !!(profile?.mRenameProperties); 137 138 if (!profile || !profile.mEnable) { 139 return null; 140 } 141 142 let options: NameGeneratorOptions = {}; 143 globalGenerator = getNameGenerator(profile.mNameGeneratorType, options); 144 145 const enableToplevel: boolean = option?.mNameObfuscation?.mTopLevel; 146 const exportObfuscation: boolean = option?.mExportObfuscation; 147 let isInitializedReservedList = false; 148 return renameIdentifierFactory; 149 150 function renameIdentifierFactory(context: TransformationContext): Transformer<Node> { 151 startSingleFileEvent(EventList.INIT_WHITELIST); 152 initWhitelist(); 153 endSingleFileEvent(EventList.INIT_WHITELIST); 154 let mangledSymbolNames: Map<Symbol, MangledSymbolInfo> = new Map<Symbol, MangledSymbolInfo>(); 155 let mangledPropertyParameterSymbolNames: Map<Declaration, MangledSymbolInfo> = new Map<Declaration, MangledSymbolInfo>(); 156 let mangledLabelNames: Map<Label, string> = new Map<Label, string>(); 157 let fileExportNames: Set<string> = undefined; 158 let fileImportNames: Set<string> = undefined; 159 let currentFileType: string | undefined = undefined; 160 exportElementsWithoutSymbol.clear(); 161 exportSymbolAliasMap.clear(); 162 nodeSymbolMap.clear(); 163 164 let historyMangledNames: Set<string> = undefined; 165 if (historyNameCache && historyNameCache.size > 0) { 166 historyMangledNames = new Set<string>(Array.from(historyNameCache.values())); 167 } 168 169 let checker: TypeChecker = undefined; 170 let manager: ScopeManager = createScopeManager(); 171 let isCurFileParamertersKept: boolean = false; 172 return renameTransformer; 173 174 /** 175 * Transformer to rename identifiers 176 * 177 * @param node ast node of a file. 178 */ 179 function renameTransformer(node: Node): Node { 180 if (nameCache.size === 0) { 181 nameCache.set(IDENTIFIER_CACHE, new Map<string, string>()); 182 nameCache.set(MEM_METHOD_CACHE, new Map<string, string>()); 183 } 184 185 if (!isSourceFile(node) || ArkObfuscator.isKeptCurrentFile) { 186 return node; 187 } 188 currentFileType = FileUtils.getFileSuffix(node.fileName).ext; 189 isCurFileParamertersKept = shouldKeepCurFileParamerters(node, profile); 190 191 const checkRecordInfo = ArkObfuscator.recordStage(MemoryDottingDefine.CREATE_CHECKER); 192 startSingleFileEvent(EventList.CREATE_CHECKER, performancePrinter.timeSumPrinter); 193 checker = TypeUtils.createChecker(node); 194 endSingleFileEvent(EventList.CREATE_CHECKER, performancePrinter.timeSumPrinter); 195 ArkObfuscator.stopRecordStage(checkRecordInfo); 196 197 const scopeRecordInfo = ArkObfuscator.recordStage(MemoryDottingDefine.SCOPE_ANALYZE); 198 startSingleFileEvent(EventList.SCOPE_ANALYZE, performancePrinter.timeSumPrinter); 199 manager.analyze(node, checker, exportObfuscation, currentFileType); 200 endSingleFileEvent(EventList.SCOPE_ANALYZE, performancePrinter.timeSumPrinter); 201 ArkObfuscator.stopRecordStage(scopeRecordInfo); 202 203 let rootScope: Scope = manager.getRootScope(); 204 fileExportNames = rootScope.fileExportNames; 205 fileImportNames = rootScope.fileImportNames; 206 let renameProcessors: ((scope: Scope) => void)[] = [renameLabelsInScope, renameNamesInScope]; 207 if (profile.mRenameProperties) { 208 renameProcessors.push(renamePropertyParametersInScope); 209 } 210 211 const obfuscateNamesRecordInfo = ArkObfuscator.recordStage(MemoryDottingDefine.OBFUSCATE_NAMES); 212 startSingleFileEvent(EventList.CREATE_OBFUSCATED_NAMES, performancePrinter.timeSumPrinter); 213 getMangledNamesInScope(rootScope, renameProcessors); 214 endSingleFileEvent(EventList.CREATE_OBFUSCATED_NAMES, performancePrinter.timeSumPrinter); 215 ArkObfuscator.stopRecordStage(obfuscateNamesRecordInfo); 216 217 rootScope = undefined; 218 219 const obfuscateNodesRecordInfo = ArkObfuscator.recordStage(MemoryDottingDefine.OBFUSCATE_NODES); 220 startSingleFileEvent(EventList.OBFUSCATE_NODES, performancePrinter.timeSumPrinter); 221 startSingleFileEvent(EventList.RENAME_IDENTIFIERS); 222 let updatedNode: Node = renameIdentifiers(node); 223 endSingleFileEvent(EventList.RENAME_IDENTIFIERS); 224 225 // obfuscate property parameter declaration 226 if (profile.mRenameProperties) { 227 startSingleFileEvent(EventList.VISIT_PROPERTY_PARAMETER); 228 updatedNode = visitPropertyParameter(updatedNode); 229 endSingleFileEvent(EventList.VISIT_PROPERTY_PARAMETER); 230 } 231 232 let parentNodes = setParentRecursive(updatedNode, true); 233 endSingleFileEvent(EventList.OBFUSCATE_NODES, performancePrinter.timeSumPrinter); 234 ArkObfuscator.stopRecordStage(obfuscateNodesRecordInfo); 235 return parentNodes; 236 } 237 238 /** 239 * get mangled names of symbols stored in scopes. 240 * 241 * @param scope scope, such as global, module, function, block 242 * @param processors processors to get mangled names 243 */ 244 function getMangledNamesInScope(scope: Scope, processors: ((scope: Scope) => void)[]): void { 245 for (const process of processors) { 246 process(scope); 247 } 248 249 let subScope = undefined; 250 while (scope.children.length > 0) { 251 subScope = scope.children.pop(); 252 getMangledNamesInScope(subScope, processors); 253 subScope = undefined; 254 } 255 } 256 257 // process symbols in scope, exclude property name. 258 function renameNamesInScope(scope: Scope): void { 259 if (isExcludeScope(scope)) { 260 return; 261 } 262 263 if (!exportObfuscation) { 264 scope.defs.forEach((def) => { 265 let parentScope = scope; 266 while (parentScope) { 267 if (parentScope.importNames && parentScope.importNames.has(def.name)) { 268 scope.defs.delete(def); 269 scope.mangledNames.add(def.name); 270 } 271 parentScope = parentScope.parent; 272 } 273 }); 274 } 275 276 startSingleFileEvent(EventList.RENAME); 277 renames(scope, scope.defs, globalGenerator); 278 endSingleFileEvent(EventList.RENAME); 279 } 280 281 // process property parameters symbols in class scope 282 function renamePropertyParametersInScope(scope: Scope): void { 283 if (!isClassScope(scope)) { 284 return; 285 } 286 287 renamePropertyParameters(scope, scope.defs, globalGenerator); 288 } 289 290 function renames(scope: Scope, defs: Set<Symbol>, generator: INameGenerator): void { 291 defs.forEach((def) => { 292 const original: string = def.name; 293 let mangled: string = original; 294 const path: string = getNameWithScopeLoc(scope, original); 295 // No allow to rename reserved names. 296 if (!Reflect.has(def, 'obfuscateAsProperty') && 297 isInLocalWhitelist(original, UnobfuscationCollections.unobfuscatedNamesMap, path, shouldPrintKeptNames) || 298 (!exportObfuscation && scope.exportNames.has(def.name)) || 299 isSkippedGlobal(enableToplevel, scope)) { 300 scope.mangledNames.add(mangled); 301 mangledSymbolNames.set(def, {mangledName: mangled, originalNameWithScope: path}); 302 return; 303 } 304 305 if (mangledSymbolNames.has(def)) { 306 return; 307 } 308 309 const declarationOfSymbol: Declaration | undefined = def.declarations?.[0]; 310 if (isCurFileParamertersKept && shouldKeepParameter(declarationOfSymbol, profile, mangledSymbolNames, checker)) { 311 mangledSymbolNames.set(def, {mangledName: mangled, originalNameWithScope: path}); 312 return; 313 } 314 315 const historyName: string = historyNameCache?.get(path); 316 if (historyName) { 317 recordHistoryUnobfuscatedNames(path); // For incremental build 318 mangled = historyName; 319 } else if (Reflect.has(def, 'obfuscateAsProperty')) { 320 mangled = getPropertyOrAnnotationMangledName(original, path); 321 } else { 322 // obfuscate local variable 323 mangled = getMangledLocalName(scope, generator); 324 } 325 // add new names to name cache 326 let identifierCache = nameCache?.get(IDENTIFIER_CACHE); 327 (identifierCache as Map<string, string>).set(path, mangled); 328 let symbolInfo: MangledSymbolInfo = { 329 mangledName: mangled, 330 originalNameWithScope: path 331 }; 332 scope.mangledNames.add(mangled); 333 mangledSymbolNames.set(def, symbolInfo); 334 }); 335 } 336 337 function renamePropertyParameters(scope: Scope, defs: Set<Symbol>, generator: INameGenerator): void { 338 defs.forEach((def) => { 339 //only rename property parameters 340 if (!def.valueDeclaration || !isParameter(def.valueDeclaration)) { 341 return; 342 } 343 const originalName: string = def.name; 344 const path: string = getNameWithScopeLoc(scope, originalName); 345 let mangledName: string; 346 if (isInPropertyWhitelist(originalName, UnobfuscationCollections.unobfuscatedPropMap, shouldPrintKeptNames)) { 347 mangledName = originalName; 348 } else { 349 mangledName = getMangledPropertyParameters(scope, generator, originalName); 350 } 351 scope.mangledNames.add(mangledName); 352 let symbolInfo: MangledSymbolInfo = { 353 mangledName: mangledName, 354 originalNameWithScope: path 355 }; 356 mangledPropertyParameterSymbolNames.set(def.valueDeclaration, symbolInfo); 357 }); 358 } 359 360 function getMangledPropertyParameters(scope: Scope, localGenerator: INameGenerator, originalName: string): string { 361 const historyName: string = PropCollections.historyMangledTable?.get(originalName); 362 let mangledName: string = historyName ? historyName : PropCollections.globalMangledTable.get(originalName); 363 while (!mangledName) { 364 let tmpName = localGenerator.getName(); 365 if (isReservedLocalVariable(tmpName)) { 366 continue; 367 } 368 if (isReservedProperty(tmpName) || tmpName === originalName) { 369 continue; 370 } 371 if (historyMangledNames && historyMangledNames.has(tmpName)) { 372 continue; 373 } 374 375 // For incremental compilation, preventing generated names from conflicting with existing global name. 376 if (PropCollections.globalMangledNamesInCache.has(tmpName)) { 377 continue; 378 } 379 if (searchMangledInParent(scope, tmpName)) { 380 continue; 381 } 382 mangledName = tmpName; 383 } 384 PropCollections.globalMangledTable.set(originalName, mangledName); 385 return mangledName; 386 } 387 388 function getMangledName(original: string): string { 389 const historyName: string = PropCollections.historyMangledTable?.get(original); 390 let mangledName: string = historyName ? historyName : PropCollections.globalMangledTable.get(original); 391 while (!mangledName) { 392 let tmpName = globalGenerator.getName(); 393 if (isReservedTopLevel(tmpName, enablePropertyObf) || 394 tmpName === original) { 395 continue; 396 } 397 398 /** 399 * In case b is obfuscated as a when only enable toplevel obfuscation: 400 * let b = 1; 401 * export let a = 1; 402 */ 403 if (cleanFileMangledNames && fileExportNames && fileExportNames.has(tmpName)) { 404 continue; 405 } 406 407 /** 408 * In case b is obfuscated as a when only enable toplevel obfuscation: 409 * import {a} from 'filePath'; 410 * let b = 1; 411 */ 412 if (cleanFileMangledNames && fileImportNames.has(tmpName)) { 413 continue; 414 } 415 416 /** 417 * In case a newly added variable get an obfuscated name that is already in history namecache 418 */ 419 if (historyMangledNames && historyMangledNames.has(tmpName)) { 420 continue; 421 } 422 423 /** 424 * For incremental compilation, preventing generated names from conflicting with existing global name. 425 */ 426 if (PropCollections.globalMangledNamesInCache.has(tmpName)) { 427 continue; 428 } 429 430 if (ApiExtractor.mConstructorPropertySet.has(tmpName)) { 431 continue; 432 } 433 434 if (ApiExtractor.mEnumMemberSet?.has(tmpName)) { 435 continue; 436 } 437 438 mangledName = tmpName; 439 } 440 441 PropCollections.globalMangledTable.set(original, mangledName); 442 return mangledName; 443 } 444 445 function getPropertyMangledName(originalName: string, nameWithScope: string): string { 446 if (isInTopLevelWhitelist(originalName, UnobfuscationCollections.unobfuscatedNamesMap, nameWithScope, enablePropertyObf, shouldPrintKeptNames)) { 447 return originalName; 448 } 449 450 let mangledName = getMangledName(originalName); 451 return mangledName; 452 } 453 454 // mangle annotation name in intermediate files 455 function getAnnotationMangledNameWithPrefix(originalName: string, nameWithScope: string): string { 456 if (isInTopLevelWhitelist(originalName, UnobfuscationCollections.unobfuscatedNamesMap, nameWithScope, enablePropertyObf, shouldPrintKeptNames)) { 457 return `${annotationPrefix}${originalName}`; 458 } 459 let mangledName: string = `${annotationPrefix}${getMangledName(originalName)}`; 460 return mangledName; 461 } 462 463 function getPropertyOrAnnotationMangledName(originalName: string, nameWithScope: string): string { 464 let name: string | undefined = originalName.startsWith(annotationPrefix) ? originalName.substring(annotationPrefix.length) : undefined; 465 if (name) { 466 // obfuscate annotation name with prefix, e.g. in intermediate files 467 return getAnnotationMangledNameWithPrefix(name, nameWithScope); 468 } else { 469 // obfuscate toplevel, export 470 return getPropertyMangledName(originalName, nameWithScope); 471 } 472 } 473 474 function isExcludeScope(scope: Scope): boolean { 475 if (isClassScope(scope)) { 476 return true; 477 } 478 479 if (isInterfaceScope(scope)) { 480 return true; 481 } 482 483 if (isEnumScope(scope)) { 484 return true; 485 } 486 487 return isObjectLiteralScope(scope); 488 } 489 490 function searchMangledInParent(scope: Scope, name: string): boolean { 491 let found: boolean = false; 492 let parentScope = scope; 493 while (parentScope) { 494 if (parentScope.mangledNames.has(name)) { 495 found = true; 496 break; 497 } 498 499 parentScope = parentScope.parent; 500 } 501 502 return found; 503 } 504 505 function getMangledLocalName(scope: Scope, localGenerator: INameGenerator): string { 506 let mangled: string = ''; 507 do { 508 mangled = localGenerator.getName()!; 509 // if it is a globally reserved name, it needs to be regenerated 510 if (isReservedLocalVariable(mangled)) { 511 mangled = ''; 512 continue; 513 } 514 515 if (fileExportNames && fileExportNames.has(mangled)) { 516 mangled = ''; 517 continue; 518 } 519 520 if (historyMangledNames && historyMangledNames.has(mangled)) { 521 mangled = ''; 522 continue; 523 } 524 525 /** 526 * For incremental compilation, preventing generated names from conflicting with existing global name. 527 */ 528 if (PropCollections.globalMangledNamesInCache.has(mangled)) { 529 mangled = ''; 530 continue; 531 } 532 533 if (searchMangledInParent(scope, mangled)) { 534 mangled = ''; 535 continue; 536 } 537 538 if (ApiExtractor.mConstructorPropertySet.has(mangled)) { 539 mangled = ''; 540 } 541 542 if (ApiExtractor.mEnumMemberSet?.has(mangled)) { 543 mangled = ''; 544 } 545 } while (mangled === ''); 546 547 return mangled; 548 } 549 550 // process labels in scope, the label can't rename as the name of top labels. 551 function renameLabelsInScope(scope: Scope): void { 552 const labels: Label[] = scope.labels; 553 if (labels.length > 0) { 554 let upperMangledLabels = getUpperMangledLabelNames(labels[0]); 555 for (const label of labels) { 556 let mangledLabel = getMangledLabel(label, upperMangledLabels); 557 mangledLabelNames.set(label, mangledLabel); 558 } 559 } 560 } 561 562 function getMangledLabel(label: Label, mangledLabels: string[]): string { 563 let mangledLabel: string = ''; 564 do { 565 mangledLabel = globalGenerator.getName(); 566 if (mangledLabel === label.name) { 567 mangledLabel = ''; 568 } 569 570 if (mangledLabels.includes(mangledLabel)) { 571 mangledLabel = ''; 572 } 573 } while (mangledLabel === ''); 574 575 return mangledLabel; 576 } 577 578 function getUpperMangledLabelNames(label: Label): string[] { 579 const results: string[] = []; 580 let parent: Label = label.parent; 581 while (parent) { 582 let mangledLabelName: string = mangledLabelNames.get(parent); 583 if (mangledLabelName) { 584 results.push(mangledLabelName); 585 } 586 parent = parent.parent; 587 } 588 589 return results; 590 } 591 592 function isFunctionLike(node: Node): boolean { 593 switch (node.kind) { 594 case SyntaxKind.FunctionDeclaration: 595 case SyntaxKind.MethodDeclaration: 596 case SyntaxKind.GetAccessor: 597 case SyntaxKind.SetAccessor: 598 case SyntaxKind.Constructor: 599 case SyntaxKind.FunctionExpression: 600 case SyntaxKind.ArrowFunction: 601 return true; 602 } 603 return false; 604 } 605 606 function nodeHasFunctionLikeChild(node: Node): boolean { 607 let hasFunctionLikeChild: boolean = false; 608 let childVisitor: (child: Node) => Node = (child: Node): Node => { 609 if (!hasFunctionLikeChild && child && isFunctionLike(child)) { 610 hasFunctionLikeChild = true; 611 } 612 return child; 613 }; 614 visitEachChild(node, childVisitor, context); 615 return hasFunctionLikeChild; 616 } 617 618 /** 619 * visit each node to change identifier name to mangled name 620 * - calculate shadow name index to find shadow node 621 * @param node 622 */ 623 function renameIdentifiers(node: Node): Node { 624 startSingleFileForMoreTimeEvent(EventList.HANDLE_POSITION_INFO); 625 let needHandlePositionInfo: boolean = isFunctionLike(node) || nodeHasFunctionLikeChild(node); 626 if (needHandlePositionInfo) { 627 // Obtain line info for nameCache. 628 handlePositionInfo(node); 629 } 630 endSingleFileForMoreTimeEvent(EventList.HANDLE_POSITION_INFO); 631 632 if (!isIdentifier(node) || !node.parent) { 633 return visitEachChild(node, renameIdentifiers, context); 634 } 635 636 if (isLabeledStatement(node.parent) || isBreakOrContinueStatement(node.parent)) { 637 return updateLabelNode(node); 638 } 639 640 startSingleFileForMoreTimeEvent(EventList.UPDATE_NAME_NODE); 641 const updatedNode = updateNameNode(node); 642 endSingleFileForMoreTimeEvent(EventList.UPDATE_NAME_NODE); 643 return updatedNode; 644 } 645 646 /** 647 * visit each property parameter to change identifier name to mangled name 648 * - calculate shadow name index to find shadow node 649 * @param node 650 */ 651 function visitPropertyParameter(node: Node): Node { 652 if (isConstructorDeclaration(node)) { 653 return visitPropertyParameterInConstructor(node); 654 } 655 656 return visitEachChild(node, visitPropertyParameter, context); 657 658 function visitPropertyParameterInConstructor(node: Node): Node { 659 if (!isIdentifier(node) || !node.parent) { 660 return visitEachChild(node, visitPropertyParameterInConstructor, context); 661 } 662 663 // we do not obfuscate the identifier of property access expression, like "a" in "this.a", 664 // since it will be obfuscated in renamePropertiesTransformer 665 if (NodeUtils.isPropertyNode(node)) { 666 return node; 667 } 668 669 return updatePropertyParameterNameNode(node); 670 } 671 } 672 673 function handlePositionInfo(node: Node): void { 674 const sourceFile = NodeUtils.getSourceFileOfNode(node); 675 if (node && node.pos < 0 && node.end < 0) { 676 // Node must have a real position for following operations. 677 // Adapting to the situation that the node does not have a real postion. 678 return; 679 } 680 const startPosition = sourceFile.getLineAndCharacterOfPosition(node.getStart()); 681 const endPosition = sourceFile.getLineAndCharacterOfPosition(node.getEnd()); 682 // 1: The line number in sourceFile starts from 0 while in IDE starts from 1. 683 const startLine = startPosition.line + 1; 684 const startCharacter = startPosition.character + 1; // 1: Same as above. 685 const endLine = endPosition.line + 1; // 1: Same as above. 686 const endCharacter = endPosition.character + 1; // 1: Same as above. 687 const lineAndColum: string = ':' + startLine + ':' + startCharacter + ':' + endLine + ':' + endCharacter; 688 689 let isProperty: boolean = isMethodDeclaration(node) || isGetAccessor(node) || 690 isSetAccessor(node) || (isConstructorDeclaration(node) && 691 !(isClassDeclaration(node.parent) && isViewPUBasedClass(node.parent))); 692 // Arrow functions are anoymous, only function expressions are considered. 693 let isPropertyParent: boolean = isFunctionExpression(node) && 694 (isPropertyDeclaration(node.parent) || isPropertyAssignment(node.parent)); 695 let isMemberMethod: boolean = isProperty || isPropertyParent; 696 if (isMemberMethod) { 697 writeMemberMethodCache(node, lineAndColum); 698 return; 699 } 700 701 let name = Reflect.get(node, 'name') as Identifier; 702 if (name?.kind === SyntaxKind.Identifier) { 703 identifierLineMap.set(name, lineAndColum); 704 } else if ((isFunctionExpression(node) || isArrowFunction(node)) && isVariableDeclaration(node.parent) && 705 node.parent.name?.kind === SyntaxKind.Identifier) { 706 // The node is anonymous, and we need to find its parent node. 707 // e.g.: let foo = function() {}; 708 identifierLineMap.set(node.parent.name, lineAndColum); 709 } 710 } 711 712 function writeMemberMethodCache(node: Node, lineAndColum: string): void { 713 let gotNode; 714 if (node.kind === SyntaxKind.Constructor) { 715 gotNode = node.parent; 716 } else if ((node.kind === SyntaxKind.FunctionExpression && 717 (isPropertyDeclaration(node.parent) || isPropertyAssignment(node.parent)))) { 718 gotNode = node.parent.initializer ?? node.parent; 719 } else { 720 gotNode = node; 721 } 722 723 let isIdentifierNode: boolean = gotNode.name && (isIdentifier(gotNode.name) || isPrivateIdentifier(gotNode.name)); 724 let valueName: string = ''; 725 726 if (isIdentifierNode) { 727 // The original method for retrieving method names used gotNode.name.escapedText. This approach limited the collection 728 // of method records in MemberMethodCache to cases where gotNode.name was an Identifier or PrivateIdentifier. 729 // To address the issue where method names starting with double underscores were transformed to start with triple underscores, 730 // we changed the retrieval method to use gotNode.name.text instead of escapedText. However, this change introduced the possibility 731 // of collecting method records when gotNode.name is a NumericLiteral or StringLiteral, which is not desired. 732 // To avoid altering the collection specifications of MemberMethodCache, we restricted the collection scenarios 733 // to match the original cases where only identifiers and private identifiers are collected. 734 valueName = gotNode.name.text; 735 } 736 737 if (valueName === '') { 738 return; 739 } 740 741 let originalName: string = valueName; 742 let keyName = originalName + lineAndColum; 743 if (node.kind === SyntaxKind.Constructor && classMangledName.has(gotNode.name)) { 744 valueName = classMangledName.get(gotNode.name); 745 classInfoInMemberMethodCache.add(keyName); 746 } 747 let memberMethodCache = nameCache?.get(MEM_METHOD_CACHE); 748 if (memberMethodCache) { 749 (memberMethodCache as Map<string, string>).set(keyName, valueName); 750 } 751 } 752 753 function updateNameNode(node: Identifier): Node { 754 // skip property in property access expression 755 if (NodeUtils.isPropertyAccessNode(node)) { 756 return node; 757 } 758 759 if (NodeUtils.isNewTargetNode(node)) { 760 return node; 761 } 762 763 let sym: Symbol | undefined = NodeUtils.findSymbolOfIdentifier(checker, node, nodeSymbolMap); 764 let mangledPropertyNameOfNoSymbolImportExport = ''; 765 if (!sym) { 766 if (shouldObfuscateNodeWithoutSymbol(node)) { 767 mangledPropertyNameOfNoSymbolImportExport = mangleNoSymbolImportExportPropertyName(node.text); 768 } else { 769 return node; 770 } 771 } 772 773 if (exportSymbolAliasMap.has(sym)) { 774 sym = exportSymbolAliasMap.get(sym); 775 } 776 777 // Add new names to name cache 778 const symbolInfo: MangledSymbolInfo = mangledSymbolNames.get(sym); 779 const identifierCache = nameCache?.get(IDENTIFIER_CACHE); 780 const lineAndColumn = identifierLineMap?.get(node); 781 // We only save the line info of FunctionLike. 782 const isFunction: boolean = sym ? Reflect.has(sym, 'isFunction') : false; 783 if (isFunction && symbolInfo && lineAndColumn) { 784 const originalName = symbolInfo.originalNameWithScope; 785 const pathWithLine: string = originalName + lineAndColumn; 786 (identifierCache as Map<string, string>).set(pathWithLine, symbolInfo.mangledName); 787 (identifierCache as Map<string, string>).delete(originalName); 788 } 789 790 let mangledName: string = mangledSymbolNames.get(sym)?.mangledName; 791 if (node?.parent.kind === SyntaxKind.ClassDeclaration) { 792 classMangledName.set(node, mangledName); 793 } 794 if (!mangledName && mangledPropertyNameOfNoSymbolImportExport !== '') { 795 mangledName = mangledPropertyNameOfNoSymbolImportExport; 796 } 797 798 if (!mangledName || mangledName === sym?.name) { 799 return node; 800 } 801 802 return factory.createIdentifier(mangledName); 803 } 804 805 function updatePropertyParameterNameNode(node: Identifier): Node { 806 let sym: Symbol | undefined = NodeUtils.findSymbolOfIdentifier(checker, node, nodeSymbolMap); 807 if (!sym || sym.valueDeclaration?.kind !== SyntaxKind.Parameter) { 808 return node; 809 } 810 811 let mangledName: string | undefined = mangledPropertyParameterSymbolNames.get(sym.valueDeclaration)?.mangledName; 812 if (!mangledName || mangledName === sym?.name) { 813 return node; 814 } 815 816 return factory.createIdentifier(mangledName); 817 } 818 819 function updateLabelNode(node: Identifier): Node { 820 let label: Label | undefined; 821 let labelName: string = ''; 822 823 mangledLabelNames.forEach((value, key) => { 824 if (key.refs.includes(node)) { 825 label = key; 826 labelName = value; 827 } 828 }); 829 830 return label ? factory.createIdentifier(labelName) : node; 831 } 832 833 /** 834 * import {A as B} from 'modulename'; 835 * import {C as D} from 'modulename'; 836 * above A、C have no symbol, so deal with them specially. 837 */ 838 function mangleNoSymbolImportExportPropertyName(original: string): string { 839 const path: string = '#' + original; 840 const historyName: string = historyNameCache?.get(path); 841 let mangled = historyName ?? getPropertyMangledName(original, path); 842 if (nameCache && nameCache.get(IDENTIFIER_CACHE)) { 843 (nameCache.get(IDENTIFIER_CACHE) as Map<string, string>).set(path, mangled); 844 } 845 return mangled; 846 } 847 848 function trySearchImportExportSpecifier(node: Node): boolean { 849 while (node.parent) { 850 node = node.parent; 851 if ((isImportSpecifier(node) || isExportSpecifier(node)) && node.propertyName && isIdentifier(node.propertyName)) { 852 return true; 853 } 854 } 855 return false; 856 } 857 858 function shouldObfuscateNodeWithoutSymbol(node: Identifier): boolean { 859 if (exportObfuscation && exportElementsWithoutSymbol.has(node) && trySearchImportExportSpecifier(node)) { 860 let isGlobalNode: boolean = exportElementsWithoutSymbol.get(node); 861 if ((isGlobalNode && enableToplevel) || !isGlobalNode) { 862 return true; 863 } 864 } 865 return false; 866 } 867 } 868 869 function initWhitelist(): void { 870 if (isInitializedReservedList) { 871 return; 872 } 873 if (enablePropertyObf) { 874 const tmpReservedProps: string[] = profile?.mReservedProperties ?? []; 875 tmpReservedProps.forEach(item => { 876 PropCollections.reservedProperties.add(item); 877 }); 878 addToSet(PropCollections.reservedProperties, AtKeepCollections.keepSymbol.propertyNames); 879 addToSet(PropCollections.reservedProperties, AtKeepCollections.keepAsConsumer.propertyNames); 880 addToSet(PropCollections.reservedProperties, AtIntentCollections.propertyNames); 881 882 if (profile?.mUniversalReservedProperties) { 883 PropCollections.universalReservedProperties = [...profile.mUniversalReservedProperties]; 884 } 885 UnobfuscationCollections.reservedLangForTopLevel.forEach(element => { 886 UnobfuscationCollections.reservedLangForProperty.add(element); 887 }); 888 UnobfuscationCollections.reservedExportName.forEach(element => { 889 UnobfuscationCollections.reservedExportNameAndProp.add(element); 890 }); 891 UnobfuscationCollections.reservedSdkApiForGlobal.forEach(element => { 892 UnobfuscationCollections.reservedSdkApiForProp.add(element); 893 }); 894 } 895 PropCollections.globalMangledNamesInCache = new Set(PropCollections.historyMangledTable?.values()); 896 LocalVariableCollections.reservedConfig = new Set(profile?.mReservedNames ?? []); 897 profile?.mReservedToplevelNames?.forEach(item => PropCollections.reservedProperties.add(item)); 898 addToSet(PropCollections.reservedProperties, AtKeepCollections.keepSymbol.globalNames); 899 addToSet(PropCollections.reservedProperties, AtKeepCollections.keepAsConsumer.globalNames); 900 addToSet(PropCollections.reservedProperties, AtIntentCollections.globalNames); 901 addToSet(UnobfuscationCollections.reservedSdkApiForProp, BytecodeObfuscationCollections.decoratorProp); 902 profile?.mUniversalReservedToplevelNames?.forEach(item => PropCollections.universalReservedProperties.push(item)); 903 isInitializedReservedList = true; 904 } 905 }; 906 907 function isSkippedGlobal(enableTopLevel: boolean, scope: Scope): boolean { 908 return !enableTopLevel && isGlobalScope(scope); 909 } 910 911 export let transformerPlugin: TransformPlugin = { 912 'name': 'renameIdentifierPlugin', 913 'order': TransformerOrder.RENAME_IDENTIFIER_TRANSFORMER, 914 'createTransformerFactory': createRenameIdentifierFactory 915 }; 916 917 export let nameCache: Map<string, string | Map<string, string>> = new Map(); 918 export let historyNameCache: Map<string, string> = undefined; 919 export let historyUnobfuscatedNamesMap: Map<string, string[]> = undefined; 920 export let identifierLineMap: Map<Identifier, string> = new Map(); 921 export let classMangledName: Map<Node, string> = new Map(); 922 // Record the original class name and line number range to distinguish between class names and member method names. 923 export let classInfoInMemberMethodCache: Set<string> = new Set(); 924 // Generate obfuscated names for all property and variable names. 925 export let globalGenerator: INameGenerator; 926 927 export function clearCaches(): void { 928 nameCache.clear(); 929 historyNameCache = undefined; 930 historyUnobfuscatedNamesMap = undefined; 931 identifierLineMap.clear(); 932 classMangledName.clear(); 933 classInfoInMemberMethodCache.clear(); 934 UnobfuscationCollections.unobfuscatedNamesMap.clear(); 935 } 936} 937 938export = secharmony; 939