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 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} from 'typescript'; 32 33import type { 34 ClassElement, 35 Identifier, 36 Node, 37 SourceFile, 38 StructDeclaration, 39 Symbol, 40 TransformationContext, 41 Transformer, 42 TransformerFactory, 43 TypeChecker 44} from 'typescript'; 45 46import { 47 createScopeManager, 48 isClassScope, 49 isGlobalScope, 50 isEnumScope, 51 isInterfaceScope, 52 isObjectLiteralScope, 53 noSymbolIdentifier, 54} from '../../utils/ScopeAnalyzer'; 55 56import type { 57 Label, 58 Scope, 59 ScopeManager 60} from '../../utils/ScopeAnalyzer'; 61 62import type {INameGenerator, NameGeneratorOptions} from '../../generator/INameGenerator'; 63import type {IOptions} from '../../configs/IOptions'; 64import type {INameObfuscationOption} from '../../configs/INameObfuscationOption'; 65import type {TransformPlugin} from '../TransformPlugin'; 66import {TransformerOrder} from '../TransformPlugin'; 67import {getNameGenerator, NameGeneratorType} from '../../generator/NameFactory'; 68import {TypeUtils} from '../../utils/TypeUtils'; 69import {collectIdentifiersAndStructs} from '../../utils/TransformUtil'; 70import {NodeUtils} from '../../utils/NodeUtils'; 71import {ApiExtractor} from '../../common/ApiExtractor'; 72import { globalMangledTable, historyMangledTable, reservedProperties } from './RenamePropertiesTransformer'; 73 74namespace secharmony { 75 /** 76 * Rename Identifiers, including: 77 * 1. variable name 78 * 2. function name 79 * 3. label name 80 * 4. class name/interface name/ label name 81 * we need implement some features: 82 * 1. rename identifiers 83 * 2. store/restore name to/from nameCache file. 84 * 3. do scope analysis for identifier obfuscations 85 * 86 * @param option 87 */ 88 const createRenameIdentifierFactory = function (option: IOptions): TransformerFactory<Node> { 89 const profile: INameObfuscationOption | undefined = option?.mNameObfuscation; 90 if (!profile || !profile.mEnable) { 91 return null; 92 } 93 94 let options: NameGeneratorOptions = {}; 95 if (profile.mNameGeneratorType === NameGeneratorType.HEX) { 96 options.hexWithPrefixSuffix = true; 97 } 98 let generator: INameGenerator = getNameGenerator(profile.mNameGeneratorType, options); 99 100 const openTopLevel: boolean = option?.mNameObfuscation?.mTopLevel; 101 const exportObfuscation: boolean = option?.mExportObfuscation; 102 return renameIdentifierFactory; 103 104 function renameIdentifierFactory(context: TransformationContext): Transformer<Node> { 105 let reservedNames: string[] = [...(profile?.mReservedNames ?? []), 'this', '__global']; 106 profile?.mReservedToplevelNames?.forEach(item => reservedProperties.add(item)); 107 let mangledSymbolNames: Map<Symbol, string> = new Map<Symbol, string>(); 108 let mangledLabelNames: Map<Label, string> = new Map<Label, string>(); 109 noSymbolIdentifier.clear(); 110 111 let historyMangledNames: Set<string> = undefined; 112 if (historyNameCache && historyNameCache.size > 0) { 113 historyMangledNames = new Set<string>(Array.from(historyNameCache.values())); 114 } 115 116 let checker: TypeChecker = undefined; 117 let manager: ScopeManager = createScopeManager(); 118 let shadowIdentifiers: Identifier[] = undefined; 119 let shadowStructs: StructDeclaration[] = undefined; 120 121 let identifierIndex: number = 0; 122 let structIndex: number = 0; 123 return renameTransformer; 124 125 /** 126 * Transformer to rename identifiers 127 * 128 * @param node ast node of a file. 129 */ 130 function renameTransformer(node: Node): Node { 131 if (!isSourceFile(node)) { 132 return node; 133 } 134 135 const shadowSourceAst: SourceFile = TypeUtils.createNewSourceFile(node); 136 checker = TypeUtils.createChecker(shadowSourceAst); 137 manager.analyze(shadowSourceAst, checker, exportObfuscation); 138 139 // the reservedNames of manager contain the struct name. 140 if (!exportObfuscation) { 141 manager.getReservedNames().forEach((name) => { 142 reservedNames.push(name); 143 }); 144 } 145 146 if (nameCache === undefined) { 147 nameCache = new Map<string, string>(); 148 } 149 150 let root: Scope = manager.getRootScope(); 151 renameInScope(root); 152 root = undefined; 153 // collect all identifiers of shadow sourceFile 154 const identifiersAndStructs = collectIdentifiersAndStructs(shadowSourceAst, context); 155 shadowIdentifiers = identifiersAndStructs.shadowIdentifiers; 156 shadowStructs = identifiersAndStructs.shadowStructs; 157 158 let ret: Node = visit(node); 159 ret = tryRemoveVirtualConstructor(ret); 160 return setParentRecursive(ret, true); 161 } 162 163 /** 164 * rename symbol table store in scopes... 165 * 166 * @param scope scope, such as global, module, function, block 167 */ 168 function renameInScope(scope: Scope): void { 169 // process labels in scope, the label can't rename as the name of top labels. 170 renameLabelsInScope(scope); 171 // process symbols in scope, exclude property name. 172 renameNamesInScope(scope); 173 174 let subScope = undefined; 175 while (scope.children.length > 0) { 176 subScope = scope.children.pop(); 177 renameInScope(subScope); 178 subScope = undefined; 179 } 180 } 181 182 function renameNamesInScope(scope: Scope): void { 183 if (isExcludeScope(scope)) { 184 return; 185 } 186 187 if (!exportObfuscation) { 188 scope.defs.forEach((def) => { 189 let parentScope = scope; 190 while (parentScope) { 191 if (parentScope.importNames && parentScope.importNames.has(def.name)) { 192 scope.defs.delete(def); 193 scope.mangledNames.add(def.name); 194 } 195 parentScope = parentScope.parent; 196 } 197 }); 198 } 199 200 renames(scope, scope.defs, generator); 201 } 202 203 function renames(scope: Scope, defs: Set<Symbol>, generator: INameGenerator): void { 204 defs.forEach((def) => { 205 const original: string = def.name; 206 let mangled: string = original; 207 // No allow to rename reserved names. 208 if ((!Reflect.has(def, 'obfuscateAsProperty') && reservedNames.includes(original)) || 209 (!exportObfuscation && scope.exportNames.has(def.name)) || 210 isSkippedGlobal(openTopLevel, scope)) { 211 scope.mangledNames.add(mangled); 212 return; 213 } 214 215 if (mangledSymbolNames.has(def)) { 216 return; 217 } 218 219 const path: string = scope.loc + '#' + original; 220 const historyName: string = historyNameCache?.get(path); 221 222 if (historyName) { 223 mangled = historyName; 224 } else if (Reflect.has(def, 'obfuscateAsProperty')) { 225 mangled = getPropertyMangledName(original); 226 } else { 227 mangled = getMangled(scope, generator); 228 } 229 230 // add new names to name cache 231 nameCache.set(path, mangled); 232 scope.mangledNames.add(mangled); 233 mangledSymbolNames.set(def, mangled); 234 }); 235 } 236 237 function getPropertyMangledName(original: string): string { 238 if (reservedProperties.has(original)) { 239 return original; 240 } 241 242 const historyName: string = historyMangledTable?.get(original); 243 let mangledName: string = historyName ? historyName : globalMangledTable.get(original); 244 245 while (!mangledName) { 246 let tmpName = generator.getName(); 247 if (reservedProperties.has(tmpName) || tmpName === original) { 248 continue; 249 } 250 251 let isInGlobalMangledTable = false; 252 for (const value of globalMangledTable.values()) { 253 if (value === tmpName) { 254 isInGlobalMangledTable = true; 255 break; 256 } 257 } 258 259 if (isInGlobalMangledTable) { 260 continue; 261 } 262 263 let isInHistoryMangledTable = false; 264 if (historyMangledTable) { 265 for (const value of historyMangledTable.values()) { 266 if (value === tmpName) { 267 isInHistoryMangledTable = true; 268 break; 269 } 270 } 271 } 272 273 if (!isInHistoryMangledTable) { 274 mangledName = tmpName; 275 break; 276 } 277 } 278 279 globalMangledTable.set(original, mangledName); 280 return mangledName; 281 } 282 283 function isExcludeScope(scope: Scope): boolean { 284 if (isClassScope(scope)) { 285 return true; 286 } 287 288 if (isInterfaceScope(scope)) { 289 return true; 290 } 291 292 if (isEnumScope(scope)) { 293 return true; 294 } 295 296 return isObjectLiteralScope(scope); 297 } 298 299 function searchMangledInParent(scope: Scope, name: string): boolean { 300 let found: boolean = false; 301 let parentScope = scope; 302 while (parentScope) { 303 if (parentScope.mangledNames.has(name)) { 304 found = true; 305 break; 306 } 307 308 parentScope = parentScope.parent; 309 } 310 311 return found; 312 } 313 314 function getMangled(scope: Scope, localGenerator: INameGenerator): string { 315 let mangled: string = ''; 316 do { 317 mangled = localGenerator.getName()!; 318 // if it is a globally reserved name, it needs to be regenerated 319 if (reservedNames.includes(mangled)) { 320 mangled = ''; 321 continue; 322 } 323 324 if (scope.exportNames && scope.exportNames.has(mangled)) { 325 mangled = ''; 326 continue; 327 } 328 329 if (historyMangledNames && historyMangledNames.has(mangled)) { 330 mangled = ''; 331 continue; 332 } 333 334 if (searchMangledInParent(scope, mangled)) { 335 mangled = ''; 336 continue; 337 } 338 339 if ((profile.mRenameProperties && manager.getRootScope().constructorReservedParams.has(mangled)) || 340 ApiExtractor.mConstructorPropertySet?.has(mangled)) { 341 mangled = ''; 342 } 343 } while (mangled === ''); 344 345 return mangled; 346 } 347 348 function renameLabelsInScope(scope: Scope): void { 349 const labels: Label[] = scope.labels; 350 if (labels.length > 0) { 351 let upperMangledLabels = getUpperMangledLabelNames(labels[0]); 352 for (const label of labels) { 353 let mangledLabel = getMangledLabel(label, upperMangledLabels); 354 mangledLabelNames.set(label, mangledLabel); 355 } 356 } 357 } 358 359 function getMangledLabel(label: Label, mangledLabels: string[]): string { 360 let mangledLabel: string = ''; 361 do { 362 mangledLabel = generator.getName(); 363 if (mangledLabel === label.name) { 364 mangledLabel = ''; 365 } 366 367 if (mangledLabels.includes(mangledLabel)) { 368 mangledLabel = ''; 369 } 370 } while (mangledLabel === ''); 371 372 return mangledLabel; 373 } 374 375 function getUpperMangledLabelNames(label: Label): string[] { 376 const results: string[] = []; 377 let parent: Label = label.parent; 378 while (parent) { 379 let mangledLabelName: string = mangledLabelNames.get(parent); 380 if (mangledLabelName) { 381 results.push(mangledLabelName); 382 } 383 parent = parent.parent; 384 } 385 386 return results; 387 } 388 389 /** 390 * visit each node to change identifier name to mangled name 391 * - calculate shadow name index to find shadow node 392 * @param node 393 */ 394 function visit(node: Node): Node { 395 if (!isIdentifier(node) || !node.parent) { 396 return visitEachChild(node, visit, context); 397 } 398 399 if (isLabeledStatement(node.parent) || isBreakOrContinueStatement(node.parent)) { 400 identifierIndex += 1; 401 return updateLabelNode(node); 402 } 403 404 const shadowNode: Identifier = shadowIdentifiers[identifierIndex]; 405 identifierIndex += 1; 406 return updateNameNode(node, shadowNode); 407 } 408 409 function tryRemoveVirtualConstructor(node: Node): Node { 410 if (isStructDeclaration(node)) { 411 const shadowNode: StructDeclaration = shadowStructs[structIndex]; 412 structIndex++; 413 const sourceFile = NodeUtils.getSourceFileOfNode(shadowNode); 414 const tempStructMembers: ClassElement[] = []; 415 if (sourceFile && sourceFile.isDeclarationFile) { 416 for (let index = 0; index < node.members.length; index++) { 417 const member = node.members[index]; 418 // @ts-ignore 419 if (isConstructorDeclaration(member) && shadowNode.members[index].virtual) { 420 continue; 421 } 422 tempStructMembers.push(member); 423 } 424 const structMembersWithVirtualConstructor = factory.createNodeArray(tempStructMembers); 425 return factory.updateStructDeclaration(node, node.modifiers, node.name, node.typeParameters, node.heritageClauses, 426 structMembersWithVirtualConstructor); 427 } 428 } 429 return visitEachChild(node, tryRemoveVirtualConstructor, context); 430 } 431 432 function updateNameNode(node: Identifier, shadowNode: Identifier): Node { 433 // skip property in property access expression 434 if (NodeUtils.isPropertyAccessNode(node)) { 435 return node; 436 } 437 438 if (NodeUtils.isNewTargetNode(node)) { 439 return node; 440 } 441 442 let sym: Symbol | undefined = checker.getSymbolAtLocation(shadowNode); 443 let mangledPropertyNameOfNoSymbolImportExport = ''; 444 if ((!sym || sym.name === 'default')) { 445 if (exportObfuscation && noSymbolIdentifier.has(shadowNode.escapedText as string) && trySearchImportExportSpecifier(shadowNode)) { 446 mangledPropertyNameOfNoSymbolImportExport = mangleNoSymbolImportExportPropertyName(shadowNode.escapedText as string); 447 } else { 448 return node; 449 } 450 } 451 452 let mangledName: string = mangledSymbolNames.get(sym); 453 if (!mangledName && mangledPropertyNameOfNoSymbolImportExport !== '') { 454 mangledName = mangledPropertyNameOfNoSymbolImportExport; 455 } 456 457 if (!mangledName || mangledName === sym?.name) { 458 return node; 459 } 460 461 return factory.createIdentifier(mangledName); 462 } 463 464 function updateLabelNode(node: Identifier): Node { 465 let label: Label | undefined; 466 let labelName: string = ''; 467 468 mangledLabelNames.forEach((value, key) => { 469 if (key.refs.includes(node)) { 470 label = key; 471 labelName = value; 472 } 473 }); 474 475 return label ? factory.createIdentifier(labelName) : node; 476 } 477 478 /** 479 * import {A as B} from 'modulename'; 480 * import {C as D} from 'modulename'; 481 * export {E as F}; 482 * above A、C、F have no symbol, so deal with them specially. 483 */ 484 function mangleNoSymbolImportExportPropertyName(original: string): string { 485 const path: string = '#' + original; 486 const historyName: string = historyNameCache?.get(path); 487 let mangled = historyName ?? getPropertyMangledName(original); 488 nameCache.set(path, mangled); 489 return mangled; 490 } 491 492 function trySearchImportExportSpecifier(node: Node): boolean { 493 while (node.parent) { 494 node = node.parent; 495 if ((isImportSpecifier(node) || isExportSpecifier(node)) && node.propertyName && isIdentifier(node.propertyName)) { 496 return true; 497 } 498 } 499 return false; 500 } 501 } 502 }; 503 504 function isSkippedGlobal(enableTopLevel: boolean, scope: Scope): boolean { 505 return !enableTopLevel && isGlobalScope(scope); 506 } 507 508 export let transformerPlugin: TransformPlugin = { 509 'name': 'renameIdentifierPlugin', 510 'order': (1 << TransformerOrder.RENAME_IDENTIFIER_TRANSFORMER), 511 'createTransformerFactory': createRenameIdentifierFactory 512 }; 513 514 export let nameCache: Map<string, string> = undefined; 515 export let historyNameCache: Map<string, string> = undefined; 516 export let globalNameCache: Map<string, string> = new Map(); 517} 518 519export = secharmony;