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 ScriptTarget, 18 SymbolFlags, 19 createCompilerHost, 20 createProgram, 21 createSourceFile 22} from 'typescript'; 23 24import type { 25 CompilerHost, 26 CompilerOptions, 27 Program, 28 SourceFile, 29 Symbol, 30 TypeChecker 31} from 'typescript'; 32import { Extension, PathAndExtension } from '../common/type'; 33import { FileUtils } from './FileUtils'; 34import { EventList, performancePrinter } from '../ArkObfuscator'; 35import { endSingleFileEvent, startSingleFileEvent } from './PrinterUtils'; 36import { exportSymbolAliasMap } from './ScopeAnalyzer'; 37 38export class TypeUtils { 39 /** 40 * Create .d.ets, .d.ts, .ts ast from .d.ets, .d.ts, .ts content. 41 * Create .ts ast from .ets, .js content 42 * @param {string} sourceFilePath 43 * @param {string} content - The content in sourceFilePath 44 */ 45 public static createObfSourceFile(sourceFilePath: string, content: string): SourceFile { 46 const pathOrExtension: PathAndExtension = FileUtils.getFileSuffix(sourceFilePath); 47 const fileSuffix = pathOrExtension.ext; 48 49 if (fileSuffix === Extension.JS) { 50 sourceFilePath = pathOrExtension.path + Extension.TS; 51 } 52 53 return createSourceFile(sourceFilePath, content, ScriptTarget.ES2015, true); 54 } 55 56 public static tsToJs(ast: SourceFile): void { 57 const pathOrExtension: PathAndExtension = FileUtils.getFileSuffix(ast.fileName); 58 const fileSuffix = Extension.JS; 59 const targetName: string = pathOrExtension.path + fileSuffix; 60 ast.fileName = targetName; 61 } 62 63 public static createChecker(ast: SourceFile): TypeChecker { 64 const host: CompilerHost = createCompilerHost({}); 65 66 const customHost: CompilerHost = { 67 getSourceFile(name, languageVersion): SourceFile | undefined { 68 if (name === ast.fileName) { 69 return ast; 70 } else { 71 return undefined; 72 } 73 }, 74 // optional 75 getDefaultLibLocation: () => '', 76 getDefaultLibFileName: () => '', 77 writeFile: (filename, data) => { 78 }, 79 getCurrentDirectory: () => '', 80 useCaseSensitiveFileNames: host.useCaseSensitiveFileNames, 81 getCanonicalFileName: host.getCanonicalFileName, 82 getNewLine: host.getNewLine, 83 fileExists: () => true, 84 readFile: (name): string => { 85 return name === ast.fileName ? ast.text : undefined; 86 }, 87 // must, read program.ts => createCompilerHost 88 directoryExists: undefined, 89 getEnvironmentVariable: undefined, 90 getDirectories: undefined, 91 }; 92 93 let option: CompilerOptions = { 94 'alwaysStrict': true 95 }; 96 if (ast.fileName.endsWith('.js')) { 97 option.allowJs = true; 98 } 99 100 startSingleFileEvent(EventList.CREATE_PROGRAM, performancePrinter.timeSumPrinter); 101 let program: Program = createProgram([ast.fileName], option, customHost); 102 endSingleFileEvent(EventList.CREATE_PROGRAM, performancePrinter.timeSumPrinter); 103 104 startSingleFileEvent(EventList.GET_CHECKER, performancePrinter.timeSumPrinter); 105 let typeChecker: TypeChecker = program.getTypeChecker(); 106 endSingleFileEvent(EventList.GET_CHECKER, performancePrinter.timeSumPrinter); 107 return typeChecker; 108 } 109 110 /** 111 * Retrieves the symbol associated with the declaration site of the given symbol. 112 * 113 * This method resolves the symbol to its declaration site to ensure consistency 114 * in obfuscated naming. Obfuscation names are bound to the symbol at the declaration 115 * site. If the symbol at the declaration site differs from the symbol at the usage 116 * site, discrepancies in obfuscated names may occur. By using this method, the 117 * obfuscation name can be consistently retrieved from the declaration site symbol, 118 * ensuring uniformity between the declaration and usage sites. 119 */ 120 public static getOriginalSymbol(symbol: Symbol, checker: TypeChecker): Symbol { 121 if (!(symbol.getFlags() & SymbolFlags.Alias)) { 122 return symbol; 123 } 124 125 if (exportSymbolAliasMap.has(symbol)) { 126 return exportSymbolAliasMap.get(symbol); 127 } 128 129 // This method helps determine if the original symbol obtained for a given symbol is valid and usable. 130 // Sometimes the original symbol's name may differ from the current symbol's name due to aliasing. 131 // For example: 132 // `let A = 1; export { A as B };` 133 // The `originalSymbol` for `B` refers to the symbol for `A`, but the names are different. 134 // However, for obfuscation purposes, we want to ensure that the symbol used in the declaration (i.e., `A` in this case) 135 // aligns with the current symbol (i.e., `B`), because this allows us to apply the declaration's obfuscation name to `B`. 136 // If the names don't match, we should avoid applying the declaration's obfuscation name to the current symbol. 137 const isValidOriginalSymbol = (originalSymbol: Symbol | undefined): boolean => 138 originalSymbol !== undefined && originalSymbol.name === symbol.name; 139 140 const originalSymbol: Symbol | undefined = checker.getAliasedSymbol(symbol); 141 if (isValidOriginalSymbol(originalSymbol)) { 142 return originalSymbol!; 143 } 144 145 const immediateSymbol: Symbol | undefined = checker.getImmediateAliasedSymbol(symbol); 146 if (isValidOriginalSymbol(immediateSymbol)) { 147 return immediateSymbol!; 148 } 149 150 return symbol; 151 } 152} 153