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 factory, 18 forEachChild, 19 isBreakOrContinueStatement, 20 isIdentifier, 21 isLabeledStatement, 22 isSourceFile, 23 setParentRecursive, 24 visitEachChild, 25} from 'typescript'; 26 27import type { 28 Identifier, 29 Node, 30 SourceFile, 31 Symbol, 32 TransformationContext, 33 Transformer, 34 TransformerFactory, 35 TypeChecker 36} from 'typescript'; 37 38import { 39 createScopeManager, 40 isClassScope, 41 isGlobalScope, 42 isEnumScope, 43 isInterfaceScope, 44 isObjectLiteralScope 45} from '../../utils/ScopeAnalyzer'; 46 47import type { 48 Label, 49 Scope, 50 ScopeManager 51} from '../../utils/ScopeAnalyzer'; 52 53import type {INameGenerator, NameGeneratorOptions} from '../../generator/INameGenerator'; 54import type {IOptions} from '../../configs/IOptions'; 55import type {INameObfuscationOption} from '../../configs/INameObfuscationOption'; 56import type {TransformPlugin} from '../TransformPlugin'; 57import {getNameGenerator, NameGeneratorType} from '../../generator/NameFactory'; 58import {TypeUtils} from '../../utils/TypeUtils'; 59import {collectIdentifiers} from '../../utils/TransformUtil'; 60import {NodeUtils} from '../../utils/NodeUtils'; 61 62namespace secharmony { 63 /** 64 * Rename Identifiers, including: 65 * 1. variable name 66 * 2. function name 67 * 3. label name 68 * 4. class name/interface name/ label name 69 * we need implement some features: 70 * 1. rename identifiers 71 * 2. store/restore name to/from nameCache file. 72 * 3. do scope analysis for identifier obfuscations 73 * 74 * @param option 75 */ 76 const createRenameIdentifierFactory = function (option: IOptions): TransformerFactory<Node> { 77 const profile: INameObfuscationOption | undefined = option?.mNameObfuscation; 78 if (!profile || !profile.mEnable) { 79 return null; 80 } 81 82 const openTopLevel: boolean = option?.mTopLevel; 83 return renameIdentifierFactory; 84 85 function renameIdentifierFactory(context: TransformationContext): Transformer<Node> { 86 let reservedNames: string[] = [...(profile?.mReservedNames ?? []), 'this', '__global']; 87 let mangledSymbolNames: Map<Symbol, string> = new Map<Symbol, string>(); 88 let mangledLabelNames: Map<Label, string> = new Map<Label, string>(); 89 90 let options: NameGeneratorOptions = {}; 91 if (profile.mNameGeneratorType === NameGeneratorType.HEX) { 92 options.hexWithPrefixSuffix = true; 93 } 94 95 let generator: INameGenerator = getNameGenerator(profile.mNameGeneratorType, options); 96 97 let historyMangledNames: Set<string> = undefined; 98 if (historyNameCache && historyNameCache.size > 0) { 99 historyMangledNames = new Set<string>(Array.from(historyNameCache.values())); 100 } 101 102 let checker: TypeChecker = undefined; 103 let manager: ScopeManager = createScopeManager(); 104 let shadowIdentifiers: Identifier[] = undefined; 105 106 let identifierIndex: number = 0; 107 return renameTransformer; 108 109 /** 110 * Transformer to rename identifiers 111 * 112 * @param node ast node of a file. 113 */ 114 function renameTransformer(node: Node): Node { 115 if (!isSourceFile(node)) { 116 return node; 117 } 118 119 const shadowSourceAst: SourceFile = TypeUtils.createNewSourceFile(node); 120 checker = TypeUtils.createChecker(shadowSourceAst); 121 manager.analyze(shadowSourceAst, checker); 122 123 manager.getReservedNames().forEach((name) => { 124 reservedNames.push(name); 125 }); 126 // collect all identifiers of shadow sourceFile 127 shadowIdentifiers = collectIdentifiers(shadowSourceAst, context); 128 129 if (nameCache === undefined) { 130 nameCache = new Map<string, string>(); 131 } 132 133 let root: Scope = manager.getRootScope(); 134 renameInScope(root); 135 return setParentRecursive(visit(node), true); 136 } 137 138 /** 139 * rename symbol table store in scopes... 140 * 141 * @param scope scope, such as global, module, function, block 142 */ 143 function renameInScope(scope: Scope): void { 144 // process labels in scope, the label can't rename as the name of top labels. 145 renameLabelsInScope(scope); 146 // process symbols in scope, exclude property name. 147 renameNamesInScope(scope); 148 149 for (const subScope of scope.children) { 150 renameInScope(subScope); 151 } 152 } 153 154 function renameNamesInScope(scope: Scope): void { 155 if (scope.parent) { 156 scope.parent.mangledNames.forEach((value) => { 157 scope.mangledNames.add(value); 158 }); 159 160 scope.parent.importNames.forEach((value) => { 161 scope.importNames.add(value); 162 }); 163 } 164 165 if (isExcludeScope(scope)) { 166 return; 167 } 168 169 scope.defs.forEach((def) => { 170 if (scope.importNames.has(def.name)) { 171 scope.defs.delete(def); 172 } 173 }); 174 175 generator.reset(); 176 renames(scope, scope.defs, generator); 177 } 178 179 function renames(scope: Scope, defs: Set<Symbol>, generator: INameGenerator): void { 180 const localCache: Map<string, string> = new Map<string, string>(); 181 findNoSymbolIdentifiers(scope); 182 183 defs.forEach((def) => { 184 const original: string = def.name; 185 let mangled: string = original; 186 // No allow to rename reserved names. 187 if (reservedNames.includes(original) || scope.exportNames.has(def.name) || isSkippedGlobal(openTopLevel, scope)) { 188 scope.mangledNames.add(mangled); 189 mangledSymbolNames.set(def, mangled); 190 return; 191 } 192 193 if (mangledSymbolNames.has(def)) { 194 return; 195 } 196 197 const path: string = scope.loc + '#' + original; 198 const historyName: string = historyNameCache?.get(path); 199 const specifyName: string = historyName ? historyName : nameCache.get(path); 200 if (specifyName) { 201 mangled = specifyName; 202 } else { 203 const sameMangled: string = localCache.get(original); 204 mangled = sameMangled ? sameMangled : getMangled(scope, generator); 205 } 206 207 // add new names to name cache 208 nameCache.set(path, mangled); 209 scope.mangledNames.add(mangled); 210 mangledSymbolNames.set(def, mangled); 211 localCache.set(original, mangled); 212 }); 213 } 214 215 function isExcludeScope(scope: Scope): boolean { 216 if (isClassScope(scope)) { 217 return true; 218 } 219 220 if (isInterfaceScope(scope)) { 221 return true; 222 } 223 224 if (isEnumScope(scope)) { 225 return true; 226 } 227 228 return isObjectLiteralScope(scope); 229 } 230 231 function getMangled(scope: Scope, localGenerator: INameGenerator): string { 232 let mangled: string = ''; 233 do { 234 mangled = localGenerator.getName()!; 235 // if it is a globally reserved name, it needs to be regenerated 236 if (reservedNames.includes(mangled)) { 237 mangled = ''; 238 continue; 239 } 240 241 if (scope.importNames && scope.importNames.has(mangled)) { 242 mangled = ''; 243 continue; 244 } 245 246 if (scope.exportNames && scope.exportNames.has(mangled)) { 247 mangled = ''; 248 continue; 249 } 250 251 if (historyMangledNames && historyMangledNames.has(mangled)) { 252 mangled = ''; 253 continue; 254 } 255 256 // the anme has already been generated in the current scope 257 if (scope.mangledNames.has(mangled)) { 258 mangled = ''; 259 } 260 } while (mangled === ''); 261 262 return mangled; 263 } 264 265 function renameLabelsInScope(scope: Scope): void { 266 const labels: Label[] = scope.labels; 267 if (labels.length > 0) { 268 let upperMangledLabels = getUpperMangledLabelNames(labels[0]); 269 for (const label of labels) { 270 let mangledLabel = getMangledLabel(label, upperMangledLabels); 271 mangledLabelNames.set(label, mangledLabel); 272 } 273 } 274 } 275 276 function getMangledLabel(label: Label, mangledLabels: string[]): string { 277 let mangledLabel: string = ''; 278 do { 279 mangledLabel = generator.getName(); 280 if (mangledLabel === label.name) { 281 mangledLabel = ''; 282 } 283 284 if (mangledLabels.includes(mangledLabel)) { 285 mangledLabel = ''; 286 } 287 } while (mangledLabel === ''); 288 289 return mangledLabel; 290 } 291 292 function getUpperMangledLabelNames(label: Label): string[] { 293 const results: string[] = []; 294 let parent: Label = label.parent; 295 while (parent) { 296 let mangledLabelName: string = mangledLabelNames.get(parent); 297 if (mangledLabelName) { 298 results.push(mangledLabelName); 299 } 300 parent = parent.parent; 301 } 302 303 return results; 304 } 305 306 /** 307 * visit each node to change identifier name to mangled name 308 * - calculate shadow name index to find shadow node 309 * @param node 310 */ 311 function visit(node: Node): Node { 312 if (!isIdentifier(node) || !node.parent) { 313 return visitEachChild(node, visit, context); 314 } 315 316 if (isLabeledStatement(node.parent) || isBreakOrContinueStatement(node.parent)) { 317 identifierIndex += 1; 318 return updateLabelNode(node); 319 } 320 321 const shadowNode: Identifier = shadowIdentifiers[identifierIndex]; 322 identifierIndex += 1; 323 return updateNameNode(node, shadowNode); 324 } 325 326 function findNoSymbolIdentifiers(scope: Scope): void { 327 const noSymbolVisit = (targetNode: Node): void => { 328 if (!isIdentifier(targetNode)) { 329 forEachChild(targetNode, noSymbolVisit); 330 return; 331 } 332 333 // skip property in property access expression 334 if (NodeUtils.isPropertyAccessNode(targetNode)) { 335 return; 336 } 337 338 const sym: Symbol | undefined = checker.getSymbolAtLocation(targetNode); 339 if (!sym) { 340 scope.mangledNames.add((targetNode as Identifier).escapedText.toString()); 341 } 342 }; 343 344 noSymbolVisit(scope.block); 345 } 346 347 function updateNameNode(node: Identifier, shadowNode: Identifier): Node { 348 // skip property in property access expression 349 if (NodeUtils.isPropertyAccessNode(node)) { 350 return node; 351 } 352 353 const sym: Symbol | undefined = checker.getSymbolAtLocation(shadowNode); 354 if (!sym || sym.name === 'default') { 355 return node; 356 } 357 358 const mangledName: string = mangledSymbolNames.get(sym); 359 if (!mangledName || mangledName === sym.name) { 360 return node; 361 } 362 363 return factory.createIdentifier(mangledName); 364 } 365 366 function updateLabelNode(node: Identifier): Node { 367 let label: Label | undefined; 368 let labelName: string = ''; 369 370 mangledLabelNames.forEach((value, key) => { 371 if (key.refs.includes(node)) { 372 label = key; 373 labelName = value; 374 } 375 }); 376 377 return label ? factory.createIdentifier(labelName) : node; 378 } 379 } 380 }; 381 382 function isSkippedGlobal(enableTopLevel: boolean, scope: Scope): boolean { 383 return !enableTopLevel && isGlobalScope(scope); 384 } 385 386 const TRANSFORMER_ORDER: number = 9; 387 export let transformerPlugin: TransformPlugin = { 388 'name': 'renameIdentifierPlugin', 389 'order': (1 << TRANSFORMER_ORDER), 390 'createTransformerFactory': createRenameIdentifierFactory 391 }; 392 393 export let nameCache: Map<string, string> = undefined; 394 export let historyNameCache: Map<string, string> = undefined; 395} 396 397export = secharmony; 398