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 fs from 'fs'; 17import path from 'path'; 18import type * as ts from 'typescript'; 19import { 20 ApiExtractor, 21 clearGlobalCaches, 22 clearNameCache, 23 deleteLineInfoForNameString, 24 endFilesEvent, 25 endSingleFileEvent, 26 EventList, 27 getRelativeSourcePath, 28 handleUniversalPathInObf, 29 mangleFilePath, 30 MemoryUtils, 31 nameCacheMap, 32 performancePrinter, 33 printTimeSumData, 34 printTimeSumInfo, 35 ProjectCollections, 36 startFilesEvent, 37 startSingleFileEvent, 38 unobfuscationNamesObj, 39 writeObfuscationNameCache, 40 writeUnobfuscationContent, 41} from 'arkguard'; 42import type { 43 ArkObfuscator, 44 HvigorErrorInfo 45} from 'arkguard'; 46 47import { GeneratedFileInHar, harFilesRecord, isPackageModulesFile, mkdirsSync, toUnixPath } from '../../../utils'; 48import { allSourceFilePaths, collectAllFiles, localPackageSet } from '../../../ets_checker'; 49import { isCurrentProjectFiles } from '../utils'; 50import { sourceFileBelongProject } from '../module/module_source_file'; 51import { 52 compileToolIsRollUp, 53 createAndStartEvent, 54 mangleDeclarationFileName, 55 ModuleInfo, 56 stopEvent, 57 writeArkguardObfuscatedSourceCode 58} from '../../../ark_utils'; 59import { OBFUSCATION_TOOL, red, yellow } from './ark_define'; 60import { logger } from '../../../compile_info'; 61import { MergedConfig } from '../common/ob_config_resolver'; 62import { ModuleSourceFile } from '../module/module_source_file'; 63import { readProjectAndLibsSource } from './process_ark_config'; 64import { CommonLogger } from '../logger'; 65import { MemoryMonitor } from '../../meomry_monitor/rollup-plugin-memory-monitor'; 66import { MemoryDefine } from '../../meomry_monitor/memory_define'; 67import { ESMODULE } from '../../../pre_define'; 68 69export { 70 collectResevedFileNameInIDEConfig, // For running unit test. 71 collectReservedNameForObf, 72 enableObfuscatedFilePathConfig, 73 enableObfuscateFileName, 74 generateConsumerObConfigFile, 75 getRelativeSourcePath, 76 handleObfuscatedFilePath, 77 handleUniversalPathInObf, 78 mangleFilePath, 79 MergedConfig, 80 nameCacheMap, 81 ObConfigResolver, 82 writeObfuscationNameCache, 83 writeUnobfuscationContent, 84} from 'arkguard'; 85 86export function resetObfuscation(): void { 87 clearGlobalCaches(); 88 sourceFileBelongProject.clear(); 89 ApiExtractor.mPropertySet?.clear(); 90 ApiExtractor.mSystemExportSet?.clear(); 91 localPackageSet?.clear(); 92} 93 94/** 95 * dependencies of sourceFiles 96 */ 97export const sourceFileDependencies: Map<string, ts.ModeAwareCache<ts.ResolvedModuleFull | undefined>> = new Map(); 98 99/** 100 * Identifier cache name 101 */ 102export const IDENTIFIER_CACHE: string = 'IdentifierCache'; 103 104/** 105 * Subsystem Number For Obfuscation 106 */ 107export const OBF_ERR_CODE = '108'; 108 109/** 110 * Logger for obfuscation 111 */ 112export let obfLogger: Object = undefined; 113 114enum LoggerLevel { 115 WARN = 'warn', 116 ERROR = 'error' 117} 118 119export function initObfLogger(share: Object): void { 120 if (share) { 121 obfLogger = share.getHvigorConsoleLogger ? share.getHvigorConsoleLogger(OBF_ERR_CODE) : share.getLogger(OBFUSCATION_TOOL); 122 return; 123 } 124 obfLogger = logger; 125} 126 127export function printObfLogger(errorInfo: string, errorCodeInfo: HvigorErrorInfo | string, level: LoggerLevel): void { 128 if (obfLogger.printError) { 129 switch (level) { 130 case LoggerLevel.WARN: 131 obfLogger.printWarn(errorCodeInfo); 132 break; 133 case LoggerLevel.ERROR: 134 obfLogger.printError(errorCodeInfo); 135 break; 136 default: 137 break; 138 } 139 return; 140 } 141 142 switch (level) { 143 case LoggerLevel.WARN: 144 obfLogger.warn(yellow, errorInfo); 145 break; 146 case LoggerLevel.ERROR: 147 obfLogger.error(red, errorInfo); 148 break; 149 default: 150 return; 151 } 152} 153 154// Collect all keep files. If the path configured by the developer is a folder, all files in the compilation will be used to match this folder. 155function collectAllKeepFiles(startPaths: string[], excludePathSet: Set<string>): Set<string> { 156 const allKeepFiles: Set<string> = new Set(); 157 const keepFolders: string[] = []; 158 startPaths.forEach(filePath => { 159 if (excludePathSet.has(filePath)) { 160 return; 161 } 162 if (fs.statSync(filePath).isDirectory()) { 163 keepFolders.push(filePath); 164 } else { 165 allKeepFiles.add(filePath); 166 } 167 }); 168 if (keepFolders.length === 0) { 169 return allKeepFiles; 170 } 171 172 allSourceFilePaths.forEach(filePath => { 173 if (keepFolders.some(folderPath => filePath.startsWith(folderPath)) && !excludePathSet.has(filePath)) { 174 allKeepFiles.add(filePath); 175 } 176 }); 177 return allKeepFiles; 178} 179 180// Collect all keep files and then collect their dependency files. 181export function handleKeepFilesAndGetDependencies(mergedObConfig: MergedConfig, arkObfuscator: ArkObfuscator, 182 projectConfig: Object): Set<string> { 183 if (mergedObConfig === undefined || mergedObConfig.keepSourceOfPaths.length === 0) { 184 sourceFileDependencies.clear(); 185 return new Set<string>(); 186 } 187 const keepPaths = mergedObConfig.keepSourceOfPaths; 188 const excludePaths = mergedObConfig.excludePathSet; 189 let allKeepFiles: Set<string> = collectAllKeepFiles(keepPaths, excludePaths); 190 arkObfuscator.setKeepSourceOfPaths(allKeepFiles); 191 const keepFilesAndDependencies: Set<string> = getFileNamesForScanningWhitelist(mergedObConfig, allKeepFiles, projectConfig); 192 sourceFileDependencies.clear(); 193 return keepFilesAndDependencies; 194} 195 196/** 197 * Use tsc's dependency collection to collect the dependency files of the keep files. 198 * Risk: The files resolved by typescript are different from the files resolved by rollup. For example, the two entry files have different priorities. 199 * Tsc looks for files in the types field in oh-packagek.json5 first, and rollup looks for files in the main field. 200 */ 201function getFileNamesForScanningWhitelist(mergedObConfig: MergedConfig, allKeepFiles: Set<string>, 202 projectConfig: Object): Set<string> { 203 const keepFilesAndDependencies: Set<string> = new Set<string>(); 204 if (!mergedObConfig.options.enableExportObfuscation) { 205 return keepFilesAndDependencies; 206 } 207 let stack: string[] = Array.from(allKeepFiles); 208 while (stack.length > 0) { 209 const filePath: string = toUnixPath(stack.pop()); 210 if (keepFilesAndDependencies.has(filePath)) { 211 continue; 212 } 213 214 keepFilesAndDependencies.add(filePath); 215 const dependentModules: ts.ModeAwareCache<ts.ResolvedModuleFull | undefined> = sourceFileDependencies.get(filePath); 216 dependentModules?.forEach(resolvedModule => { 217 if (!resolvedModule) { 218 // For `import moduleName form 'xx.so'`, when the xx.so cannot be resolved, dependentModules is [null] 219 return; 220 } 221 let curDependencyPath: string = toUnixPath(resolvedModule.resolvedFileName); 222 // resolvedModule can record system Api declaration files and ignore them 223 if (isCurrentProjectFiles(curDependencyPath, projectConfig)) { 224 stack.push(curDependencyPath); 225 } 226 }); 227 } 228 return keepFilesAndDependencies; 229} 230 231/** 232 * Get namecache by path 233 * 234 * If it is a declaration file, retrieves the corresponding source file's obfuscation results 235 * Or retrieves obfuscation results from full compilation run 236 */ 237export function getNameCacheByPath( 238 moduleInfo: ModuleInfo, 239 isDeclaration: boolean, 240 projectRootPath: string | undefined 241): Map<string, string> { 242 let historyNameCache = new Map<string, string>(); 243 let nameCachePath = moduleInfo.relativeSourceFilePath; 244 if (isDeclaration) { 245 nameCachePath = getRelativeSourcePath( 246 moduleInfo.originSourceFilePath, 247 projectRootPath, 248 sourceFileBelongProject.get(toUnixPath(moduleInfo.originSourceFilePath)) 249 ); 250 } 251 if (nameCacheMap) { 252 let identifierCache = nameCacheMap.get(nameCachePath)?.[IDENTIFIER_CACHE]; 253 deleteLineInfoForNameString(historyNameCache, identifierCache); 254 } 255 return historyNameCache; 256} 257 258/** 259 * Set newly updated namecache for project source files 260 */ 261export function setNewNameCache( 262 newNameCache: Object, 263 isDeclaration: boolean, 264 moduleInfo: ModuleInfo, 265 projectConfig: Object 266): void { 267 if (newNameCache && !isDeclaration) { 268 let obfName: string = moduleInfo.relativeSourceFilePath; 269 let isOhModule: boolean = isPackageModulesFile(moduleInfo.originSourceFilePath, projectConfig); 270 if (projectConfig.obfuscationMergedObConfig?.options.enableFileNameObfuscation && !isOhModule) { 271 obfName = mangleFilePath(moduleInfo.relativeSourceFilePath); 272 } 273 newNameCache.obfName = obfName; 274 nameCacheMap.set(moduleInfo.relativeSourceFilePath, newNameCache); 275 } 276} 277 278/** 279 * Set unobfuscation list after obfuscation 280 */ 281export function setUnobfuscationNames( 282 unobfuscationNameMap: Map<string, Set<string>> | undefined, 283 relativeSourceFilePath: string, 284 isDeclaration: boolean 285): void { 286 if (unobfuscationNameMap && !isDeclaration) { 287 let arrayObject: Record<string, string[]> = {}; 288 // The type of unobfuscationNameMap's value is Set, convert Set to Array. 289 unobfuscationNameMap.forEach((value: Set<string>, key: string) => { 290 let array: string[] = Array.from(value); 291 arrayObject[key] = array; 292 }); 293 unobfuscationNamesObj[relativeSourceFilePath] = arrayObject; 294 } 295} 296 297/** 298 * Write out obfuscated files 299 */ 300export function writeObfuscatedFile(newFilePath: string, content: string): void { 301 startSingleFileEvent(EventList.WRITE_FILE, performancePrinter.timeSumPrinter); 302 mkdirsSync(path.dirname(newFilePath)); 303 fs.writeFileSync(newFilePath, content); 304 endSingleFileEvent(EventList.WRITE_FILE, performancePrinter.timeSumPrinter, false, true); 305} 306 307// create or update incremental caches, also set the shouldReObfuscate flag if needed 308export function updateIncrementalCaches(arkObfuscator: ArkObfuscator): void { 309 if (arkObfuscator.filePathManager) { 310 const deletedFilesSet: Set<string> = arkObfuscator.filePathManager.getDeletedSourceFilePaths(); 311 ProjectCollections.projectWhiteListManager.createOrUpdateWhiteListCaches(deletedFilesSet); 312 arkObfuscator.fileContentManager.deleteFileContent(deletedFilesSet); 313 arkObfuscator.shouldReObfuscate = ProjectCollections.projectWhiteListManager.getShouldReObfuscate(); 314 } 315} 316 317// Scan all files of project and create or update cache for it 318export function readProjectCaches(allFiles: Set<string>, arkObfuscator: ArkObfuscator): void { 319 arkObfuscator.filePathManager?.createOrUpdateSourceFilePaths(allFiles); 320 321 // read fileContent caches if is increamental 322 if (arkObfuscator.isIncremental) { 323 arkObfuscator.fileContentManager.readFileNamesMap(); 324 } 325} 326 327export function getUpdatedFiles( 328 sourceProjectConfig: Object, 329 allSourceFilePaths: Set<string>, 330 moduleSourceFiles: ModuleSourceFile[] 331): Set<string> { 332 let updatedFiles: Set<string> = new Set(); 333 if (sourceProjectConfig.arkObfuscator?.isIncremental) { 334 moduleSourceFiles.forEach((sourceFile) => { 335 updatedFiles.add(toUnixPath(sourceFile.moduleId)); 336 }); 337 sourceProjectConfig.arkObfuscator?.filePathManager.addedSourceFilePaths.forEach((path: string) => { 338 updatedFiles.add(path); 339 }); 340 // Add declaration files written by users 341 allSourceFilePaths.forEach((path: string) => { 342 if (path.endsWith('.d.ts') || path.endsWith('.d.ets')) { 343 updatedFiles.add(path); 344 } 345 }); 346 } else { 347 updatedFiles = allSourceFilePaths; 348 } 349 return updatedFiles; 350} 351 352/** 353 * Preprocess of obfuscation: 354 * 1. collect whileLists for each file. 355 * 2. create or update incremental caches 356 */ 357export function obfuscationPreprocess( 358 sourceProjectConfig: Object, 359 obfuscationConfig: MergedConfig, 360 allSourceFilePaths: Set<string>, 361 keepFilesAndDependencies: Set<string>, 362 moduleSourceFiles: ModuleSourceFile[] 363): void { 364 if (sourceProjectConfig.arkObfuscator) { 365 readProjectCaches(allSourceFilePaths, sourceProjectConfig.arkObfuscator); 366 367 const updatedFiles = getUpdatedFiles(sourceProjectConfig, allSourceFilePaths, moduleSourceFiles); 368 369 readProjectAndLibsSource( 370 updatedFiles, 371 obfuscationConfig, 372 sourceProjectConfig.arkObfuscator, 373 sourceProjectConfig.compileHar, 374 keepFilesAndDependencies 375 ); 376 377 updateIncrementalCaches(sourceProjectConfig.arkObfuscator); 378 ProjectCollections.clearProjectWhiteListManager(); 379 } 380} 381 382/** 383 * Reobfuscate all files if needed. 384 */ 385export async function reObfuscate( 386 arkObfuscator: ArkObfuscator, 387 harFilesRecord: Map<string, GeneratedFileInHar>, 388 logger: Function, 389 projectConfig: Object 390): Promise<void> { 391 // name cache cannot be used since we need to reObfuscate all files 392 clearNameCache(); 393 const sortedFiles: string[] = arkObfuscator.fileContentManager.getSortedFiles(); 394 for (const originFilePath of sortedFiles) { 395 const transformedFilePath: string = arkObfuscator.fileContentManager.fileNamesMap.get(originFilePath); 396 const fileContentObj: ProjectCollections.FileContent = arkObfuscator.fileContentManager.readFileContent(transformedFilePath); 397 const originSourceFilePath: string = toUnixPath(fileContentObj.moduleInfo.originSourceFilePath); 398 const isOriginalDeclaration: boolean = (/\.d\.e?ts$/).test(originSourceFilePath); 399 if (isOriginalDeclaration) { 400 if (!harFilesRecord.has(originSourceFilePath)) { 401 const genFilesInHar: GeneratedFileInHar = { 402 sourcePath: originSourceFilePath, 403 originalDeclarationCachePath: fileContentObj.moduleInfo.buildFilePath, 404 originalDeclarationContent: fileContentObj.moduleInfo.content 405 }; 406 harFilesRecord.set(originSourceFilePath, genFilesInHar); 407 } 408 continue; 409 } 410 MemoryUtils.tryGC(); 411 await writeArkguardObfuscatedSourceCode(fileContentObj.moduleInfo, logger, projectConfig, fileContentObj.previousStageSourceMap); 412 MemoryUtils.tryGC(); 413 } 414} 415 416/** 417 * Include collect file, resolve denpendency, read source 418 */ 419export function collectSourcesWhiteList(rollupObject: Object, allSourceFilePaths: Set<string>, sourceProjectConfig: Object, 420 sourceFiles: ModuleSourceFile[] 421): void { 422 collectAllFiles(undefined, rollupObject.getModuleIds(), rollupObject); 423 startFilesEvent(EventList.SCAN_SOURCEFILES, performancePrinter.timeSumPrinter); 424 const recordInfo = MemoryMonitor.recordStage(MemoryDefine.SCAN_SOURCEFILES); 425 if (compileToolIsRollUp()) { 426 const obfuscationConfig = sourceProjectConfig.obfuscationMergedObConfig; 427 handleUniversalPathInObf(obfuscationConfig, allSourceFilePaths); 428 const keepFilesAndDependencies = handleKeepFilesAndGetDependencies( 429 obfuscationConfig, 430 sourceProjectConfig.arkObfuscator, 431 sourceProjectConfig 432 ); 433 obfuscationPreprocess( 434 sourceProjectConfig, 435 obfuscationConfig, 436 allSourceFilePaths, 437 keepFilesAndDependencies, 438 sourceFiles 439 ); 440 } 441 MemoryMonitor.stopRecordStage(recordInfo); 442 endFilesEvent(EventList.SCAN_SOURCEFILES, performancePrinter.timeSumPrinter); 443} 444 445/** 446 * Handle post obfuscation tasks: 447 * 1.ReObfuscate all files if whitelists changed in incremental build. 448 * 2.Obfuscate declaration files. 449 * 3.Write fileNamesMap. 450 */ 451export async function handlePostObfuscationTasks( 452 sourceProjectConfig: Object, 453 projectConfig: Object, 454 rollupObject: Object, 455 logger: Function 456): Promise<void> { 457 const arkObfuscator = sourceProjectConfig.arkObfuscator; 458 459 if (arkObfuscator?.shouldReObfuscate) { 460 await reObfuscate(arkObfuscator, harFilesRecord, logger, projectConfig); 461 arkObfuscator.shouldReObfuscate = false; 462 } 463 464 if (compileToolIsRollUp() && rollupObject.share.arkProjectConfig.compileMode === ESMODULE) { 465 await mangleDeclarationFileName(logger, rollupObject.share.arkProjectConfig, sourceFileBelongProject); 466 } 467 468 if (arkObfuscator?.fileContentManager) { 469 arkObfuscator.fileContentManager.writeFileNamesMap(); 470 } 471 472 printTimeSumInfo('All files obfuscation:'); 473 printTimeSumData(); 474 endFilesEvent(EventList.ALL_FILES_OBFUSCATION); 475} 476 477/** 478 * Write obfuscation caches if needed 479 */ 480export function writeObfuscationCaches(sourceProjectConfig: Object, eventOrEventFactory: Object): void { 481 const eventObfuscatedCode = createAndStartEvent(eventOrEventFactory, 'write obfuscation name cache'); 482 483 const needToWriteCache = compileToolIsRollUp() && sourceProjectConfig.arkObfuscator && sourceProjectConfig.obfuscationOptions; 484 const isWidgetCompile = sourceProjectConfig.widgetCompile; 485 486 if (needToWriteCache) { 487 writeObfuscationNameCache( 488 sourceProjectConfig, 489 sourceProjectConfig.entryPackageInfo, 490 sourceProjectConfig.obfuscationOptions.obfuscationCacheDir, 491 sourceProjectConfig.obfuscationMergedObConfig.options?.printNameCache 492 ); 493 } 494 495 if (needToWriteCache && !isWidgetCompile) { 496 writeUnobfuscationContent(sourceProjectConfig); 497 } 498 499 stopEvent(eventObfuscatedCode); 500}