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