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 createPrinter, 18 createTextWriter, 19 transform, 20 createObfTextSingleLineWriter, 21} from 'typescript'; 22 23import type { 24 CompilerOptions, 25 EmitTextWriter, 26 Node, 27 Printer, 28 PrinterOptions, 29 RawSourceMap, 30 SourceFile, 31 SourceMapGenerator, 32 TransformationResult, 33 TransformerFactory, 34} from 'typescript'; 35 36import path from 'path'; 37 38import { 39 AtIntentCollections, 40 AtKeepCollections, 41 BytecodeObfuscationCollections, 42 LocalVariableCollections, 43 PropCollections 44} from './utils/CommonCollections'; 45import type { IOptions } from './configs/IOptions'; 46import { FileUtils } from './utils/FileUtils'; 47import { TransformerManager } from './transformers/TransformerManager'; 48import { getSourceMapGenerator } from './utils/SourceMapUtil'; 49import { 50 decodeSourcemap, 51 ExistingDecodedSourceMap, 52 Source, 53 SourceMapLink, 54 SourceMapSegmentObj, 55 mergeSourceMap 56} from './utils/SourceMapMergingUtil'; 57import { 58 deleteLineInfoForNameString, 59 getMapFromJson, 60 IDENTIFIER_CACHE, 61 MEM_METHOD_CACHE 62} from './utils/NameCacheUtil'; 63import { ListUtil } from './utils/ListUtil'; 64import { needReadApiInfo, readProjectPropertiesByCollectedPaths } from './common/ApiReader'; 65import type { ReseverdSetForArkguard } from './common/ApiReader'; 66import { ApiExtractor } from './common/ApiExtractor'; 67import esInfo from './configs/preset/es_reserved_properties.json'; 68import optimizeEsInfo from './configs/preset/es_reserved_properties_optimized.json'; 69import { 70 TimeSumPrinter, 71 TimeTracker, 72 endFilesEvent, 73 endSingleFileEvent, 74 initPerformancePrinter, 75 startFilesEvent, 76 startSingleFileEvent, 77} from './utils/PrinterUtils'; 78import { ObConfigResolver } from './initialization/ConfigResolver'; 79import { 80 EventList, 81 TimeAndMemTimeTracker, 82 clearTimeAndMemPrinterData, 83 disablePrinterTimeAndMemConfig, 84 initPerformanceTimeAndMemPrinter, 85} from './utils/PrinterTimeAndMemUtils'; 86 87export { 88 TimeSumPrinter, 89 blockPrinter, 90 endFilesEvent, 91 endSingleFileEvent, 92 printTimeSumData, 93 printTimeSumInfo, 94 startFilesEvent, 95 startSingleFileEvent, 96} from './utils/PrinterUtils'; 97export { 98 EventList, 99 PerfMode, 100 TimeAndMemTimeTracker, 101 blockTimeAndMemPrinter, 102 configurePerformancePrinter, 103 enableTimeAndMemoryPrint, 104} from './utils/PrinterTimeAndMemUtils'; 105import { Extension, type ProjectInfo, type FilePathObj } from './common/type'; 106export { type HvigorErrorInfo } from './common/type'; 107export { FileUtils } from './utils/FileUtils'; 108export { MemoryUtils } from './utils/MemoryUtils'; 109import { TypeUtils } from './utils/TypeUtils'; 110import { handleReservedConfig } from './utils/TransformUtil'; 111import { UnobfuscationCollections } from './utils/CommonCollections'; 112import { FilePathManager, FileContentManager, initProjectWhiteListManager } from './utils/ProjectCollections'; 113import { clearHistoryUnobfuscatedMap, historyAllUnobfuscatedNamesMap } from './initialization/Initializer'; 114import { MemoryDottingDefine } from './utils/MemoryDottingDefine'; 115import { nodeSymbolMap } from './utils/ScopeAnalyzer'; 116import { clearUnobfuscationNamesObj } from './initialization/CommonObject'; 117export { UnobfuscationCollections } from './utils/CommonCollections'; 118export * as ProjectCollections from './utils/ProjectCollections'; 119export { separateUniversalReservedItem, containWildcards, wildcardTransformer } from './utils/TransformUtil'; 120export type { ReservedNameInfo } from './utils/TransformUtil'; 121export type { ReseverdSetForArkguard } from './common/ApiReader'; 122 123export { initObfuscationConfig, printerTimeAndMemDataConfig } from './initialization/Initializer'; 124export { nameCacheMap, unobfuscationNamesObj } from './initialization/CommonObject'; 125export { 126 collectResevedFileNameInIDEConfig, // For running unit test. 127 enableObfuscatedFilePathConfig, 128 enableObfuscateFileName, 129 generateConsumerObConfigFile, 130 getRelativeSourcePath, 131 handleObfuscatedFilePath, 132 handleUniversalPathInObf, 133 mangleFilePath, 134 MergedConfig, 135 ObConfigResolver, 136 readNameCache, 137 clearNameCache, 138 writeObfuscationNameCache, 139 writeUnobfuscationContent 140} from './initialization/ConfigResolver'; 141export { 142 collectReservedNameForObf 143} from './utils/NodeUtils'; 144 145export const renameIdentifierModule = require('./transformers/rename/RenameIdentifierTransformer'); 146export const renameFileNameModule = require('./transformers/rename/RenameFileNameTransformer'); 147 148export { getMapFromJson, readProjectPropertiesByCollectedPaths, deleteLineInfoForNameString, ApiExtractor, PropCollections }; 149export let orignalFilePathForSearching: string | undefined; 150export let cleanFileMangledNames: boolean = false; 151export interface PerformancePrinter { 152 filesPrinter?: TimeTracker; 153 singleFilePrinter?: TimeTracker; 154 timeSumPrinter?: TimeSumPrinter; 155} 156// TimeAndMem performance printer interface 157export interface PerformanceTimeAndMemPrinter { 158 filesPrinter?: TimeAndMemTimeTracker; 159 singleFilePrinter?: TimeAndMemTimeTracker; 160} 161export let performancePrinter: PerformancePrinter = { 162 filesPrinter: new TimeTracker(), 163 singleFilePrinter: new TimeTracker(), 164 timeSumPrinter: new TimeSumPrinter(), 165}; 166// Create instance of the TimeAndMem performance printer 167export let performanceTimeAndMemPrinter: PerformanceTimeAndMemPrinter = { 168 filesPrinter: new TimeAndMemTimeTracker(), 169 singleFilePrinter: new TimeAndMemTimeTracker(), 170}; 171 172// When the module is compiled, call this function to clear global collections. 173export function clearGlobalCaches(): void { 174 PropCollections.clearPropsCollections(); 175 UnobfuscationCollections.clear(); 176 LocalVariableCollections.clear(); 177 AtKeepCollections.clear(); 178 AtIntentCollections.clear(); 179 renameFileNameModule.clearCaches(); 180 clearUnobfuscationNamesObj(); 181 clearHistoryUnobfuscatedMap(); 182 clearTimeAndMemPrinterData(); 183 disablePrinterTimeAndMemConfig(); 184 ApiExtractor.mConstructorPropertySet.clear(); 185 ApiExtractor.mEnumMemberSet.clear(); 186 BytecodeObfuscationCollections.clear(); 187} 188 189export type ObfuscationResultType = { 190 content: string; 191 sourceMap?: RawSourceMap; 192 nameCache?: { [k: string]: string | {} }; 193 filePath?: string; 194 unobfuscationNameMap?: Map<string, Set<string>>; 195}; 196 197export interface RecordInfo { 198 recordStage: string; 199 recordIndex: number; 200}; 201 202const JSON_TEXT_INDENT_LENGTH: number = 2; 203export class ArkObfuscator { 204 // Used only for testing 205 protected mWriteOriginalFile: boolean = false; 206 207 // A text writer of Printer 208 private mTextWriter: EmitTextWriter; 209 210 // Compiler Options for typescript, use to parse ast 211 private readonly mCompilerOptions: CompilerOptions; 212 213 // User custom obfuscation profiles. 214 protected mCustomProfiles: IOptions; 215 216 private mTransformers: TransformerFactory<Node>[]; 217 218 private static memoryDottingCallback: (stage: string) => RecordInfo; 219 220 private static memoryDottingStopCallback: (recordInfo: RecordInfo) => void; 221 222 static mProjectInfo: ProjectInfo | undefined; 223 224 // If isKeptCurrentFile is true, both identifier and property obfuscation are skipped. 225 static mIsKeptCurrentFile: boolean = false; 226 227 public isIncremental: boolean = false; 228 229 public shouldReObfuscate: boolean = false; 230 231 public filePathManager: FilePathManager | undefined; 232 233 public fileContentManager: FileContentManager | undefined; 234 235 public obfConfigResolver: ObConfigResolver; 236 237 public constructor() { 238 this.mCompilerOptions = {}; 239 this.mTransformers = []; 240 } 241 242 public getWriteOriginalFileForTest(): boolean { 243 return this.mWriteOriginalFile; 244 } 245 246 public setWriteOriginalFile(flag: boolean): void { 247 this.mWriteOriginalFile = flag; 248 } 249 250 // Pass the collected whitelists related to property obfuscation to Arkguard. 251 public addReservedSetForPropertyObf(properties: ReseverdSetForArkguard): void { 252 if (properties.structPropertySet && properties.structPropertySet.size > 0) { 253 for (let reservedProperty of properties.structPropertySet) { 254 UnobfuscationCollections.reservedStruct.add(reservedProperty); 255 } 256 } 257 258 if (properties.stringPropertySet && properties.stringPropertySet.size > 0) { 259 UnobfuscationCollections.reservedStrProp = properties.stringPropertySet; 260 } 261 262 if (properties.exportNameAndPropSet && properties.exportNameAndPropSet.size > 0) { 263 UnobfuscationCollections.reservedExportNameAndProp = properties.exportNameAndPropSet; 264 } 265 266 if (properties.enumPropertySet && properties.enumPropertySet.size > 0) { 267 for (let reservedEnum of properties.enumPropertySet) { 268 UnobfuscationCollections.reservedEnum.add(reservedEnum); 269 } 270 } 271 } 272 273 public addReservedSetForDefaultObf(properties: ReseverdSetForArkguard): void { 274 if (properties.exportNameSet && properties.exportNameSet.size > 0) { 275 UnobfuscationCollections.reservedExportName = properties.exportNameSet; 276 } 277 } 278 279 public setKeepSourceOfPaths(mKeepSourceOfPaths: Set<string>): void { 280 this.mCustomProfiles.mKeepFileSourceCode.mKeepSourceOfPaths = mKeepSourceOfPaths; 281 } 282 283 public handleTsHarComments(sourceFile: SourceFile, originalPath: string | undefined): void { 284 if (ArkObfuscator.projectInfo?.useTsHar && (originalPath?.endsWith(Extension.ETS) && !originalPath?.endsWith(Extension.DETS))) { 285 // @ts-ignore 286 sourceFile.writeTsHarComments = true; 287 } 288 } 289 290 public get customProfiles(): IOptions { 291 return this.mCustomProfiles; 292 } 293 294 public static get isKeptCurrentFile(): boolean { 295 return ArkObfuscator.mIsKeptCurrentFile; 296 } 297 298 public static set isKeptCurrentFile(isKeptFile: boolean) { 299 ArkObfuscator.mIsKeptCurrentFile = isKeptFile; 300 } 301 302 public static get projectInfo(): ProjectInfo { 303 return ArkObfuscator.mProjectInfo; 304 } 305 306 public static set projectInfo(projectInfo: ProjectInfo) { 307 ArkObfuscator.mProjectInfo = projectInfo; 308 } 309 310 public static recordStage(stage: string): RecordInfo | null { 311 if (ArkObfuscator.memoryDottingCallback) { 312 return ArkObfuscator.memoryDottingCallback(stage); 313 } 314 return null; 315 } 316 317 public static stopRecordStage(recordInfo: RecordInfo | null): void { 318 if (ArkObfuscator.memoryDottingStopCallback) { 319 if (recordInfo !== null) { 320 ArkObfuscator.memoryDottingStopCallback(recordInfo); 321 } 322 } 323 } 324 325 public isCurrentFileInKeepPathsForTest(customProfiles: IOptions, originalFilePath: string): boolean { 326 return this.isCurrentFileInKeepPaths(customProfiles, originalFilePath); 327 } 328 329 private isCurrentFileInKeepPaths(customProfiles: IOptions, originalFilePath: string): boolean { 330 const keepFileSourceCode = customProfiles.mKeepFileSourceCode; 331 if (keepFileSourceCode === undefined || keepFileSourceCode.mKeepSourceOfPaths.size === 0) { 332 return false; 333 } 334 const keepPaths: Set<string> = keepFileSourceCode.mKeepSourceOfPaths; 335 const originalPath = FileUtils.toUnixPath(originalFilePath); 336 return keepPaths.has(originalPath); 337 } 338 339 /** 340 * init ArkObfuscator according to user config 341 * should be called after constructor 342 */ 343 public init(config: IOptions | undefined, cachePath?: string): boolean { 344 if (!config) { 345 console.error('obfuscation config file is not found and no given config.'); 346 return false; 347 } 348 349 handleReservedConfig(config, 'mRenameFileName', 'mReservedFileNames', 'mUniversalReservedFileNames'); 350 handleReservedConfig(config, 'mRemoveDeclarationComments', 'mReservedComments', 'mUniversalReservedComments', 'mEnable'); 351 this.mCustomProfiles = config; 352 353 if (this.mCustomProfiles.mCompact) { 354 this.mTextWriter = createObfTextSingleLineWriter(); 355 } else { 356 this.mTextWriter = createTextWriter('\n'); 357 } 358 359 if (this.mCustomProfiles.mEnableSourceMap) { 360 this.mCompilerOptions.sourceMap = true; 361 } 362 363 if (this.mCustomProfiles.mAllowEtsAnnotations) { 364 this.mCompilerOptions.etsAnnotationsEnable = true; 365 } 366 367 const enableTopLevel: boolean = this.mCustomProfiles.mNameObfuscation?.mTopLevel; 368 const exportObfuscation: boolean = this.mCustomProfiles.mExportObfuscation; 369 const propertyObfuscation: boolean = this.mCustomProfiles.mNameObfuscation?.mRenameProperties; 370 /** 371 * clean mangledNames in case skip name check when generating names 372 */ 373 cleanFileMangledNames = enableTopLevel && !exportObfuscation && !propertyObfuscation; 374 375 // load transformers 376 this.mTransformers = new TransformerManager(this.mCustomProfiles).getTransformers(); 377 378 initPerformancePrinter(this.mCustomProfiles); 379 380 initPerformanceTimeAndMemPrinter(); 381 if (needReadApiInfo(this.mCustomProfiles)) { 382 // if -enable-property-obfuscation or -enable-export-obfuscation, collect language reserved keywords. 383 let languageSet: Set<string> = new Set(); 384 let presetLanguageWhitelist = this.mCustomProfiles.mStripLanguageDefaultWhitelist ? optimizeEsInfo : esInfo; 385 for (const key of Object.keys(presetLanguageWhitelist)) { 386 const valueArray = presetLanguageWhitelist[key]; 387 valueArray.forEach((element: string) => { 388 languageSet.add(element); 389 }); 390 } 391 UnobfuscationCollections.reservedLangForProperty = languageSet; 392 } 393 394 if (cachePath) { 395 this.initIncrementalCache(cachePath, !!this.mCustomProfiles.mNameObfuscation.mEnableAtKeep); 396 } 397 398 return true; 399 } 400 401 public static setMemoryDottingCallBack(memoryDottingCallback: (stage: string) => RecordInfo, 402 memoryDottingStopCallback: (recordInfo: RecordInfo) => void): void { 403 if (memoryDottingCallback) { 404 ArkObfuscator.memoryDottingCallback = memoryDottingCallback; 405 } 406 if (memoryDottingStopCallback) { 407 ArkObfuscator.memoryDottingStopCallback = memoryDottingStopCallback; 408 } 409 } 410 411 public static clearMemoryDottingCallBack(): void { 412 ArkObfuscator.memoryDottingCallback = undefined; 413 ArkObfuscator.memoryDottingStopCallback = undefined; 414 } 415 416 /** 417 * Init incremental cache according to cachePath 418 */ 419 private initIncrementalCache(cachePath: string, enableAtKeep: boolean): void { 420 this.filePathManager = new FilePathManager(cachePath); 421 422 this.isIncremental = this.filePathManager.isIncremental(); 423 424 this.fileContentManager = new FileContentManager(cachePath, this.isIncremental); 425 426 initProjectWhiteListManager(cachePath, this.isIncremental, enableAtKeep); 427 } 428 429 /** 430 * A Printer to output obfuscated codes. 431 */ 432 public createObfsPrinter(isDeclarationFile: boolean): Printer { 433 // set print options 434 let printerOptions: PrinterOptions = {}; 435 let removeOption = this.mCustomProfiles.mRemoveDeclarationComments; 436 let hasReservedList = removeOption?.mReservedComments?.length || removeOption?.mUniversalReservedComments?.length; 437 let keepDeclarationComments = hasReservedList || !removeOption?.mEnable; 438 439 if (isDeclarationFile && keepDeclarationComments) { 440 printerOptions.removeComments = false; 441 } 442 if ((!isDeclarationFile && this.mCustomProfiles.mRemoveComments) || (isDeclarationFile && !keepDeclarationComments)) { 443 printerOptions.removeComments = true; 444 } 445 446 return createPrinter(printerOptions); 447 } 448 449 protected isObfsIgnoreFile(fileName: string): boolean { 450 let suffix: string = FileUtils.getFileExtension(fileName); 451 452 return suffix !== 'js' && suffix !== 'ts' && suffix !== 'ets'; 453 } 454 455 private convertLineBasedOnSourceMap(targetCache: string, sourceMapLink?: SourceMapLink): Map<string, string> { 456 let originalCache: Map<string, string> = renameIdentifierModule.nameCache.get(targetCache); 457 let updatedCache: Map<string, string> = new Map<string, string>(); 458 for (const [key, value] of originalCache) { 459 if (!key.includes(':')) { 460 // No need to save line info for identifier which is not function-like, i.e. key without ':' here. 461 updatedCache[key] = value; 462 continue; 463 } 464 const [scopeName, oldStartLine, oldStartColumn, oldEndLine, oldEndColumn] = key.split(':'); 465 let newKey: string = key; 466 if (!sourceMapLink) { 467 // In Arkguard, we save line info of source code, so do not need to use sourcemap mapping. 468 newKey = `${scopeName}:${oldStartLine}:${oldEndLine}`; 469 updatedCache[newKey] = value; 470 continue; 471 } 472 473 const parts = scopeName.split('#'); 474 // 1: Get the last word 'zz' in '#xx#yy#zz'. 475 const lastScopeName: string = parts[parts.length - 1]; 476 477 const startPosition: SourceMapSegmentObj | null = sourceMapLink.traceSegment( 478 // 1: The line number in originalCache starts from 1 while in source map starts from 0. 479 Number(oldStartLine) - 1, Number(oldStartColumn) - 1, ''); // Minus 1 to get the correct original position. 480 if (!startPosition && lastScopeName === value) { 481 // Do not save methods that do not exist in the source code and not be obfuscated, e.g. 'build' in ArkUI. 482 continue; 483 } 484 const endPosition: SourceMapSegmentObj | null = sourceMapLink.traceSegment( 485 Number(oldEndLine) - 1, Number(oldEndColumn) - 1, ''); // 1: Same as above. 486 if (!endPosition && lastScopeName === value) { 487 // Do not save methods that do not exist in the source code and not be obfuscated, e.g. 'build' in ArkUI. 488 continue; 489 } 490 491 if (!startPosition || !endPosition) { 492 updatedCache[scopeName] = value; 493 } else { 494 const startLine = startPosition.line + 1; // 1: The final line number in updatedCache should starts from 1. 495 const endLine = endPosition.line + 1; // 1: Same as above. 496 newKey = `${scopeName}:${startLine}:${endLine}`; 497 updatedCache[newKey] = value; 498 } 499 } 500 return updatedCache; 501 } 502 503 public convertLineBasedOnSourceMapForTest( 504 targetCache: string, 505 sourceMapLink?: SourceMapLink 506 ): Map<string, string> { 507 return this.convertLineBasedOnSourceMap(targetCache, sourceMapLink); 508 } 509 510 /** 511 * Obfuscate ast of a file. 512 * @param content ast or source code of a source file 513 * @param sourceFilePathObj 514 * @param previousStageSourceMap 515 * @param historyNameCache 516 * @param originalFilePath When filename obfuscation is enabled, it is used as the source code path. 517 */ 518 public async obfuscate( 519 content: SourceFile | string, 520 sourceFilePathObj: FilePathObj, 521 previousStageSourceMap?: RawSourceMap, 522 historyNameCache?: Map<string, string>, 523 originalFilePath?: string, 524 projectInfo?: ProjectInfo, 525 ): Promise<ObfuscationResultType> { 526 startFilesEvent(EventList.CONFIG_INITIALIZATION); 527 ArkObfuscator.projectInfo = projectInfo; 528 let result: ObfuscationResultType = { content: undefined }; 529 if (this.isObfsIgnoreFile(sourceFilePathObj.buildFilePath)) { 530 // need add return value 531 endFilesEvent(EventList.CONFIG_INITIALIZATION); 532 return result; 533 } 534 535 let ast: SourceFile = this.createAst(content, sourceFilePathObj.buildFilePath); 536 if (ast.statements.length === 0) { 537 endFilesEvent(EventList.CONFIG_INITIALIZATION); 538 return result; 539 } 540 541 if (historyNameCache && historyNameCache.size > 0 && this.mCustomProfiles.mNameObfuscation) { 542 renameIdentifierModule.historyNameCache = historyNameCache; 543 } 544 545 if (this.mCustomProfiles.mUnobfuscationOption?.mPrintKeptNames) { 546 let historyUnobfuscatedNames = historyAllUnobfuscatedNamesMap.get(sourceFilePathObj.relativeFilePath); 547 if (historyUnobfuscatedNames) { 548 renameIdentifierModule.historyUnobfuscatedNamesMap = new Map(Object.entries(historyUnobfuscatedNames)); 549 } 550 } 551 552 originalFilePath = originalFilePath ?? ast.fileName; 553 if (this.mCustomProfiles.mRenameFileName?.mEnable) { 554 orignalFilePathForSearching = originalFilePath; 555 } 556 ArkObfuscator.isKeptCurrentFile = this.isCurrentFileInKeepPaths(this.mCustomProfiles, originalFilePath); 557 endFilesEvent(EventList.CONFIG_INITIALIZATION); 558 559 this.handleDeclarationFile(ast); 560 561 ast = this.obfuscateAst(ast); 562 563 startSingleFileEvent(EventList.WRITE_OBFUSCATION_RESULT); 564 this.writeObfuscationResult(ast, sourceFilePathObj.buildFilePath, result, previousStageSourceMap, originalFilePath); 565 endSingleFileEvent(EventList.WRITE_OBFUSCATION_RESULT); 566 567 this.clearCaches(); 568 return result; 569 } 570 571 private createAst(content: SourceFile | string, sourceFilePath: string): SourceFile { 572 const recordInfo = ArkObfuscator.recordStage(MemoryDottingDefine.CREATE_AST); 573 startSingleFileEvent(EventList.CREATE_AST, performancePrinter.timeSumPrinter); 574 let ast: SourceFile; 575 if (typeof content === 'string') { 576 ast = TypeUtils.createObfSourceFile(sourceFilePath, content, this.mCompilerOptions); 577 } else { 578 ast = content; 579 } 580 endSingleFileEvent(EventList.CREATE_AST, performancePrinter.timeSumPrinter); 581 ArkObfuscator.stopRecordStage(recordInfo); 582 583 return ast; 584 } 585 586 private obfuscateAst(ast: SourceFile): SourceFile { 587 const recordInfo = ArkObfuscator.recordStage(MemoryDottingDefine.OBFUSCATE_AST); 588 startSingleFileEvent(EventList.OBFUSCATE_AST, performancePrinter.timeSumPrinter); 589 let transformedResult: TransformationResult<Node> = transform(ast, this.mTransformers, this.mCompilerOptions); 590 endSingleFileEvent(EventList.OBFUSCATE_AST, performancePrinter.timeSumPrinter); 591 ArkObfuscator.stopRecordStage(recordInfo); 592 ast = transformedResult.transformed[0] as SourceFile; 593 return ast; 594 } 595 596 private handleDeclarationFile(ast: SourceFile): void { 597 if (ast.isDeclarationFile) { 598 if (!this.mCustomProfiles.mRemoveDeclarationComments || !this.mCustomProfiles.mRemoveDeclarationComments.mEnable) { 599 //@ts-ignore 600 ast.reservedComments = undefined; 601 //@ts-ignore 602 ast.universalReservedComments = undefined; 603 } else { 604 //@ts-ignore 605 ast.reservedComments ??= this.mCustomProfiles.mRemoveDeclarationComments.mReservedComments ? 606 this.mCustomProfiles.mRemoveDeclarationComments.mReservedComments : []; 607 //@ts-ignore 608 ast.universalReservedComments = this.mCustomProfiles.mRemoveDeclarationComments.mUniversalReservedComments ?? []; 609 } 610 } else { 611 //@ts-ignore 612 ast.reservedComments = this.mCustomProfiles.mRemoveComments ? [] : undefined; 613 //@ts-ignore 614 ast.universalReservedComments = this.mCustomProfiles.mRemoveComments ? [] : undefined; 615 } 616 } 617 618 /** 619 * write obfuscated code, sourcemap and namecache 620 */ 621 private writeObfuscationResult(ast: SourceFile, sourceFilePath: string, result: ObfuscationResultType, 622 previousStageSourceMap?: RawSourceMap, originalFilePath?: string): void { 623 // convert ast to output source file and generate sourcemap if needed. 624 let sourceMapGenerator: SourceMapGenerator = undefined; 625 if (this.mCustomProfiles.mEnableSourceMap) { 626 startSingleFileEvent(EventList.GET_SOURCEMAP_GENERATOR, performancePrinter.timeSumPrinter); 627 sourceMapGenerator = getSourceMapGenerator(sourceFilePath); 628 endSingleFileEvent(EventList.GET_SOURCEMAP_GENERATOR, performancePrinter.timeSumPrinter); 629 } 630 631 if (sourceFilePath.endsWith('.js')) { 632 TypeUtils.tsToJs(ast); 633 } 634 this.handleTsHarComments(ast, originalFilePath); 635 const recordInfo = ArkObfuscator.recordStage(MemoryDottingDefine.CREATE_PRINTER); 636 startSingleFileEvent(EventList.CREATE_PRINTER, performancePrinter.timeSumPrinter); 637 this.createObfsPrinter(ast.isDeclarationFile).writeFile(ast, this.mTextWriter, sourceMapGenerator); 638 endSingleFileEvent(EventList.CREATE_PRINTER, performancePrinter.timeSumPrinter); 639 ArkObfuscator.stopRecordStage(recordInfo); 640 641 startSingleFileEvent(EventList.GET_OBFUSCATED_CODE); 642 result.filePath = ast.fileName; 643 result.content = this.mTextWriter.getText(); 644 endSingleFileEvent(EventList.GET_OBFUSCATED_CODE); 645 646 if (this.mCustomProfiles.mUnobfuscationOption?.mPrintKeptNames) { 647 this.handleUnobfuscationNames(result); 648 } 649 650 startSingleFileEvent(EventList.PROCESS_SOURCEMAP); 651 if (this.mCustomProfiles.mEnableSourceMap && sourceMapGenerator) { 652 this.handleSourceMapAndNameCache(sourceMapGenerator, sourceFilePath, result, previousStageSourceMap); 653 } 654 endSingleFileEvent(EventList.PROCESS_SOURCEMAP); 655 } 656 657 private handleUnobfuscationNames(result: ObfuscationResultType): void { 658 result.unobfuscationNameMap = new Map(UnobfuscationCollections.unobfuscatedNamesMap); 659 } 660 661 private handleSourceMapAndNameCache(sourceMapGenerator: SourceMapGenerator, sourceFilePath: string, 662 result: ObfuscationResultType, previousStageSourceMap?: RawSourceMap): void { 663 startSingleFileEvent(EventList.SOURCEMAP_MERGE, performancePrinter.timeSumPrinter); 664 let sourceMapJson: RawSourceMap = sourceMapGenerator.toJSON(); 665 sourceMapJson.sourceRoot = ''; 666 sourceMapJson.file = path.basename(sourceFilePath); 667 if (previousStageSourceMap) { 668 sourceMapJson = mergeSourceMap(previousStageSourceMap as RawSourceMap, sourceMapJson); 669 } 670 result.sourceMap = sourceMapJson; 671 endSingleFileEvent(EventList.SOURCEMAP_MERGE, performancePrinter.timeSumPrinter); 672 startSingleFileEvent(EventList.CREATE_NAMECACHE, performancePrinter.timeSumPrinter); 673 let nameCache = renameIdentifierModule.nameCache; 674 if (this.mCustomProfiles.mEnableNameCache) { 675 let newIdentifierCache!: Object; 676 let newMemberMethodCache!: Object; 677 if (previousStageSourceMap) { 678 // The process in sdk, need to use sourcemap mapping. 679 // 1: Only one file in the source map; 0: The first and the only one. 680 const sourceFileName = previousStageSourceMap.sources?.length === 1 ? previousStageSourceMap.sources[0] : ''; 681 const source: Source = new Source(sourceFileName, null); 682 const decodedSourceMap: ExistingDecodedSourceMap = decodeSourcemap(previousStageSourceMap); 683 let sourceMapLink: SourceMapLink = new SourceMapLink(decodedSourceMap, [source]); 684 newIdentifierCache = this.convertLineBasedOnSourceMap(IDENTIFIER_CACHE, sourceMapLink); 685 newMemberMethodCache = this.convertLineBasedOnSourceMap(MEM_METHOD_CACHE, sourceMapLink); 686 } else { 687 // The process in Arkguard. 688 newIdentifierCache = this.convertLineBasedOnSourceMap(IDENTIFIER_CACHE); 689 newMemberMethodCache = this.convertLineBasedOnSourceMap(MEM_METHOD_CACHE); 690 } 691 nameCache.set(IDENTIFIER_CACHE, newIdentifierCache); 692 nameCache.set(MEM_METHOD_CACHE, newMemberMethodCache); 693 result.nameCache = { [IDENTIFIER_CACHE]: newIdentifierCache, [MEM_METHOD_CACHE]: newMemberMethodCache }; 694 } 695 endSingleFileEvent(EventList.CREATE_NAMECACHE, performancePrinter.timeSumPrinter); 696 } 697 698 private clearCaches(): void { 699 // clear cache of text writer 700 this.mTextWriter.clear(); 701 renameIdentifierModule.clearCaches(); 702 if (cleanFileMangledNames) { 703 PropCollections.globalMangledTable.clear(); 704 } 705 UnobfuscationCollections.unobfuscatedNamesMap.clear(); 706 nodeSymbolMap.clear(); 707 } 708} 709