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 forEachChild, 18 getModifiers, 19 getOriginalNode, 20 isCatchClause, 21 isClassDeclaration, 22 isConstructorDeclaration, 23 isExportDeclaration, 24 isExportSpecifier, 25 isFunctionDeclaration, 26 isFunctionLike, 27 isFunctionTypeNode, 28 isIdentifier, 29 isImportSpecifier, 30 isMethodDeclaration, 31 isNamedExports, 32 SyntaxKind, 33 isVariableDeclaration, 34 isFunctionExpression, 35 isArrowFunction, 36 isGetAccessor, 37 isSetAccessor, 38 isPropertyDeclaration, 39 isParameter, 40} from 'typescript'; 41 42import type { 43 BreakOrContinueStatement, 44 CaseBlock, 45 CatchClause, 46 ClassDeclaration, 47 ClassElement, 48 ClassExpression, 49 EnumDeclaration, 50 ExportSpecifier, 51 ForInOrOfStatement, 52 ForStatement, 53 FunctionLikeDeclaration, 54 Identifier, 55 ImportEqualsDeclaration, 56 ImportSpecifier, 57 InterfaceDeclaration, 58 LabeledStatement, 59 ModuleDeclaration, 60 NamespaceExport, 61 Node, 62 ObjectBindingPattern, 63 ObjectLiteralExpression, 64 ParameterDeclaration, 65 SourceFile, 66 Symbol, 67 SymbolTable, 68 TypeAliasDeclaration, 69 TypeChecker, 70 TypeElement 71} from 'typescript'; 72 73import { NodeUtils } from './NodeUtils'; 74import { isParameterPropertyModifier, isViewPUBasedClass } from './OhsUtil'; 75import { TypeUtils } from './TypeUtils'; 76/** 77 * kind of a scope 78 */ 79namespace secharmony { 80 type ForLikeStatement = ForStatement | ForInOrOfStatement; 81 type ClassLikeDeclaration = ClassDeclaration | ClassExpression; 82 /** 83 * A map used to track whether identifiers without symbols are in the top-level scope. 84 */ 85 export const exportElementsWithoutSymbol: Map<Node, boolean> = new Map(); 86 /** 87 * Alias symbol for export elements with corresponding original symbol 88 * key: symbols of export elements 89 * value: original symbols of export elements 90 */ 91 export const exportSymbolAliasMap: Map<Symbol, Symbol> = new Map(); 92 /** 93 * Records of nodes and corresponding symbols 94 */ 95 export const nodeSymbolMap: Map<Node, Symbol> = new Map(); 96 97 /** 98 * type of scope 99 */ 100 export enum ScopeKind { 101 GLOBAL, 102 MODULE, 103 FUNCTION, 104 CLASS, 105 FOR, 106 SWITCH, 107 BLOCK, 108 INTERFACE, 109 CATCH, 110 ENUM, 111 OBJECT_LITERAL 112 } 113 114 export function isGlobalScope(scope: Scope): boolean { 115 return scope.kind === ScopeKind.GLOBAL; 116 } 117 118 export function isFunctionScope(scope: Scope): boolean { 119 return scope.kind === ScopeKind.FUNCTION; 120 } 121 122 export function isClassScope(scope: Scope): boolean { 123 return scope.kind === ScopeKind.CLASS; 124 } 125 126 export function isInterfaceScope(scope: Scope): boolean { 127 return scope.kind === ScopeKind.INTERFACE; 128 } 129 130 export function isEnumScope(scope: Scope): boolean { 131 return scope.kind === ScopeKind.ENUM; 132 } 133 134 export function isObjectLiteralScope(scope: Scope): boolean { 135 return scope.kind === ScopeKind.OBJECT_LITERAL; 136 } 137 138 /** 139 * get a new scope. 140 * @param name - name of the scope. 141 * @param node - node of a current scope in ast. 142 * @param type - type of the scope. 143 * @param lexicalScope - indicates if the scope is a lexical scope. 144 * @param upper - parent scope of the current scope. 145 */ 146 export class Scope { 147 // scope name 148 name: string; 149 // kind of a scope, such as global ,function like, block .. 150 kind: ScopeKind; 151 // node of a current scope in ast. 152 block: Node; 153 // parent scope of current scope 154 parent: Scope | undefined; 155 // sub scopes of current scope 156 children: Scope[]; 157 158 // symbols define in current scope 159 defs: Set<Symbol>; 160 161 // labels in current scope 162 labels: Label[]; 163 164 importNames: Set<string>; 165 exportNames: Set<string>; 166 fileExportNames?: Set<string>; 167 fileImportNames?: Set<string>; 168 mangledNames: Set<string>; 169 // location path 170 loc: string; 171 172 constructor(name: string, node: Node, type: ScopeKind, lexicalScope: boolean = false, upper?: Scope) { 173 this.name = name; 174 this.kind = type; 175 this.block = node; 176 this.parent = upper; 177 this.children = []; 178 this.defs = new Set<Symbol>(); 179 this.labels = []; 180 this.importNames = new Set<string>(); 181 this.exportNames = new Set<string>(); 182 this.mangledNames = new Set<string>(); 183 this.loc = this.parent?.loc ? getNameWithScopeLoc(this.parent, this.name) : this.name; 184 185 this.parent?.addChild(this); 186 } 187 188 /** 189 * add a sub scope to current scope 190 * 191 * @param child 192 */ 193 addChild(child: Scope): void { 194 this.children.push(child); 195 } 196 197 /** 198 * add definition symbol into current scope 199 * 200 * @param def definition symbol 201 */ 202 addDefinition(def: Symbol, obfuscateAsProperty: boolean = false): void { 203 if (this.kind === ScopeKind.GLOBAL || obfuscateAsProperty) { 204 Reflect.set(def, 'obfuscateAsProperty', true); 205 } 206 this.defs.add(def); 207 } 208 209 /** 210 * add label to current scope 211 * 212 * @param label label statement 213 */ 214 addLabel(label: Label): void { 215 this.labels.push(label); 216 } 217 218 /** 219 * get symbol location 220 * 221 * @param sym symbol 222 */ 223 getSymbolLocation(sym: Symbol): string { 224 if (!this.defs.has(sym)) { 225 return ''; 226 } 227 228 return this.loc ? sym.name : getNameWithScopeLoc(this, sym.name); 229 } 230 231 /** 232 * get label location 233 * 234 * @param label 235 */ 236 getLabelLocation(label: Label): string { 237 if (!this.labels.includes(label)) { 238 return ''; 239 } 240 241 let index: number = this.labels.findIndex((lb: Label) => lb === label); 242 return this.loc ? label.name : getNameWithScopeLoc(this, index + label.name); 243 } 244 } 245 246 export interface Label { 247 name: string; 248 locInfo: string; 249 refs: Identifier[]; 250 parent: Label | undefined; 251 children: Label[]; 252 scope: Scope; 253 } 254 255 export function createLabel(node: LabeledStatement, scope: Scope, parent?: Label | undefined): Label { 256 let labelName: string = '$' + scope.labels.length + '_' + node.label.text; 257 let label: Label = { 258 'name': node.label.text, 259 'locInfo': labelName, 260 'refs': [node.label], 261 'parent': parent, 262 'children': [], 263 'scope': scope, 264 }; 265 266 scope.labels.push(label); 267 parent?.children.push(label); 268 269 return label; 270 } 271 272 export interface ScopeManager { 273 274 /** 275 * get reserved names like ViewPU component class name 276 */ 277 getReservedNames(): Set<string>; 278 279 /** 280 * do scope analysis 281 * 282 * @param ast ast tree of a source file 283 * @param checker 284 */ 285 analyze(ast: SourceFile, checker: TypeChecker, isEnabledExportObfuscation: boolean): void; 286 287 /** 288 * get root scope of a file 289 */ 290 getRootScope(): Scope; 291 292 /** 293 * find block Scope of a node 294 * @param node 295 */ 296 getScopeOfNode(node: Node): Scope | undefined; 297 } 298 299 export function createScopeManager(): ScopeManager { 300 let reservedNames: Set<string> = new Set<string>(); 301 let root: Scope; 302 let current: Scope; 303 let scopes: Scope[] = []; 304 305 let checker: TypeChecker = null; 306 let upperLabel: Label | undefined = undefined; 307 let exportObfuscation: boolean = false; 308 309 return { 310 getReservedNames, 311 analyze, 312 getRootScope, 313 getScopeOfNode, 314 }; 315 316 function analyze(ast: SourceFile, typeChecker: TypeChecker, isEnabledExportObfuscation = false): void { 317 checker = typeChecker; 318 exportObfuscation = isEnabledExportObfuscation; 319 analyzeScope(ast); 320 } 321 322 function getReservedNames(): Set<string> { 323 return reservedNames; 324 } 325 326 function getRootScope(): Scope { 327 return root; 328 } 329 330 function addSymbolInScope(node: Node): void { 331 let defSymbols: SymbolTable = node?.locals; 332 if (!defSymbols) { 333 return; 334 } 335 336 defSymbols.forEach((def: Symbol) => { 337 // with export identification, special handling. 338 if (def.exportSymbol) { 339 current.exportNames.add(def.name); 340 root.fileExportNames.add(def.name); 341 if (def.exportSymbol.name === def.name) { 342 /* For export declaration, `def` and its `exportSymbol` has same name, 343 eg. export class Ability {} 344 def.name: "Ability" 345 def.exportSymbol.name: "Ability" 346 Collect the `def.exportSymbol` since import symbol is asscociated with it. 347 */ 348 current.addDefinition(def.exportSymbol, true); 349 } else { 350 /* For default exports, `def` and its `exportSymbol` has different name, 351 eg. export default class Ability {} 352 def.name: "Ability" 353 def.exportSymbol.name: "default" 354 Collect the `def` symbol since we should obfuscate "Ability" instead of "default". 355 */ 356 current.addDefinition(def); 357 } 358 } else { 359 current.addDefinition(def); 360 } 361 }); 362 } 363 364 function addExportSymbolInScope(node: Node): void { 365 let defSymbol: Symbol = node?.symbol; 366 367 if (!defSymbol) { 368 return; 369 } 370 371 let originalSymbol: Symbol = TypeUtils.getOriginalSymbol(defSymbol, checker); 372 if (defSymbol !== originalSymbol) { 373 exportSymbolAliasMap.set(defSymbol, originalSymbol); 374 } 375 tryAddExportNamesIntoParentScope(originalSymbol, current); 376 377 // Mark export symbol as 'obfucate as property' 378 // to ensure the corresponding element can be kept by -keep-global-name 379 current.addDefinition(originalSymbol, true); 380 } 381 382 /** 383 * analyze chain of scopes 384 * @param node 385 */ 386 function analyzeScope(node: Node): void { 387 switch (node.kind) { 388 // global 389 case SyntaxKind.SourceFile: 390 analyzeSourceFile(node as SourceFile); 391 break; 392 393 // namespace or module 394 case SyntaxKind.ModuleDeclaration: 395 analyzeModule(node as ModuleDeclaration); 396 break; 397 398 // function like 399 case SyntaxKind.FunctionDeclaration: 400 case SyntaxKind.MethodDeclaration: 401 case SyntaxKind.GetAccessor: 402 case SyntaxKind.SetAccessor: 403 case SyntaxKind.Constructor: 404 case SyntaxKind.FunctionExpression: 405 case SyntaxKind.ArrowFunction: 406 analyzeFunctionLike(node as FunctionLikeDeclaration); 407 break; 408 409 // class like 410 case SyntaxKind.ClassExpression: 411 case SyntaxKind.ClassDeclaration: 412 case SyntaxKind.StructDeclaration: 413 analyzeClassLike(node as ClassLikeDeclaration); 414 break; 415 416 // for like 417 case SyntaxKind.ForStatement: 418 case SyntaxKind.ForInStatement: 419 case SyntaxKind.ForOfStatement: 420 analyzeForLike(node as ForLikeStatement); 421 break; 422 case SyntaxKind.CaseBlock: 423 // caseBlock property in switch statement 424 analyzeSwitch(node as CaseBlock); 425 break; 426 case SyntaxKind.Block: 427 // while, do ...while, block, if/else.. 428 analyzeBlock(node); 429 break; 430 431 case SyntaxKind.InterfaceDeclaration: 432 analyzeInterface(node as InterfaceDeclaration); 433 break; 434 435 case SyntaxKind.EnumDeclaration: 436 analyzeEnum(node as EnumDeclaration); 437 break; 438 439 case SyntaxKind.Identifier: 440 analyzeSymbol(node as Identifier); 441 break; 442 443 case SyntaxKind.TypeAliasDeclaration: 444 analyzeTypeAliasDeclaration(node as TypeAliasDeclaration); 445 break; 446 447 case SyntaxKind.LabeledStatement: 448 analyzeLabel(node as LabeledStatement); 449 break; 450 451 case SyntaxKind.BreakStatement: 452 case SyntaxKind.ContinueStatement: 453 analyzeBreakOrContinue(node as BreakOrContinueStatement); 454 break; 455 case SyntaxKind.ImportSpecifier: 456 analyzeImportNames(node as ImportSpecifier); 457 break; 458 459 case SyntaxKind.ObjectBindingPattern: 460 analyzeObjectBindingPatternRequire(node as ObjectBindingPattern); 461 break; 462 463 case SyntaxKind.ObjectLiteralExpression: 464 analyzeObjectLiteralExpression(node as ObjectLiteralExpression); 465 break; 466 467 case SyntaxKind.ExportSpecifier: 468 analyzeExportNames(node as ExportSpecifier); 469 break; 470 471 case SyntaxKind.NamespaceExport: 472 analyzeNamespaceExport(node as NamespaceExport); 473 break; 474 475 case SyntaxKind.CatchClause: 476 analyzeCatchClause(node as CatchClause); 477 break; 478 479 case SyntaxKind.ImportEqualsDeclaration: 480 analyzeImportEqualsDeclaration(node as ImportEqualsDeclaration); 481 break; 482 483 default: 484 forEachChild(node, analyzeScope); 485 break; 486 } 487 } 488 489 function analyzeImportNames(node: ImportSpecifier): void { 490 try { 491 const propertyNameNode: Identifier | undefined = node.propertyName; 492 if (exportObfuscation) { 493 // try to collect symbol for `A` in `import { A as B } from './file'; into current scope` 494 tryAddPropertyNameNodeSymbol(propertyNameNode); 495 const nameSymbol: Symbol | undefined = getAndRecordSymbolOfIdentifier(checker, node.name); 496 if (nameSymbol) { 497 // nameSymbol is the symbol of A in `import { A } from './file';` and propertyNameNode is undefined 498 // nameSymbol is the symbol of B in `import { A as B } from './file';` and propertyNameNode is A 499 let shouldObfuscateAsImportElement: boolean = propertyNameNode === undefined; 500 current.addDefinition(nameSymbol, shouldObfuscateAsImportElement); 501 } 502 } else { 503 const nameText = propertyNameNode ? propertyNameNode.text : node.name.text; 504 current.importNames.add(nameText); 505 root.fileImportNames.add(nameText); 506 } 507 forEachChild(node, analyzeScope); 508 } catch (e) { 509 console.error(e); 510 } 511 } 512 513 function tryAddPropertyNameNodeSymbol(propertyNameNode: Identifier | undefined): void { 514 if (!propertyNameNode) { 515 return; 516 } 517 518 if (propertyNameNode.text === 'default') { 519 return; 520 } 521 522 const propertySymbol: Symbol | undefined = getAndRecordSymbolOfIdentifier(checker, propertyNameNode); 523 524 if (!propertySymbol) { 525 exportElementsWithoutSymbol.set(propertyNameNode, current.kind === ScopeKind.GLOBAL); 526 return; 527 } 528 529 let parentNode = propertyNameNode.parent; 530 let shouldObfuscateAsExportElement: boolean = false; 531 if (isImportSpecifier(parentNode)) { 532 // If it is part of an `import` statement, it always comes from another file: 533 // Example: import { A as B } from 'module'; 534 // Here, `A` is an exported element from `module`. 535 shouldObfuscateAsExportElement = true; 536 } else if (isExportSpecifier(parentNode)) { 537 // If it is part of an `export` statement with `from`, it comes from another file: 538 // Example: export { A as B } from 'module'; 539 // Here, `A` is an exported element from `module`. 540 // 541 // If it is part of a bare `export` statement without `from`, it is declared locally: 542 // Example: export { A as B }; 543 // Here, `A` is not an exported element from another file. 544 shouldObfuscateAsExportElement = parentNode.parent?.parent?.moduleSpecifier !== undefined; 545 } 546 current.addDefinition(propertySymbol, shouldObfuscateAsExportElement); 547 } 548 549 /** example 550 * const { x1, y: customY, z = 0 }: { x: number; y?: number; z?: number } = { x: 1, y: 2 }; 551 * bindingElement.name is x1 for the first element. 552 * bindingElement.name is customY for the second element. 553 */ 554 function analyzeObjectBindingPatternRequire(node: ObjectBindingPattern): void { 555 if (!NodeUtils.isObjectBindingPatternAssignment(node)) { 556 forEachChild(node, analyzeScope); 557 return; 558 } 559 560 if (!node.elements) { 561 return; 562 } 563 564 node.elements.forEach((bindingElement) => { 565 if (!bindingElement) { 566 return; 567 } 568 569 findNoSymbolIdentifiers(bindingElement); 570 571 if (!bindingElement.name || !isIdentifier(bindingElement.name)) { 572 return; 573 } 574 575 if (bindingElement.propertyName) { 576 return; 577 } 578 579 current.importNames.add(bindingElement.name.text); 580 root.fileImportNames.add(bindingElement.name.text); 581 }); 582 } 583 584 function analyzeObjectLiteralExpression(node: ObjectLiteralExpression): void { 585 let scopeName: string = '$' + current.children.length; 586 current = new Scope(scopeName, node, ScopeKind.OBJECT_LITERAL, false, current); 587 scopes.push(current); 588 589 addSymbolInScope(node); 590 forEachChild(node, analyzeScope); 591 current = current.parent || current; 592 } 593 594 function analyzeExportNames(node: ExportSpecifier): void { 595 // get export names. 596 let curExportName: string = node.name.text; 597 current.exportNames.add(curExportName); 598 root.fileExportNames.add(curExportName); 599 if (curExportName !== 'default') { 600 addExportSymbolInScope(node); 601 } 602 const propetyNameNode: Identifier | undefined = node.propertyName; 603 if (exportObfuscation) { 604 tryAddPropertyNameNodeSymbol(propetyNameNode); 605 } 606 forEachChild(node, analyzeScope); 607 } 608 609 function analyzeNamespaceExport(node: NamespaceExport): void { 610 if (!exportObfuscation) { 611 return; 612 } 613 614 let symbol: Symbol | undefined = getAndRecordSymbolOfIdentifier(checker, node.name); 615 if (symbol) { 616 current.addDefinition(symbol, true); 617 } 618 } 619 620 function analyzeBreakOrContinue(node: BreakOrContinueStatement): void { 621 let labelName: string = node?.label?.text ?? ''; 622 let label: Label = findTargetLabel(labelName); 623 if (!label) { 624 return; 625 } 626 627 if (node.label) { 628 label?.refs.push(node.label); 629 } 630 631 forEachChild(node, analyzeScope); 632 } 633 634 function findTargetLabel(labelName: string): Label | null { 635 if (!labelName) { 636 return null; 637 } 638 639 let label: Label | undefined = upperLabel; 640 // avoid loop 641 while (label && label?.name !== labelName) { 642 label = label?.parent; 643 } 644 645 return label; 646 } 647 648 function analyzeSourceFile(node: SourceFile): void { 649 let scopeName: string = ''; 650 root = new Scope(scopeName, node, ScopeKind.GLOBAL, true); 651 root.fileExportNames = new Set<string>(); 652 root.fileImportNames = new Set<string>(); 653 current = root; 654 scopes.push(current); 655 // locals of a node(scope) is symbol that defines in current scope(node). 656 addSymbolInScope(node); 657 forEachChild(node, analyzeScope); 658 current = current.parent || current; 659 extractImportExports(); 660 } 661 662 function analyzeCatchClause(node: CatchClause): void { 663 let scopeName: string = '$' + current.children.length; 664 current = new Scope(scopeName, node, ScopeKind.CATCH, false, current); 665 scopes.push(current); 666 // add in catch declaration. 667 addSymbolInScope(node); 668 if (node.block) { 669 // add in block declaration. 670 addSymbolInScope(node.block); 671 } 672 673 forEachChild(node, analyzeScope); 674 current = current.parent || current; 675 } 676 677 function extractImportExports(): void { 678 for (const def of current.defs) { 679 if (def.exportSymbol) { 680 if (!current.exportNames.has(def.name)) { 681 current.exportNames.add(def.name); 682 root.fileExportNames.add(def.name); 683 } 684 const name: string = def.exportSymbol.name; 685 if (!current.exportNames.has(name)) { 686 current.exportNames.add(name); 687 root.fileExportNames.add(def.name); 688 } 689 } 690 } 691 } 692 693 function analyzeTypeAliasDeclaration(node: TypeAliasDeclaration): void { 694 let scopeName: string = node.name.text ?? '$' + current.children.length; 695 current = new Scope(scopeName, node, ScopeKind.INTERFACE, true, current); 696 scopes.push(current); 697 addSymbolInScope(node); 698 forEachChild(node, analyzeScope); 699 current = current.parent || current; 700 } 701 702 /** 703 * namespace ns { 704 * ... 705 * } 706 * @param node 707 */ 708 function analyzeModule(node: ModuleDeclaration): void { 709 /** 710 * if it is an anonymous scope, generate the scope name with a number, 711 * which is based on the order of its child scopes in the upper scope 712 */ 713 let scopeName: string = node.name.text ?? '$' + current.children.length; 714 current = new Scope(scopeName, node, ScopeKind.MODULE, true, current); 715 scopes.push(current); 716 addSymbolInScope(node); 717 node.forEachChild((sub: Node) => { 718 if (isIdentifier(sub)) { 719 return; 720 } 721 analyzeScope(sub); 722 }); 723 current = current.parent || current; 724 } 725 726 /** 727 * exclude constructor's parameter witch should be treated as property, example: 728 * constructor(public name){}, name should be treated as property 729 * @param node 730 */ 731 function excludeConstructorParameter(node: Node): void { 732 if (!isConstructorDeclaration(node)) { 733 return; 734 } 735 736 const visitParam = (param: ParameterDeclaration): void => { 737 const modifiers = getModifiers(param); 738 if (!modifiers || modifiers.length <= 0) { 739 return; 740 } 741 742 const findRet = modifiers.find(modifier => isParameterPropertyModifier(modifier)); 743 if (!isIdentifier(param.name) || findRet === undefined) { 744 return; 745 } 746 747 current.defs.forEach((def) => { 748 if (isIdentifier(param.name) && (def.name === param.name.text)) { 749 current.defs.delete(def); 750 current.mangledNames.add(def.name); 751 } 752 }); 753 }; 754 755 node.parameters.forEach((param) => { 756 visitParam(param); 757 }); 758 } 759 760 /** 761 * function func(param1...) { 762 * ... 763 * } 764 * @param node 765 */ 766 function analyzeFunctionLike(node: FunctionLikeDeclaration): void { 767 // For example, the constructor of the StructDeclaration, inserted by arkui, will add a virtual attribute. 768 // @ts-ignore 769 if (getOriginalNode(node).virtual) { 770 return; 771 } 772 let scopeName: string = (node?.name as Identifier)?.text ?? '$' + current.children.length; 773 let loc: string = current?.loc ? getNameWithScopeLoc(current, scopeName) : scopeName; 774 let overloading: boolean = false; 775 for (const sub of current.children) { 776 if (sub.loc === loc) { 777 overloading = true; 778 current = sub; 779 break; 780 } 781 } 782 783 if (!overloading) { 784 current = new Scope(scopeName, node, ScopeKind.FUNCTION, true, current); 785 scopes.push(current); 786 } 787 788 let nameNode: Node; 789 if ((isFunctionExpression(node) || isArrowFunction(node)) && isVariableDeclaration(node.parent)) { 790 nameNode = node.name ? node.name : node.parent.name; 791 } else { 792 nameNode = node.name; 793 } 794 let symbol: Symbol | undefined = getAndRecordSymbolOfIdentifier(checker, nameNode); 795 if (symbol) { 796 Reflect.set(symbol, 'isFunction', true); 797 } 798 799 addSymbolInScope(node); 800 /** 801 * { 802 * get name(): "INT"; 803 * set orignal(): 0; 804 * } 805 * // the above getaccessor and setaccessor were obfuscated as identifiers. 806 */ 807 if (!(isGetAccessor(node) || isSetAccessor(node)) && symbol && current.parent && !current.parent.defs.has(symbol)) { 808 /* 809 Handle the case when `FunctionLikeDeclaration` node is as initializer of variable declaration. 810 eg. const foo = function bar() {}; 811 The `current` scope is the function's scope, the `current.parent` scope is where the function is defined. 812 `foo` has already added in the parent scope, we need to add `bar` here too. 813 */ 814 current.parent.defs.add(symbol); 815 } 816 817 if (isFunctionDeclaration(node) || isMethodDeclaration(node)) { 818 // function declaration requires skipping function names 819 node.forEachChild((sub: Node) => { 820 if (isIdentifier(sub)) { 821 tryAddNoSymbolIdentifiers(sub); 822 return; 823 } 824 825 analyzeScope(sub); 826 }); 827 } else { 828 forEachChild(node, analyzeScope); 829 } 830 831 excludeConstructorParameter(node); 832 current = current.parent || current; 833 } 834 835 function analyzeSwitch(node: CaseBlock): void { 836 let scopeName: string = '$' + current.children.length; 837 current = new Scope(scopeName, node, ScopeKind.SWITCH, false, current); 838 scopes.push(current); 839 addSymbolInScope(node); 840 forEachChild(node, analyzeScope); 841 current = current.parent || current; 842 } 843 844 /** 845 * ES6+ class like scope, The members of a class aren't not allow to rename in rename identifiers transformer, but 846 * rename in rename properties transformer. 847 * 848 * @param node 849 */ 850 function analyzeClassLike(node: ClassLikeDeclaration): void { 851 if (isClassDeclaration(node) && isViewPUBasedClass(node)) { 852 reservedNames.add(node.name.text); 853 } 854 855 try { 856 let scopeName: string = node?.name?.text ?? '$' + current.children.length; 857 current = new Scope(scopeName, node, ScopeKind.CLASS, true, current); 858 scopes.push(current); 859 addSymbolInScope(node); 860 // Class members are seen as attribute names, and the reference of external symbols can be renamed as the same 861 node.symbol.members?.forEach((symbol: Symbol) => { 862 current.addDefinition(symbol); 863 }); 864 865 forEachChild(node, analyzeScope); 866 } catch (e) { 867 console.error(e); 868 } 869 870 current = current.parent || current; 871 } 872 873 function analyzeForLike(node: ForLikeStatement): void { 874 let scopeName: string = '$' + current.children.length; 875 current = new Scope(scopeName, node, ScopeKind.FOR, false, current); 876 scopes.push(current); 877 addSymbolInScope(node); 878 forEachChild(node, analyzeScope); 879 current = current.parent || current; 880 } 881 882 function analyzeBlock(node: Node): void { 883 // when block is body of a function 884 if ((isFunctionScope(current) && isFunctionLike(node.parent)) || isCatchClause(node.parent)) { 885 // skip direct block scope in function scope 886 forEachChild(node, analyzeScope); 887 return; 888 } 889 890 let scopeName: string = '$' + current.children.length; 891 current = new Scope(scopeName, node, ScopeKind.BLOCK, false, current); 892 scopes.push(current); 893 addSymbolInScope(node); 894 forEachChild(node, analyzeScope); 895 current = current.parent || current; 896 } 897 898 function analyzeInterface(node: InterfaceDeclaration): void { 899 let scopeName: string = node.name.text; 900 current = new Scope(scopeName, node, ScopeKind.INTERFACE, true, current); 901 scopes.push(current); 902 try { 903 addSymbolInScope(node); 904 } catch (e) { 905 console.error(''); 906 } 907 908 node.members?.forEach((elm: TypeElement) => { 909 if (elm?.symbol) { 910 current.addDefinition(elm.symbol); 911 } 912 }); 913 914 forEachChild(node, analyzeScope); 915 current = current.parent || current; 916 } 917 918 function analyzeEnum(node: EnumDeclaration): void { 919 let scopeName: string = node.name.text; 920 current = new Scope(scopeName, node, ScopeKind.ENUM, true, current); 921 scopes.push(current); 922 for (const member of node.members) { 923 if (member.symbol) { 924 current.addDefinition(member.symbol); 925 } 926 } 927 928 forEachChild(node, analyzeScope); 929 current = current.parent || current; 930 } 931 932 function analyzeSymbol(node: Identifier): void { 933 // ignore all identifiers that treat as property in property access 934 if (NodeUtils.isPropertyAccessNode(node)) { 935 return; 936 } 937 938 /* 939 * Skip obfuscating the parameters of a FunctionType node. 940 * For example, type MyFunc = (param: number) => void; 941 * 'param' is the parameter of 'MyFunc', so it will not be obfuscated by default. 942 */ 943 if (isParameter(node.parent) && isFunctionTypeNode(node.parent.parent)) { 944 return; 945 } 946 947 let symbol: Symbol | undefined; 948 949 try { 950 symbol = getAndRecordSymbolOfIdentifier(checker, node); 951 } catch (e) { 952 console.error(e); 953 return; 954 } 955 956 if (!symbol) { 957 current.mangledNames.add(node.text); 958 return; 959 } 960 961 // ignore all identifiers that treat as property in property declaration 962 if (NodeUtils.isPropertyDeclarationNode(node)) { 963 return; 964 } 965 966 // add def symbol that don't found in current defs. 967 addSymbolIntoDefsIfNeeded(node, symbol, current.defs); 968 } 969 970 function addSymbolIntoDefsIfNeeded(node: Identifier, symbol: Symbol, currentDefs: Set<Symbol>): boolean { 971 // process a new def not in currentDefs 972 let isSameName: boolean = false; 973 for (const def of currentDefs) { 974 if (def.name === node.text) { 975 isSameName = true; 976 break; 977 } 978 } 979 980 if (isSameName) { 981 // exclude the possibility of external symbols, as those with duplicate names have been added to currentDefs (this avoids the possibility of omissions) 982 if (!currentDefs.has(symbol) && !checkOriginalSymbolExist(symbol, currentDefs)) { 983 currentDefs.add(symbol); 984 } 985 986 if ( 987 symbol.exportSymbol && 988 !currentDefs.has(symbol.exportSymbol) && 989 !checkOriginalSymbolExist(symbol, currentDefs) 990 ) { 991 Reflect.set(symbol, 'obfuscateAsProperty', true); 992 currentDefs.add(symbol); 993 } 994 } 995 996 return isSameName; 997 } 998 999 function checkOriginalSymbolExist(symbol: Symbol, currentDefs: Set<Symbol>): boolean { 1000 const originalSymbol = exportSymbolAliasMap.get(symbol); 1001 if (originalSymbol && currentDefs.has(originalSymbol)) { 1002 return true; 1003 } 1004 1005 return false; 1006 } 1007 1008 function analyzeLabel(node: LabeledStatement): void { 1009 // labels within the same scope are allowed to be duplicated, so label names need to have numbering information to distinguish them 1010 upperLabel = upperLabel ? createLabel(node, current, upperLabel) : createLabel(node, current); 1011 forEachChild(node, analyzeScope); 1012 upperLabel = upperLabel?.parent; 1013 } 1014 1015 function getScopeOfNode(node: Node): Scope | undefined { 1016 if (!isIdentifier(node)) { 1017 return undefined; 1018 } 1019 1020 let sym: Symbol | undefined = getAndRecordSymbolOfIdentifier(checker, node); 1021 if (!sym) { 1022 return undefined; 1023 } 1024 1025 for (const scope of scopes) { 1026 if (scope?.defs.has(sym)) { 1027 return scope; 1028 } 1029 } 1030 1031 return undefined; 1032 } 1033 1034 function analyzeImportEqualsDeclaration(node: ImportEqualsDeclaration): void { 1035 let hasExport: boolean = false; 1036 if (node.modifiers) { 1037 for (const modifier of node.modifiers) { 1038 if (modifier.kind === SyntaxKind.ExportKeyword) { 1039 hasExport = true; 1040 break; 1041 } 1042 } 1043 } 1044 if (hasExport) { 1045 current.exportNames.add(node.name.text); 1046 root.fileExportNames.add(node.name.text); 1047 let sym: Symbol | undefined = getAndRecordSymbolOfIdentifier(checker, node.name); 1048 if (sym) { 1049 current.addDefinition(sym, true); 1050 } 1051 } 1052 forEachChild(node, analyzeScope); 1053 } 1054 1055 function tryAddNoSymbolIdentifiers(node: Identifier): void { 1056 if (!isIdentifier(node)) { 1057 return; 1058 } 1059 1060 // skip property in property access expression 1061 if (NodeUtils.isPropertyAccessNode(node)) { 1062 return; 1063 } 1064 1065 const sym: Symbol | undefined = getAndRecordSymbolOfIdentifier(checker, node); 1066 1067 if (!sym) { 1068 current.mangledNames.add((node as Identifier).text); 1069 } 1070 } 1071 1072 function tryAddExportNamesIntoParentScope(originalSymbol: Symbol, currentScope: Scope): void { 1073 if (currentScope.kind === ScopeKind.GLOBAL) { 1074 return; 1075 } 1076 let parentScope: Scope = currentScope.parent; 1077 while (parentScope) { 1078 tryAddExportNamesIntoCurrentScope(originalSymbol, parentScope); 1079 parentScope = parentScope.parent; 1080 } 1081 } 1082 1083 function tryAddExportNamesIntoCurrentScope(originalSymbol: Symbol, currentScope: Scope): void { 1084 if (currentScope.exportNames.has(originalSymbol.name)) { 1085 return; 1086 } 1087 let currentDefs: Set<Symbol> = currentScope.defs; 1088 for (const curDef of currentDefs) { 1089 if (curDef === originalSymbol) { 1090 currentScope.exportNames.add(originalSymbol.name); 1091 return; 1092 } 1093 } 1094 } 1095 1096 function findNoSymbolIdentifiers(node: Node): void { 1097 const noSymbolVisit = (targetNode: Node): void => { 1098 if (!isIdentifier(targetNode)) { 1099 forEachChild(targetNode, noSymbolVisit); 1100 return; 1101 } 1102 tryAddNoSymbolIdentifiers(targetNode); 1103 }; 1104 1105 noSymbolVisit(node); 1106 } 1107 1108 function getAndRecordSymbolOfIdentifier(checker: TypeChecker, node: Node | undefined): Symbol | undefined { 1109 const sym: Symbol | undefined = 1110 (node && isIdentifier(node)) ? 1111 NodeUtils.findSymbolOfIdentifier(checker, node, nodeSymbolMap) : 1112 checker.getSymbolAtLocation(node); 1113 1114 if (sym) { 1115 nodeSymbolMap.set(node, sym); 1116 } 1117 return sym; 1118 } 1119 } 1120 1121 export function getNameWithScopeLoc(scope: Scope, name: string): string { 1122 return scope.loc + '#' + name; 1123 } 1124} 1125 1126export = secharmony; 1127