• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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}