• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (c) 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 fs from 'fs';
17import path from 'path';
18import JSON5 from 'json5';
19
20import type * as ts from 'typescript';
21
22import { FileUtils } from '../utils/FileUtils';
23import {
24  type ReservedNameInfo,
25  ApiExtractor,
26  containWildcards,
27  EventList,
28  getMapFromJson,
29  performancePrinter,
30  PropCollections,
31  renameFileNameModule,
32  separateUniversalReservedItem,
33  wildcardTransformer,
34  ArkObfuscator,
35} from '../ArkObfuscator';
36
37import { isDebug, isFileExist, sortAndDeduplicateStringArr, mergeSet, convertSetToArray } from './utils';
38import { nameCacheMap, yellow, unobfuscationNamesObj } from './CommonObject';
39import { clearHistoryUnobfuscatedMap, historyAllUnobfuscatedNamesMap, historyUnobfuscatedPropMap } from './Initializer';
40import { AtKeepCollections, LocalVariableCollections, UnobfuscationCollections } from '../utils/CommonCollections';
41import { INameObfuscationOption } from '../configs/INameObfuscationOption';
42import { WhitelistType } from '../utils/TransformUtil';
43import { endFilesEvent, startFilesEvent } from '../utils/PrinterUtils';
44import { initScanProjectConfigByMergeConfig, scanProjectConfig, resetScanProjectConfig } from '../common/ApiReader';
45import { MemoryDottingDefine } from '../utils/MemoryDottingDefine';
46import type { HvigorErrorInfo } from '../common/type';
47import { addToSet, KeepInfo } from '../utils/ProjectCollections';
48
49enum OptionType {
50  NONE,
51  KEEP,
52  KEEP_DTS,
53  KEEP_GLOBAL_NAME,
54  KEEP_PROPERTY_NAME,
55  KEEP_FILE_NAME,
56  KEEP_COMMENTS,
57  DISABLE_OBFUSCATION,
58  ENABLE_PROPERTY_OBFUSCATION,
59  ENABLE_STRING_PROPERTY_OBFUSCATION,
60  ENABLE_TOPLEVEL_OBFUSCATION,
61  ENABLE_FILENAME_OBFUSCATION,
62  ENABLE_EXPORT_OBFUSCATION,
63  ENABLE_LIB_OBFUSCATION_OPTIONS,
64  ENABLE_ATKEEP,
65  COMPACT,
66  REMOVE_LOG,
67  REMOVE_COMMENTS,
68  PRINT_NAMECACHE,
69  PRINT_KEPT_NAMES,
70  APPLY_NAMECACHE,
71  EXTRA_OPTIONS,
72  STRIP_LANGUAGE_DEFAULT,
73  STRIP_SYSTEM_API_ARGS,
74  KEEP_PARAMETER_NAMES,
75}
76export { OptionType as OptionTypeForTest };
77
78type SystemApiContent = {
79  ReservedPropertyNames?: string[];
80  ReservedGlobalNames?: string[];
81  ReservedLocalNames?: string[];
82};
83
84class ObOptions {
85  disableObfuscation: boolean = false;
86  enablePropertyObfuscation: boolean = false;
87  enableStringPropertyObfuscation: boolean = false;
88  enableToplevelObfuscation: boolean = false;
89  enableFileNameObfuscation: boolean = false;
90  enableExportObfuscation: boolean = false;
91  enableLibObfuscationOptions: boolean = false;
92  enableAtKeep: boolean = false;
93  printKeptNames: boolean = false;
94  removeComments: boolean = false;
95  compact: boolean = false;
96  removeLog: boolean = false;
97  printNameCache: string = '';
98  printKeptNamesPath: string = '';
99  applyNameCache: string = '';
100  stripLanguageDefault: boolean = false;
101  stripSystemApiArgs: boolean = false;
102  keepParameterNames: boolean = false;
103
104  mergeObOptions(other: ObOptions): void {
105    this.disableObfuscation = this.disableObfuscation || other.disableObfuscation;
106    this.enablePropertyObfuscation = this.enablePropertyObfuscation || other.enablePropertyObfuscation;
107    this.enableToplevelObfuscation = this.enableToplevelObfuscation || other.enableToplevelObfuscation;
108    this.enableStringPropertyObfuscation =
109      this.enableStringPropertyObfuscation || other.enableStringPropertyObfuscation;
110    this.removeComments = this.removeComments || other.removeComments;
111    this.compact = this.compact || other.compact;
112    this.removeLog = this.removeLog || other.removeLog;
113    this.enableFileNameObfuscation = this.enableFileNameObfuscation || other.enableFileNameObfuscation;
114    this.enableExportObfuscation = this.enableExportObfuscation || other.enableExportObfuscation;
115    this.stripLanguageDefault = this.stripLanguageDefault || other.stripLanguageDefault;
116    this.stripSystemApiArgs = this.stripSystemApiArgs || other.stripSystemApiArgs;
117    this.keepParameterNames = this.keepParameterNames || other.keepParameterNames;
118    this.enableAtKeep = this.enableAtKeep || other.enableAtKeep;
119
120    if (other.printNameCache.length > 0) {
121      this.printNameCache = other.printNameCache;
122    }
123    if (other.printKeptNamesPath.length > 0) {
124      this.printKeptNamesPath = other.printKeptNamesPath;
125    }
126    if (other.applyNameCache.length > 0) {
127      this.applyNameCache = other.applyNameCache;
128    }
129  }
130}
131export const ObOptionsForTest = ObOptions;
132
133export class MergedConfig {
134  options: ObOptions = new ObOptions();
135  reservedPropertyNames: string[] = [];
136  reservedGlobalNames: string[] = [];
137  reservedNames: string[] = [];
138  reservedFileNames: string[] = [];
139  keepComments: string[] = [];
140  keepSourceOfPaths: string[] = []; // The file path or folder path configured by the developer.
141  universalReservedPropertyNames: RegExp[] = []; // Support reserved property names contain wildcards.
142  universalReservedGlobalNames: RegExp[] = []; // Support reserved global names contain wildcards.
143  keepUniversalPaths: RegExp[] = []; // Support reserved paths contain wildcards.
144  excludeUniversalPaths: RegExp[] = []; // Support excluded paths contain wildcards.
145  excludePathSet: Set<string> = new Set();
146
147  mergeKeepOptions(other: MergedConfig): void {
148    this.reservedPropertyNames.push(...other.reservedPropertyNames);
149    this.reservedGlobalNames.push(...other.reservedGlobalNames);
150    this.reservedFileNames.push(...other.reservedFileNames);
151    this.keepComments.push(...other.keepComments);
152    this.keepSourceOfPaths.push(...other.keepSourceOfPaths);
153    this.keepUniversalPaths.push(...other.keepUniversalPaths);
154    this.excludeUniversalPaths.push(...other.excludeUniversalPaths);
155    other.excludePathSet.forEach((excludePath) => {
156      this.excludePathSet.add(excludePath);
157    });
158  }
159
160  mergeAllRules(other: MergedConfig): void {
161    this.options.mergeObOptions(other.options);
162    this.mergeKeepOptions(other);
163  }
164
165  sortAndDeduplicate(): void {
166    this.reservedPropertyNames = sortAndDeduplicateStringArr(this.reservedPropertyNames);
167    this.reservedGlobalNames = sortAndDeduplicateStringArr(this.reservedGlobalNames);
168    this.reservedFileNames = sortAndDeduplicateStringArr(this.reservedFileNames);
169    this.keepComments = sortAndDeduplicateStringArr(this.keepComments);
170    this.keepSourceOfPaths = sortAndDeduplicateStringArr(this.keepSourceOfPaths);
171  }
172
173  serializeMergedConfig(): string {
174    let resultStr: string = '';
175    const keys = Object.keys(this.options);
176    for (const key of keys) {
177      // skip the export of some switches.
178      if (this.options[key] === true && ObConfigResolver.exportedSwitchMap.has(String(key))) {
179        resultStr += ObConfigResolver.exportedSwitchMap.get(String(key)) + '\n';
180      }
181    }
182
183    if (this.reservedGlobalNames.length > 0) {
184      resultStr += ObConfigResolver.KEEP_GLOBAL_NAME + '\n';
185      this.reservedGlobalNames.forEach((item) => {
186        resultStr += item + '\n';
187      });
188    }
189    if (this.reservedPropertyNames.length > 0) {
190      resultStr += ObConfigResolver.KEEP_PROPERTY_NAME + '\n';
191      this.reservedPropertyNames.forEach((item) => {
192        resultStr += item + '\n';
193      });
194    }
195    return resultStr;
196  }
197}
198
199export class ObConfigResolver {
200  sourceObConfig: any;
201  printObfLogger: Function;
202  isHarCompiled: boolean | undefined;
203  isHspCompiled: boolean | undefined;
204  isTerser: boolean;
205  needConsumerConfigs: boolean = false;
206  dependencyConfigs: MergedConfig;
207
208  constructor(projectConfig: any, printObfLogger: Function, isTerser?: boolean) {
209    this.sourceObConfig = projectConfig.obfuscationOptions;
210    this.printObfLogger = printObfLogger;
211    this.isHarCompiled = projectConfig.compileHar;
212    this.isHspCompiled = projectConfig.compileShared;
213    this.isTerser = isTerser;
214  }
215
216  public resolveObfuscationConfigs(): MergedConfig {
217    let sourceObConfig = this.sourceObConfig;
218    if (!sourceObConfig) {
219      return new MergedConfig();
220    }
221    let enableObfuscation: boolean = sourceObConfig.selfConfig.ruleOptions.enable;
222
223    let selfConfig: MergedConfig = new MergedConfig();
224    if (enableObfuscation) {
225      this.getSelfConfigs(selfConfig);
226      enableObfuscation = !selfConfig.options.disableObfuscation;
227    } else {
228      selfConfig.options.disableObfuscation = true;
229    }
230
231    this.needConsumerConfigs = (this.isHarCompiled || this.isHspCompiled) &&
232      sourceObConfig.selfConfig.consumerRules &&
233      sourceObConfig.selfConfig.consumerRules.length > 0;
234    let needDependencyConfigs: boolean = enableObfuscation || this.needConsumerConfigs;
235
236    this.dependencyConfigs = new MergedConfig();
237    const dependencyMaxLength: number = Math.max(
238      sourceObConfig.dependencies.libraries.length,
239      sourceObConfig.dependencies.hars.length,
240      sourceObConfig.dependencies.hsps?.length ?? 0,
241      sourceObConfig.dependencies.hspLibraries?.length ?? 0
242    );
243    if (needDependencyConfigs && dependencyMaxLength > 0) {
244      this.dependencyConfigs = new MergedConfig();
245      this.getDependencyConfigs(sourceObConfig, this.dependencyConfigs);
246      enableObfuscation = enableObfuscation && !this.dependencyConfigs.options.disableObfuscation;
247    }
248    const mergedConfigs: MergedConfig = this.getMergedConfigs(selfConfig, this.dependencyConfigs);
249    this.handleReservedArray(mergedConfigs);
250
251    let needKeepSystemApi =
252      enableObfuscation &&
253      (mergedConfigs.options.enablePropertyObfuscation ||
254        (mergedConfigs.options.enableExportObfuscation && mergedConfigs.options.enableToplevelObfuscation));
255
256    if (needKeepSystemApi && sourceObConfig.obfuscationCacheDir) {
257      const systemApiCachePath: string = path.join(sourceObConfig.obfuscationCacheDir, 'systemApiCache.json');
258      if (isFileExist(systemApiCachePath)) {
259        this.getSystemApiConfigsByCache(systemApiCachePath);
260      } else {
261        const recordInfo = ArkObfuscator.recordStage(MemoryDottingDefine.SCAN_SYS_API);
262        startFilesEvent(EventList.SCAN_SYSTEMAPI, performancePrinter.timeSumPrinter);
263        this.collectSystemApiWhitelist(mergedConfigs, systemApiCachePath);
264        endFilesEvent(EventList.SCAN_SYSTEMAPI, performancePrinter.timeSumPrinter);
265        ArkObfuscator.stopRecordStage(recordInfo);
266      }
267    }
268
269    // when atKeep is enabled, we can not emit here since we need to collect names marked with atKeep
270    if (!mergedConfigs.options.enableAtKeep) {
271      this.emitConsumerConfigFiles();
272    }
273    return mergedConfigs;
274  }
275
276  public emitConsumerConfigFiles(): void {
277    if (this.needConsumerConfigs) {
278      let selfConsumerConfig = new MergedConfig();
279      this.getSelfConsumerConfig(selfConsumerConfig);
280      this.genConsumerConfigFiles(this.sourceObConfig, selfConsumerConfig, this.dependencyConfigs);
281    }
282  }
283
284  private getSelfConfigs(selfConfigs: MergedConfig): void {
285    if (this.sourceObConfig.selfConfig.ruleOptions.rules) {
286      const configPaths: string[] = this.sourceObConfig.selfConfig.ruleOptions.rules;
287      for (const path of configPaths) {
288        this.getConfigByPath(path, selfConfigs);
289      }
290    }
291  }
292
293  public getSelfConfigsForTest(selfConfigs: MergedConfig): void {
294    return this.getSelfConfigs(selfConfigs);
295  }
296
297  private getConfigByPath(path: string, configs: MergedConfig): void {
298    let fileContent = undefined;
299    try {
300      fileContent = fs.readFileSync(path, 'utf-8');
301    } catch (err) {
302      const errorInfo = `Failed to open ${path}. Error message: ${err}`;
303      const errorCodeInfo: HvigorErrorInfo = {
304        code: '10804001',
305        description: 'ArkTS compiler Error',
306        cause: `Failed to open obfuscation config file from ${path}. Error message: ${err}`,
307        position: path,
308        solutions: [`Please check whether ${path} exists.`],
309      };
310      this.printObfLogger(errorInfo, errorCodeInfo, 'error');
311    }
312    this.handleConfigContent(fileContent, configs, path);
313  }
314
315  public getConfigByPathForTest(path: string, configs: MergedConfig): void {
316    return this.getConfigByPath(path, configs);
317  }
318
319  private handleReservedArray(mergedConfigs: MergedConfig): void {
320    const shouldPrintKeptNames = mergedConfigs.options.printKeptNames;
321    if (mergedConfigs.options.enablePropertyObfuscation && mergedConfigs.reservedPropertyNames) {
322      const propertyReservedInfo: ReservedNameInfo =
323        separateUniversalReservedItem(mergedConfigs.reservedPropertyNames, shouldPrintKeptNames);
324      mergedConfigs.universalReservedPropertyNames = propertyReservedInfo.universalReservedArray;
325      mergedConfigs.reservedPropertyNames = propertyReservedInfo.specificReservedArray;
326    }
327
328    if (mergedConfigs.options.enableToplevelObfuscation && mergedConfigs.reservedGlobalNames) {
329      const globalReservedInfo: ReservedNameInfo =
330        separateUniversalReservedItem(mergedConfigs.reservedGlobalNames, shouldPrintKeptNames);
331      mergedConfigs.universalReservedGlobalNames = globalReservedInfo.universalReservedArray;
332      mergedConfigs.reservedGlobalNames = globalReservedInfo.specificReservedArray;
333    }
334  }
335
336  public handleReservedArrayForTest(mergedConfigs: MergedConfig): void {
337    return this.handleReservedArray(mergedConfigs);
338  }
339
340  // obfuscation options
341  static readonly KEEP = '-keep';
342  static readonly KEEP_DTS = '-keep-dts';
343  static readonly KEEP_GLOBAL_NAME = '-keep-global-name';
344  static readonly KEEP_PROPERTY_NAME = '-keep-property-name';
345  static readonly KEEP_FILE_NAME = '-keep-file-name';
346  static readonly KEEP_COMMENTS = '-keep-comments';
347  static readonly DISABLE_OBFUSCATION = '-disable-obfuscation';
348  static readonly ENABLE_PROPERTY_OBFUSCATION = '-enable-property-obfuscation';
349  static readonly ENABLE_STRING_PROPERTY_OBFUSCATION = '-enable-string-property-obfuscation';
350  static readonly ENABLE_TOPLEVEL_OBFUSCATION = '-enable-toplevel-obfuscation';
351  static readonly ENABLE_FILENAME_OBFUSCATION = '-enable-filename-obfuscation';
352  static readonly ENABLE_EXPORT_OBFUSCATION = '-enable-export-obfuscation';
353  static readonly ENABLE_LIB_OBFUSCATION_OPTIONS = '-enable-lib-obfuscation-options';
354  static readonly ENABLE_ATKEEP = '-use-keep-in-source';
355  static readonly REMOVE_COMMENTS = '-remove-comments';
356  static readonly COMPACT = '-compact';
357  static readonly REMOVE_LOG = '-remove-log';
358  static readonly PRINT_NAMECACHE = '-print-namecache';
359  static readonly PRINT_KEPT_NAMES = '-print-kept-names';
360  static readonly APPLY_NAMECACHE = '-apply-namecache';
361  static readonly EXTRA_OPTIONS = '-extra-options';
362  static readonly STRIP_LANGUAGE_DEFAULT = 'strip-language-default';
363  static readonly STRIP_SYSTEM_API_ARGS = 'strip-system-api-args';
364  static readonly KEEP_PARAMETER_NAMES = '-keep-parameter-names';
365
366  // renameFileName, printNameCache, applyNameCache, removeComments and keepComments won't be reserved in obfuscation.txt file.
367  static exportedSwitchMap: Map<string, string> = new Map([
368    ['enablePropertyObfuscation', ObConfigResolver.ENABLE_PROPERTY_OBFUSCATION],
369    ['enableStringPropertyObfuscation', ObConfigResolver.ENABLE_STRING_PROPERTY_OBFUSCATION],
370    ['enableToplevelObfuscation', ObConfigResolver.ENABLE_TOPLEVEL_OBFUSCATION],
371    ['compact', ObConfigResolver.COMPACT],
372    ['removeLog', ObConfigResolver.REMOVE_LOG],
373  ]);
374
375  private getTokenType(token: string): OptionType {
376    switch (token) {
377      case ObConfigResolver.KEEP_DTS:
378        return OptionType.KEEP_DTS;
379      case ObConfigResolver.KEEP_GLOBAL_NAME:
380        return OptionType.KEEP_GLOBAL_NAME;
381      case ObConfigResolver.KEEP_PROPERTY_NAME:
382        return OptionType.KEEP_PROPERTY_NAME;
383      case ObConfigResolver.KEEP_FILE_NAME:
384        return OptionType.KEEP_FILE_NAME;
385      case ObConfigResolver.KEEP_COMMENTS:
386        return OptionType.KEEP_COMMENTS;
387      case ObConfigResolver.DISABLE_OBFUSCATION:
388        return OptionType.DISABLE_OBFUSCATION;
389      case ObConfigResolver.ENABLE_PROPERTY_OBFUSCATION:
390        return OptionType.ENABLE_PROPERTY_OBFUSCATION;
391      case ObConfigResolver.ENABLE_STRING_PROPERTY_OBFUSCATION:
392        return OptionType.ENABLE_STRING_PROPERTY_OBFUSCATION;
393      case ObConfigResolver.ENABLE_TOPLEVEL_OBFUSCATION:
394        return OptionType.ENABLE_TOPLEVEL_OBFUSCATION;
395      case ObConfigResolver.ENABLE_FILENAME_OBFUSCATION:
396        return OptionType.ENABLE_FILENAME_OBFUSCATION;
397      case ObConfigResolver.ENABLE_EXPORT_OBFUSCATION:
398        return OptionType.ENABLE_EXPORT_OBFUSCATION;
399      case ObConfigResolver.ENABLE_LIB_OBFUSCATION_OPTIONS:
400        return OptionType.ENABLE_LIB_OBFUSCATION_OPTIONS;
401      case ObConfigResolver.ENABLE_ATKEEP:
402        return OptionType.ENABLE_ATKEEP;
403      case ObConfigResolver.REMOVE_COMMENTS:
404        return OptionType.REMOVE_COMMENTS;
405      case ObConfigResolver.COMPACT:
406        return OptionType.COMPACT;
407      case ObConfigResolver.REMOVE_LOG:
408        return OptionType.REMOVE_LOG;
409      case ObConfigResolver.PRINT_NAMECACHE:
410        return OptionType.PRINT_NAMECACHE;
411      case ObConfigResolver.PRINT_KEPT_NAMES:
412        return OptionType.PRINT_KEPT_NAMES;
413      case ObConfigResolver.APPLY_NAMECACHE:
414        return OptionType.APPLY_NAMECACHE;
415      case ObConfigResolver.KEEP:
416        return OptionType.KEEP;
417      case ObConfigResolver.EXTRA_OPTIONS:
418        return OptionType.EXTRA_OPTIONS;
419      case ObConfigResolver.STRIP_LANGUAGE_DEFAULT:
420        return OptionType.STRIP_LANGUAGE_DEFAULT;
421      case ObConfigResolver.STRIP_SYSTEM_API_ARGS:
422        return OptionType.STRIP_SYSTEM_API_ARGS;
423      case ObConfigResolver.KEEP_PARAMETER_NAMES:
424        return OptionType.KEEP_PARAMETER_NAMES;
425      default:
426        return OptionType.NONE;
427    }
428  }
429
430  public getTokenTypeForTest(token: string): OptionType {
431    return this.getTokenType(token);
432  }
433
434  private handleConfigContent(data: string, configs: MergedConfig, configPath: string): void {
435    data = this.removeComments(data);
436    const tokens = data.split(/[',', '\t', ' ', '\n', '\r\n']/).filter((item) => item !== '');
437    let type: OptionType = OptionType.NONE;
438    let extraOptionType: OptionType = OptionType.NONE;
439    let tokenType: OptionType;
440    let dtsFilePaths: string[] = [];
441    let keepConfigs: string[] = [];
442    for (let i = 0; i < tokens.length; i++) {
443      const token = tokens[i];
444      tokenType = this.getTokenType(token);
445      // handle switches cases
446      switch (tokenType) {
447        case OptionType.DISABLE_OBFUSCATION: {
448          configs.options.disableObfuscation = true;
449          extraOptionType = OptionType.NONE;
450          continue;
451        }
452        case OptionType.ENABLE_PROPERTY_OBFUSCATION: {
453          configs.options.enablePropertyObfuscation = true;
454          extraOptionType = OptionType.NONE;
455          continue;
456        }
457        case OptionType.ENABLE_STRING_PROPERTY_OBFUSCATION: {
458          configs.options.enableStringPropertyObfuscation = true;
459          extraOptionType = OptionType.NONE;
460          continue;
461        }
462        case OptionType.ENABLE_TOPLEVEL_OBFUSCATION: {
463          configs.options.enableToplevelObfuscation = true;
464          extraOptionType = OptionType.NONE;
465          continue;
466        }
467        case OptionType.REMOVE_COMMENTS: {
468          configs.options.removeComments = true;
469          extraOptionType = OptionType.NONE;
470          continue;
471        }
472        case OptionType.ENABLE_FILENAME_OBFUSCATION: {
473          configs.options.enableFileNameObfuscation = true;
474          extraOptionType = OptionType.NONE;
475          continue;
476        }
477        case OptionType.ENABLE_EXPORT_OBFUSCATION: {
478          configs.options.enableExportObfuscation = true;
479          extraOptionType = OptionType.NONE;
480          continue;
481        }
482        case OptionType.ENABLE_LIB_OBFUSCATION_OPTIONS: {
483          configs.options.enableLibObfuscationOptions = true;
484          extraOptionType = OptionType.NONE;
485          continue;
486        }
487        case OptionType.ENABLE_ATKEEP: {
488          configs.options.enableAtKeep = true;
489          extraOptionType = OptionType.NONE;
490          continue;
491        }
492        case OptionType.COMPACT: {
493          configs.options.compact = true;
494          extraOptionType = OptionType.NONE;
495          continue;
496        }
497        case OptionType.REMOVE_LOG: {
498          configs.options.removeLog = true;
499          extraOptionType = OptionType.NONE;
500          continue;
501        }
502        case OptionType.EXTRA_OPTIONS: {
503          extraOptionType = tokenType;
504          continue;
505        }
506        case OptionType.PRINT_KEPT_NAMES: {
507          configs.options.printKeptNames = true;
508          type = tokenType;
509          extraOptionType = OptionType.NONE;
510          continue;
511        }
512        case OptionType.KEEP_PARAMETER_NAMES: {
513          configs.options.keepParameterNames = true;
514          extraOptionType = OptionType.NONE;
515          continue;
516        }
517        case OptionType.KEEP:
518        case OptionType.KEEP_DTS:
519        case OptionType.KEEP_GLOBAL_NAME:
520        case OptionType.KEEP_PROPERTY_NAME:
521        case OptionType.KEEP_FILE_NAME:
522        case OptionType.KEEP_COMMENTS:
523        case OptionType.PRINT_NAMECACHE:
524        case OptionType.APPLY_NAMECACHE:
525          type = tokenType;
526          extraOptionType = OptionType.NONE;
527          continue;
528        case OptionType.NONE:
529          extraOptionType = OptionType.NONE;
530        default: {
531          // fall-through
532        }
533      }
534      const matchedExtraOptions: boolean = this.isMatchedExtraOptions(extraOptionType, tokenType, configs);
535      if (matchedExtraOptions) {
536        continue;
537      }
538      // handle 'keep' options and 'namecache' options
539      switch (type) {
540        case OptionType.KEEP: {
541          keepConfigs.push(token);
542          continue;
543        }
544        case OptionType.KEEP_DTS: {
545          dtsFilePaths.push(token);
546          continue;
547        }
548        case OptionType.KEEP_GLOBAL_NAME: {
549          configs.reservedGlobalNames.push(token);
550          continue;
551        }
552        case OptionType.KEEP_PROPERTY_NAME: {
553          configs.reservedPropertyNames.push(token);
554          continue;
555        }
556        case OptionType.KEEP_FILE_NAME: {
557          configs.reservedFileNames.push(token);
558          continue;
559        }
560        case OptionType.KEEP_COMMENTS: {
561          configs.keepComments.push(token);
562          continue;
563        }
564        case OptionType.PRINT_NAMECACHE: {
565          configs.options.printNameCache = this.resolvePath(configPath, token);
566          type = OptionType.NONE;
567          continue;
568        }
569        case OptionType.PRINT_KEPT_NAMES: {
570          configs.options.printKeptNamesPath = this.resolvePath(configPath, token);
571          type = OptionType.NONE;
572          continue;
573        }
574        case OptionType.APPLY_NAMECACHE: {
575          const absNameCachePath: string = this.resolvePath(configPath, token);
576          this.determineNameCachePath(absNameCachePath, configPath);
577          configs.options.applyNameCache = absNameCachePath;
578          type = OptionType.NONE;
579          continue;
580        }
581        default:
582          continue;
583      }
584    }
585
586    this.resolveDts(dtsFilePaths, configs);
587    this.resolveKeepConfig(keepConfigs, configs, configPath);
588  }
589
590  public isMatchedExtraOptions(extraOptionType: OptionType, tokenType: OptionType, configs: MergedConfig): boolean {
591      if (extraOptionType !== OptionType.EXTRA_OPTIONS) {
592          return false;
593      }
594      switch (tokenType) {
595        case OptionType.STRIP_LANGUAGE_DEFAULT: {
596          configs.options.stripLanguageDefault = true;
597          return true;
598        }
599        case OptionType.STRIP_SYSTEM_API_ARGS: {
600          configs.options.stripSystemApiArgs = true;
601          return true;
602        }
603      }
604      extraOptionType = OptionType.NONE;
605      return false;
606  }
607
608  public handleConfigContentForTest(data: string, configs: MergedConfig, configPath: string): void {
609    return this.handleConfigContent(data, configs, configPath);
610  }
611  // get absolute path
612  private resolvePath(configPath: string, token: string): string {
613    if (path.isAbsolute(token)) {
614      return token;
615    }
616    const configDirectory = path.dirname(configPath);
617    return path.resolve(configDirectory, token);
618  }
619
620  public resolvePathForTest(configPath: string, token: string): string {
621    return this.resolvePath(configPath, token);
622  }
623
624  // get names in .d.ts files and add them into reserved list
625  private resolveDts(dtsFilePaths: string[], configs: MergedConfig): void {
626    ApiExtractor.mPropertySet.clear();
627    dtsFilePaths.forEach((token) => {
628      ApiExtractor.traverseApiFiles(token, ApiExtractor.ApiType.KEEP_DTS);
629    });
630    configs.reservedNames = configs.reservedNames.concat([...ApiExtractor.mPropertySet]);
631    configs.reservedPropertyNames = configs.reservedPropertyNames.concat([...ApiExtractor.mPropertySet]);
632    configs.reservedGlobalNames = configs.reservedGlobalNames.concat([...ApiExtractor.mPropertySet]);
633    ApiExtractor.mPropertySet.clear();
634  }
635
636  public resolveKeepConfig(keepConfigs: string[], configs: MergedConfig, configPath: string): void {
637    for (let keepPath of keepConfigs) {
638      let tempAbsPath: string;
639      const isExclude: boolean = keepPath.startsWith('!');
640      // 1: remove '!'
641      tempAbsPath = FileUtils.getAbsPathBaseConfigPath(configPath, isExclude ? keepPath.substring(1) : keepPath);
642
643      // contains '*', '?'
644      if (containWildcards(tempAbsPath)) {
645        const regexPattern = wildcardTransformer(tempAbsPath, true);
646        const regexOperator = new RegExp(`^${regexPattern}$`);
647        if (isExclude) {
648          // start with '!'
649          configs.excludeUniversalPaths.push(regexOperator);
650        } else {
651          configs.keepUniversalPaths.push(regexOperator);
652        }
653        continue;
654      }
655
656      if (isExclude) {
657        // exclude specific path
658        configs.excludePathSet.add(tempAbsPath);
659        continue;
660      }
661
662      if (!fs.existsSync(tempAbsPath)) {
663        const warnInfo: string = `ArkTS: The path of obfuscation \'-keep\' configuration does not exist: ${keepPath}`;
664        this.printObfLogger(warnInfo, warnInfo, 'warn');
665        continue;
666      }
667      tempAbsPath = fs.realpathSync(tempAbsPath);
668      configs.keepSourceOfPaths.push(FileUtils.toUnixPath(tempAbsPath));
669    }
670  }
671
672  // the content from '#' to '\n' are comments
673  private removeComments(data: string): string {
674    const commentStart = '#';
675    const commentEnd = '\n';
676    let tmpStr = '';
677    let isInComments = false;
678    for (let i = 0; i < data.length; i++) {
679      if (isInComments) {
680        isInComments = data[i] !== commentEnd;
681      } else if (data[i] !== commentStart) {
682        tmpStr += data[i];
683      } else {
684        isInComments = true;
685      }
686    }
687    return tmpStr;
688  }
689
690  /**
691   * arkguardConfigs includes the API directorys.
692   * component directory and pre_define.js file path needs to be concatenated
693   * @param config
694   */
695  private collectSystemApiWhitelist(config: MergedConfig, systemApiCachePath: string): void {
696    ApiExtractor.mPropertySet.clear();
697    ApiExtractor.mSystemExportSet.clear();
698    initScanProjectConfigByMergeConfig(config);
699    const sdkApis: string[] = sortAndDeduplicateStringArr(this.sourceObConfig.sdkApis);
700    let existPreDefineFilePath: string = '';
701    let existArkUIWhitelistPath: string = '';
702    const scannedRootFolder: Set<string> = new Set();
703    for (let apiPath of sdkApis) {
704      this.collectSdkApiWhitelist(apiPath, scannedRootFolder);
705      const preDefineFilePath: string = path.join(apiPath, '../build-tools/ets-loader/lib/pre_define.js');
706      if (fs.existsSync(preDefineFilePath)) {
707        existPreDefineFilePath = preDefineFilePath;
708      }
709      const arkUIWhitelistPath: string = path.join(apiPath, '../build-tools/ets-loader/obfuscateWhiteList.json5');
710      if (fs.existsSync(arkUIWhitelistPath)) {
711        existArkUIWhitelistPath = arkUIWhitelistPath;
712      }
713    }
714    const arkUIReservedPropertyNames: string[] = this.collectUIApiWhitelist(existPreDefineFilePath, existArkUIWhitelistPath, config);
715    let systemApiContent: SystemApiContent = {};
716    if (config.options.enablePropertyObfuscation) {
717      if (!config.options.stripSystemApiArgs) {
718        UnobfuscationCollections.reservedSdkApiForLocal = new Set(ApiExtractor.mPropertySet);
719        systemApiContent.ReservedLocalNames = Array.from(ApiExtractor.mPropertySet);
720      }
721      const savedNameAndPropertySet = new Set([...ApiExtractor.mPropertySet, ...arkUIReservedPropertyNames]);
722      UnobfuscationCollections.reservedSdkApiForProp = savedNameAndPropertySet;
723      systemApiContent.ReservedPropertyNames = Array.from(savedNameAndPropertySet);
724    }
725    if (config.options.enableToplevelObfuscation && config.options.enableExportObfuscation) {
726      const savedExportNamesSet = new Set(ApiExtractor.mSystemExportSet);
727      UnobfuscationCollections.reservedSdkApiForGlobal = savedExportNamesSet;
728      systemApiContent.ReservedGlobalNames = Array.from(savedExportNamesSet);
729    }
730
731    if (!fs.existsSync(path.dirname(systemApiCachePath))) {
732      fs.mkdirSync(path.dirname(systemApiCachePath), { recursive: true });
733    }
734    fs.writeFileSync(systemApiCachePath, JSON.stringify(systemApiContent, null, 2));
735    ApiExtractor.mPropertySet.clear();
736    ApiExtractor.mSystemExportSet.clear();
737    resetScanProjectConfig();
738  }
739
740  public collectSystemApiWhitelistForTest(config: MergedConfig, systemApiCachePath: string): void {
741    return this.collectSystemApiWhitelist(config, systemApiCachePath);
742  }
743
744  private collectSdkApiWhitelist(sdkApiPath: string, scannedRootFolder: Set<string>): void {
745    ApiExtractor.traverseApiFiles(sdkApiPath, ApiExtractor.ApiType.API);
746    const componentPath: string = path.join(sdkApiPath, '../component');
747    if (!scannedRootFolder.has(componentPath) && fs.existsSync(componentPath)) {
748      scannedRootFolder.add(componentPath);
749      ApiExtractor.traverseApiFiles(componentPath, ApiExtractor.ApiType.COMPONENT);
750    }
751  }
752
753  private collectPreDefineFile(uiApiPath: string): void {
754    ApiExtractor.extractStringsFromFile(uiApiPath);
755  }
756
757  private collectUIApiWhitelist(preDefineFilePath: string, arkUIWhitelistPath: string, config: MergedConfig): string[] {
758    interface ArkUIWhitelist {
759      ReservedPropertyNames: string[],
760      OptimizedReservedPropertyNames: string[]
761    }
762    // OptimizedReservedPropertyNames saves accurate list of UI API that need to be kept
763    let arkUIWhitelist: ArkUIWhitelist = { ReservedPropertyNames: [], OptimizedReservedPropertyNames: [] };
764    if (fs.existsSync(arkUIWhitelistPath)) {
765      arkUIWhitelist = JSON5.parse(fs.readFileSync(arkUIWhitelistPath, 'utf-8'));
766    }
767    // if enable -extra-options strip-system-api-args, use OptimizedReservedPropertyNames in arkUIWhitelist, and not scan pre_define.js
768    let arkUIReservedPropertyNames: string[] = [...arkUIWhitelist.ReservedPropertyNames, ...arkUIWhitelist.OptimizedReservedPropertyNames];
769    if (!config.options.stripSystemApiArgs) {
770      if (fs.existsSync(preDefineFilePath)) {
771        this.collectPreDefineFile(preDefineFilePath);
772      }
773      arkUIReservedPropertyNames = [...arkUIWhitelist.ReservedPropertyNames];
774    }
775    return arkUIReservedPropertyNames;
776  }
777
778  private getDependencyConfigs(sourceObConfig: SourceObConfig, dependencyConfigs: MergedConfig): void {
779    for (const lib of sourceObConfig.dependencies.libraries || []) {
780      if (lib.consumerRules && lib.consumerRules.length > 0) {
781        this.mergeDependencyConfigsByPath(lib.consumerRules, dependencyConfigs);
782      }
783    }
784
785    for (const lib of sourceObConfig.dependencies.hspLibraries || []) {
786      if (lib.consumerRules && lib.consumerRules.length > 0) {
787        this.mergeDependencyConfigsByPath(lib.consumerRules, dependencyConfigs);
788      }
789    }
790
791    if (
792      sourceObConfig.dependencies &&
793      sourceObConfig.dependencies.hars &&
794      sourceObConfig.dependencies.hars.length > 0
795    ) {
796      this.mergeDependencyConfigsByPath(sourceObConfig.dependencies.hars, dependencyConfigs);
797    }
798
799    if (
800      sourceObConfig.dependencies &&
801      sourceObConfig.dependencies.hsps &&
802      sourceObConfig.dependencies.hsps.length > 0
803    ) {
804      this.mergeDependencyConfigsByPath(sourceObConfig.dependencies.hsps, dependencyConfigs);
805    }
806  }
807
808  private mergeDependencyConfigsByPath(paths: string[], dependencyConfigs: MergedConfig): void {
809    for (const path of paths) {
810      const thisConfig = new MergedConfig();
811      this.getConfigByPath(path, thisConfig);
812      dependencyConfigs.mergeAllRules(thisConfig);
813    }
814  }
815
816  public getDependencyConfigsForTest(sourceObConfig: SourceObConfig, dependencyConfigs: MergedConfig): void {
817    return this.getDependencyConfigs(sourceObConfig, dependencyConfigs);
818  }
819
820  private getSystemApiConfigsByCache(systemApiCachePath: string): void {
821    let systemApiContent: {
822      ReservedPropertyNames?: string[];
823      ReservedGlobalNames?: string[];
824      ReservedLocalNames?: string[];
825    } = JSON.parse(fs.readFileSync(systemApiCachePath, 'utf-8'));
826    if (systemApiContent.ReservedPropertyNames) {
827      UnobfuscationCollections.reservedSdkApiForProp = new Set(systemApiContent.ReservedPropertyNames);
828    }
829    if (systemApiContent.ReservedGlobalNames) {
830      UnobfuscationCollections.reservedSdkApiForGlobal = new Set(systemApiContent.ReservedGlobalNames);
831    }
832    if (systemApiContent.ReservedLocalNames) {
833      UnobfuscationCollections.reservedSdkApiForLocal = new Set(systemApiContent.ReservedLocalNames);
834    }
835  }
836
837  public getSystemApiConfigsByCacheForTest(systemApiCachePath: string): void {
838    return this.getSystemApiConfigsByCache(systemApiCachePath);
839  }
840
841  private getSelfConsumerConfig(selfConsumerConfig: MergedConfig): void {
842    for (const path of this.sourceObConfig.selfConfig.consumerRules) {
843      this.getConfigByPath(path, selfConsumerConfig);
844    }
845  }
846
847  public getSelfConsumerConfigForTest(selfConsumerConfig: MergedConfig): void {
848    return this.getSelfConsumerConfig(selfConsumerConfig);
849  }
850
851  private getMergedConfigs(selfConfigs: MergedConfig, dependencyConfigs: MergedConfig): MergedConfig {
852    if (dependencyConfigs) {
853      if (selfConfigs.options.enableLibObfuscationOptions) {
854        selfConfigs.mergeAllRules(dependencyConfigs);
855      } else {
856        selfConfigs.mergeKeepOptions(dependencyConfigs);
857      }
858    }
859    selfConfigs.sortAndDeduplicate();
860    return selfConfigs;
861  }
862
863  public getMergedConfigsForTest(selfConfigs: MergedConfig, dependencyConfigs: MergedConfig): MergedConfig {
864    return this.getMergedConfigs(selfConfigs, dependencyConfigs);
865  }
866
867  private genConsumerConfigFiles(
868    sourceObConfig: SourceObConfig,
869    selfConsumerConfig: MergedConfig,
870    dependencyConfigs: MergedConfig,
871  ): void {
872    if (this.isHarCompiled) {
873      selfConsumerConfig.mergeAllRules(dependencyConfigs);
874    }
875    this.addKeepConsumer(selfConsumerConfig, AtKeepCollections.keepAsConsumer);
876    selfConsumerConfig.sortAndDeduplicate();
877    this.writeConsumerConfigFile(selfConsumerConfig, sourceObConfig.exportRulePath);
878  }
879
880  private addKeepConsumer(selfConsumerConfig: MergedConfig, keepAsConsumer: KeepInfo): void {
881    keepAsConsumer.propertyNames.forEach((propertyName) => {
882      selfConsumerConfig.reservedPropertyNames.push(propertyName);
883    });
884    keepAsConsumer.globalNames.forEach((globalName) =>{
885      selfConsumerConfig.reservedGlobalNames.push(globalName);
886    });
887  }
888
889  public genConsumerConfigFilesForTest(
890    sourceObConfig: SourceObConfig,
891    selfConsumerConfig: MergedConfig,
892    dependencyConfigs: MergedConfig,
893  ): void {
894    return this.genConsumerConfigFiles(sourceObConfig, selfConsumerConfig, dependencyConfigs);
895  }
896
897  public writeConsumerConfigFile(selfConsumerConfig: MergedConfig, outpath: string): void {
898    const configContent: string = selfConsumerConfig.serializeMergedConfig();
899    fs.writeFileSync(outpath, configContent);
900  }
901
902  private determineNameCachePath(nameCachePath: string, configPath: string): void {
903    if (!fs.existsSync(nameCachePath)) {
904      const errorInfo: string = `The applied namecache file '${nameCachePath}' configured by '${configPath}' does not exist.`;
905      const errorCodeInfo: HvigorErrorInfo = {
906        code: '10804004',
907        description: 'ArkTS compiler Error',
908        cause: `The applied namecache file '${nameCachePath}' configured by '${configPath}' does not exist.`,
909        position: configPath,
910        solutions: [`Please check ${configPath} and make sure the file configured by -apply-namecache exists`],
911      };
912      this.printObfLogger(errorInfo, errorCodeInfo, 'error');
913    }
914  }
915}
916
917/**
918 * Collect reserved file name configured in oh-package.json5 and module.json5.
919 * @param ohPackagePath The 'main' and 'types' fileds in oh-package.json5 need to be reserved.
920 * @param projectConfig Several paths or file contents in projectconfig need to be reserved.
921 *   1: module.json's 'srcEntry' field
922 *   2: projectPath: /library/src/main/ets
923 *   3: cachePath: /library/build/default/cache/default/default@HarCompileArkTs/esmodules/release
924 *      target reserved path: /library/build/default/cache/default/default@HarCompileArkTs/esmodules/release/src/main/ets
925 *   4: aceModuleBuild/etsFortgz directory: /library/build/default/intermediates/loader_out/etsFortgz
926 *      If compile the hsp module, the declaration file will be written to the 'aceModuleBuild/etsFortgz' directory.
927 * @param modulePathMap packageName of local har package should be reserved as it is a fixed part of ohmUrl.
928 *   example: modulePathMap: { packageName: path }
929 * @returns reservedFileNames
930 */
931export function collectResevedFileNameInIDEConfig(
932  ohPackagePath: string,
933  projectConfig: any,
934  modulePathMap: Object | undefined,
935  entryArray: Array<string> | undefined,
936): string[] {
937  const reservedFileNames: string[] = [];
938  const moduleJsonPath: string = projectConfig.aceModuleJsonPath;
939  const projectPath: string = projectConfig.projectPath;
940  const cachePath: string = projectConfig.cachePath;
941
942  if (entryArray) {
943    entryArray.forEach((element) => {
944      FileUtils.collectPathReservedString(element, reservedFileNames);
945    });
946  }
947
948  if (modulePathMap) {
949    const modulePaths = Object.values(modulePathMap);
950    const moduleNames = Object.keys(modulePathMap);
951    modulePaths.forEach((val) => {
952      FileUtils.collectPathReservedString(val, reservedFileNames);
953    });
954    moduleNames.forEach((val) => {
955      FileUtils.collectPathReservedString(val, reservedFileNames);
956    });
957  }
958  if (fs.existsSync(ohPackagePath)) {
959    const ohPackageContent = JSON5.parse(fs.readFileSync(ohPackagePath, 'utf-8'));
960    ohPackageContent.main && FileUtils.collectPathReservedString(ohPackageContent.main, reservedFileNames);
961    ohPackageContent.types && FileUtils.collectPathReservedString(ohPackageContent.types, reservedFileNames);
962  }
963
964  if (fs.existsSync(moduleJsonPath)) {
965    const moduleJsonContent = JSON5.parse(fs.readFileSync(moduleJsonPath, 'utf-8'));
966    moduleJsonContent.module?.srcEntry &&
967      FileUtils.collectPathReservedString(moduleJsonContent.module?.srcEntry, reservedFileNames);
968  }
969
970  if (projectConfig.compileShared || projectConfig.byteCodeHar) {
971    FileUtils.collectPathReservedString(projectConfig.aceModuleBuild, reservedFileNames);
972    reservedFileNames.push('etsFortgz');
973  }
974
975  FileUtils.collectPathReservedString(projectPath, reservedFileNames);
976  FileUtils.collectPathReservedString(cachePath, reservedFileNames);
977  return reservedFileNames;
978}
979
980export function readNameCache(nameCachePath: string, printObfLogger: Function): void {
981  try {
982    const fileContent = fs.readFileSync(nameCachePath, 'utf-8');
983    const nameCache: {
984      compileSdkVersion?: string;
985      [key: string]: Object;
986      PropertyCache?: Object;
987      FileNameCache?: Object;
988    } = JSON.parse(fileContent);
989    if (nameCache.PropertyCache) {
990      PropCollections.historyMangledTable = getMapFromJson(nameCache.PropertyCache);
991    }
992    if (nameCache.FileNameCache) {
993      renameFileNameModule.historyFileNameMangledTable = getMapFromJson(nameCache.FileNameCache);
994    }
995
996    const { compileSdkVersion, PropertyCache, FileNameCache, ...rest } = nameCache;
997    Object.keys(rest).forEach((key) => {
998      nameCacheMap.set(key, rest[key]);
999    });
1000  } catch (err) {
1001    const errorInfo: string = `Failed to open ${nameCachePath}. Error message: ${err}`;
1002    const errorCodeInfo: HvigorErrorInfo = {
1003      code: '10804002',
1004      description: 'ArkTS compiler Error',
1005      cause: `Failed to open namecache file from ${nameCachePath}, Error message: ${err}`,
1006      position: nameCachePath,
1007      solutions: [`Please check ${nameCachePath} as error message suggested.`],
1008    };
1009    printObfLogger(errorInfo, errorCodeInfo, 'error');
1010  }
1011}
1012
1013// Clear name caches, used when we need to reobfuscate all files
1014export function clearNameCache(): void {
1015  PropCollections.historyMangledTable?.clear();
1016  nameCacheMap?.clear();
1017  clearHistoryUnobfuscatedMap();
1018}
1019
1020/**
1021 * collect the reserved or excluded paths containing wildcards
1022 */
1023export function handleUniversalPathInObf(mergedObConfig: MergedConfig, allSourceFilePaths: Set<string>): void {
1024  if (
1025    !mergedObConfig ||
1026    (mergedObConfig.keepUniversalPaths.length === 0 && mergedObConfig.excludeUniversalPaths.length === 0)
1027  ) {
1028    return;
1029  }
1030  for (const realFilePath of allSourceFilePaths) {
1031    let isReserved = false;
1032    for (const universalPath of mergedObConfig.keepUniversalPaths) {
1033      if (universalPath.test(realFilePath)) {
1034        isReserved = true;
1035        break;
1036      }
1037    }
1038    for (const excludePath of mergedObConfig.excludeUniversalPaths) {
1039      if (excludePath.test(realFilePath)) {
1040        isReserved = false;
1041        mergedObConfig.excludePathSet.add(realFilePath);
1042        break;
1043      }
1044    }
1045    if (isReserved) {
1046      mergedObConfig.keepSourceOfPaths.push(realFilePath);
1047    }
1048  }
1049}
1050
1051export function getArkguardNameCache(
1052  enablePropertyObfuscation: boolean,
1053  enableFileNameObfuscation: boolean,
1054  enableExportObfuscation: boolean,
1055  sdkVersion: string,
1056  entryPackageInfo: string,
1057): string {
1058  let writeContent: string = '';
1059  let nameCacheCollection: {
1060    compileSdkVersion?: string;
1061    PropertyCache?: Object;
1062    FileNameCache?: Object;
1063    entryPackageInfo?: string;
1064  } = Object.fromEntries(nameCacheMap.entries());
1065  nameCacheCollection.compileSdkVersion = sdkVersion;
1066  nameCacheCollection.entryPackageInfo = entryPackageInfo;
1067
1068  if (enablePropertyObfuscation || enableExportObfuscation) {
1069    const mergedPropertyNameCache: Map<string, string> = new Map();
1070    fillNameCache(PropCollections.historyMangledTable, mergedPropertyNameCache);
1071    fillNameCache(PropCollections.globalMangledTable, mergedPropertyNameCache);
1072    nameCacheCollection.PropertyCache = Object.fromEntries(mergedPropertyNameCache);
1073  }
1074
1075  if (enableFileNameObfuscation) {
1076    const mergedFileNameCache: Map<string, string> = new Map();
1077    fillNameCache(renameFileNameModule.historyFileNameMangledTable, mergedFileNameCache);
1078    fillNameCache(renameFileNameModule.globalFileNameMangledTable, mergedFileNameCache);
1079    nameCacheCollection.FileNameCache = Object.fromEntries(mergedFileNameCache);
1080  }
1081
1082  writeContent += JSON.stringify(nameCacheCollection, null, 2);
1083  return writeContent;
1084}
1085
1086// export fillNameCache function
1087export function fillNameCache(table: Map<string, string>, nameCache: Map<string, string>): void {
1088  if (table) {
1089    for (const [key, value] of table.entries()) {
1090      nameCache.set(key, value);
1091    }
1092  }
1093  return;
1094}
1095
1096export function writeObfuscationNameCache(
1097  projectConfig: any,
1098  entryPackageInfo: string,
1099  obfuscationCacheDir?: string,
1100  printNameCache?: string,
1101): void {
1102  if (!projectConfig.arkObfuscator) {
1103    return;
1104  }
1105  let options = projectConfig.obfuscationMergedObConfig.options;
1106  let writeContent = getArkguardNameCache(
1107    options.enablePropertyObfuscation,
1108    options.enableFileNameObfuscation,
1109    options.enableExportObfuscation,
1110    projectConfig.etsLoaderVersion,
1111    entryPackageInfo,
1112  );
1113  if (obfuscationCacheDir && obfuscationCacheDir.length > 0) {
1114    const defaultNameCachePath: string = path.join(obfuscationCacheDir, 'nameCache.json');
1115    if (!fs.existsSync(path.dirname(defaultNameCachePath))) {
1116      fs.mkdirSync(path.dirname(defaultNameCachePath), { recursive: true });
1117    }
1118    fs.writeFileSync(defaultNameCachePath, writeContent);
1119  }
1120  if (printNameCache && printNameCache.length > 0) {
1121    fs.writeFileSync(printNameCache, writeContent);
1122  }
1123}
1124
1125// Print unobfuscation names, reasons and whitelist, if -print-kept-names is enabled.
1126export function writeUnobfuscationContent(projectConfig: any): void {
1127  let obfuscationOptions = projectConfig.obfuscationMergedObConfig.options;
1128  let unobfuscationOptions = projectConfig.arkObfuscator.mCustomProfiles.mUnobfuscationOption;
1129  let nameOptions = projectConfig.arkObfuscator.mCustomProfiles.mNameObfuscation;
1130  if (!unobfuscationOptions.mPrintKeptNames) {
1131    return;
1132  }
1133
1134  let configPath = unobfuscationOptions.mPrintPath;
1135  let printDir = projectConfig.obfuscationOptions.obfuscationCacheDir;
1136  let printUnobfPath = path.join(printDir, 'keptNames.json');
1137  printUnobfuscationReasons(configPath, printUnobfPath);
1138  let printWhitelistPath = path.join(printDir, 'whitelist.json');
1139  printWhitelist(obfuscationOptions, nameOptions, printWhitelistPath);
1140}
1141
1142// Merge similar whitelists and output according to whether the corresponding options are enabled.
1143export function printWhitelist(obfuscationOptions: ObOptions, nameOptions: INameObfuscationOption, defaultPath: string): void {
1144  const enableToplevel = obfuscationOptions.enableToplevelObfuscation;
1145  const enableProperty = obfuscationOptions.enablePropertyObfuscation;
1146  const enableStringProp = obfuscationOptions.enableStringPropertyObfuscation;
1147  const enableExport = obfuscationOptions.enableExportObfuscation;
1148  const enableAtKeep = obfuscationOptions.enableAtKeep;
1149  const reservedConfToplevelArrary = nameOptions.mReservedToplevelNames ?? [];
1150  const reservedConfPropertyArray = nameOptions.mReservedProperties ?? [];
1151  let whitelistObj = {
1152    lang: [],
1153    conf: [],
1154    struct: [],
1155    exported: [],
1156    strProp: [],
1157    enum: []
1158  };
1159
1160  let languareSet: Set<string>;
1161  if (enableProperty) {
1162    languareSet = mergeSet(UnobfuscationCollections.reservedLangForProperty, LocalVariableCollections.reservedLangForLocal);
1163  } else {
1164    languareSet = LocalVariableCollections.reservedLangForLocal;
1165  }
1166  whitelistObj.lang = convertSetToArray(languareSet);
1167
1168  let structSet: Set<string>;
1169  if (enableProperty) {
1170    structSet = UnobfuscationCollections.reservedStruct;
1171  }
1172  whitelistObj.struct = convertSetToArray(structSet);
1173
1174  let exportedSet: Set<string>;
1175  if (enableProperty) {
1176    exportedSet = UnobfuscationCollections.reservedExportNameAndProp;
1177  } else if (enableExport) {
1178    exportedSet = UnobfuscationCollections.reservedExportName;
1179  }
1180  whitelistObj.exported = convertSetToArray(exportedSet);
1181
1182  let stringSet: Set<string>;
1183  if (enableProperty && !enableStringProp) {
1184    stringSet = UnobfuscationCollections.reservedStrProp;
1185  }
1186  whitelistObj.strProp = convertSetToArray(stringSet);
1187
1188  whitelistObj.conf = convertSetToArray(LocalVariableCollections.reservedConfig);
1189  const hasPropertyConfig = enableProperty && reservedConfPropertyArray?.length > 0;
1190  const hasTopLevelConfig = enableToplevel && reservedConfToplevelArrary?.length > 0;
1191  if (hasPropertyConfig) {
1192    whitelistObj.conf.push(...reservedConfPropertyArray);
1193    handleUniversalReservedList(nameOptions.mUniversalReservedProperties, whitelistObj.conf);
1194  }
1195  if (hasTopLevelConfig) {
1196    whitelistObj.conf.push(...reservedConfToplevelArrary);
1197    handleUniversalReservedList(nameOptions.mUniversalReservedToplevelNames, whitelistObj.conf);
1198  }
1199  if (enableAtKeep) {
1200    let atKeepSet: Set<string> = new Set();
1201    addToSet(atKeepSet, AtKeepCollections.keepAsConsumer.globalNames);
1202    addToSet(atKeepSet, AtKeepCollections.keepAsConsumer.propertyNames);
1203    addToSet(atKeepSet, AtKeepCollections.keepSymbol.globalNames);
1204    addToSet(atKeepSet, AtKeepCollections.keepSymbol.propertyNames);
1205    whitelistObj.conf.push(...atKeepSet);
1206  }
1207
1208  let enumSet: Set<string>;
1209  if (enableProperty) {
1210    enumSet = UnobfuscationCollections.reservedEnum;
1211  }
1212  whitelistObj.enum = convertSetToArray(enumSet);
1213
1214  let whitelistContent = JSON.stringify(whitelistObj, null, 2); // 2: indentation
1215  if (!fs.existsSync(path.dirname(defaultPath))) {
1216    fs.mkdirSync(path.dirname(defaultPath), { recursive: true });
1217  }
1218  fs.writeFileSync(defaultPath, whitelistContent);
1219}
1220
1221function handleUniversalReservedList(universalList: RegExp[] | undefined, configArray: string[]): void {
1222  if (universalList?.length > 0) {
1223    universalList.forEach((value) => {
1224      const originalString = UnobfuscationCollections.reservedWildcardMap.get(value);
1225      if (originalString) {
1226        configArray.push(originalString);
1227      }
1228    });
1229  }
1230}
1231
1232// Merge KeptReasons and KeptNames and output
1233export function printUnobfuscationReasons(configPath: string, defaultPath: string): void {
1234  let property: Record<string, string[]> = {};
1235  let unobfuscationObj = { keptReasons: {}, keptNames: { property } };
1236  type WhitelistObject = {
1237    [key in WhitelistType]: string;
1238  };
1239  let keptReasons: WhitelistObject = {
1240    sdk: 'same as the system api names',
1241    lang: 'same as the language keywords',
1242    conf: 'same as the user-configured kept name',
1243    struct: 'same as the ArkUI struct property',
1244    strProp: 'same as the string property',
1245    exported: 'same as the exported names and properties',
1246    enum: 'same as the members in the enum'
1247  };
1248  unobfuscationObj.keptReasons = keptReasons;
1249
1250  if (historyUnobfuscatedPropMap.size === 0) {
1251    // Full compilation or there is no cache after the previous compilation.
1252    UnobfuscationCollections.unobfuscatedPropMap.forEach((value: Set<string>, key: string) => {
1253      let array: string[] = Array.from(value);
1254      unobfuscationObj.keptNames.property[key] = array;
1255    });
1256  } else {
1257    // Retrieve the cache from 'historyUnobfuscatedPropMap' after the previous compilation.
1258    UnobfuscationCollections.unobfuscatedPropMap.forEach((value: Set<string>, key: string) => {
1259      let array: string[] = Array.from(value);
1260      historyUnobfuscatedPropMap.set(key, array);
1261    });
1262    historyUnobfuscatedPropMap.forEach((value: string[], key: string) => {
1263      unobfuscationObj.keptNames.property[key] = value;
1264    });
1265  }
1266
1267  if (historyAllUnobfuscatedNamesMap.size === 0) {
1268    // Full compilation or there is no cache after the previous compilation.
1269    Object.assign(unobfuscationObj.keptNames, unobfuscationNamesObj);
1270  } else {
1271    // Retrieve the cache from 'historyAllUnobfuscatedNamesMap' after the previous compilation.
1272    let historyAllUnobfuscatedNamesObj = Object.fromEntries(historyAllUnobfuscatedNamesMap);
1273    Object.keys(unobfuscationNamesObj).forEach(key => {
1274      historyAllUnobfuscatedNamesObj[key] = unobfuscationNamesObj[key];
1275    });
1276    Object.assign(unobfuscationObj.keptNames, historyAllUnobfuscatedNamesObj);
1277  }
1278
1279  let unobfuscationContent = JSON.stringify(unobfuscationObj, null, 2);
1280  if (!fs.existsSync(path.dirname(defaultPath))) {
1281    fs.mkdirSync(path.dirname(defaultPath), { recursive: true });
1282  }
1283  fs.writeFileSync(defaultPath, unobfuscationContent);
1284
1285  if (!fs.existsSync(path.dirname(configPath))) {
1286    fs.mkdirSync(path.dirname(configPath), { recursive: true });
1287  }
1288  if (configPath) {
1289    fs.copyFileSync(defaultPath, configPath);
1290  }
1291}
1292
1293
1294export function generateConsumerObConfigFile(obfuscationOptions: SourceObConfig, printObfLogger: Function): void {
1295  const projectConfig = { obfuscationOptions, compileHar: true };
1296  const obConfig: ObConfigResolver = new ObConfigResolver(projectConfig, printObfLogger);
1297  obConfig.resolveObfuscationConfigs();
1298}
1299
1300export function mangleFilePath(originalPath: string): string {
1301  const mangledFilePath = renameFileNameModule.getMangleCompletePath(originalPath);
1302  return mangledFilePath;
1303}
1304
1305export function enableObfuscatedFilePathConfig(isPackageModules: boolean, projectConfig: any): boolean {
1306  const isDebugMode = isDebug(projectConfig);
1307  const hasObfuscationConfig = projectConfig.obfuscationMergedObConfig;
1308  if (isDebugMode || !hasObfuscationConfig) {
1309    return false;
1310  }
1311  const disableObfuscation = hasObfuscationConfig.options.disableObfuscation;
1312  const enableFileNameObfuscation = hasObfuscationConfig.options.enableFileNameObfuscation;
1313  if (disableObfuscation || !enableFileNameObfuscation) {
1314    return false;
1315  }
1316  return true;
1317}
1318
1319export function handleObfuscatedFilePath(filePath: string, isPackageModules: boolean, projectConfig: Object): string {
1320  if (!enableObfuscatedFilePathConfig(isPackageModules, projectConfig)) {
1321    return filePath;
1322  }
1323  // Do not obfuscate the file path in dir oh_modules.
1324  if (!isPackageModules) {
1325    return mangleFilePath(filePath);
1326  }
1327  // When open the config 'enableFileNameObfuscation', keeping all paths in unix format.
1328  return FileUtils.toUnixPath(filePath);
1329}
1330
1331export function enableObfuscateFileName(isPackageModules: boolean, projectConfig: Object): boolean {
1332  if (!enableObfuscatedFilePathConfig(isPackageModules, projectConfig)) {
1333    return false;
1334  }
1335
1336  // Do not obfuscate the file path in dir oh_modules.
1337  if (!isPackageModules) {
1338    return true;
1339  }
1340  // When open the config 'enableFileNameObfuscation', keeping all paths in unix format.
1341  return false;
1342}
1343
1344/**
1345 * Get the relative path relative to the project based on the file's associated project
1346 */
1347export function getRelativeSourcePath(
1348  filePath: string,
1349  projectRootPath: string | undefined,
1350  belongProjectPath: string | undefined,
1351): string {
1352  filePath = FileUtils.toUnixPath(filePath);
1353
1354  if (projectRootPath) {
1355    projectRootPath = FileUtils.toUnixPath(projectRootPath);
1356  }
1357
1358  if (belongProjectPath) {
1359    belongProjectPath = FileUtils.toUnixPath(belongProjectPath);
1360  }
1361
1362  let relativeFilePath: string = filePath;
1363  if (projectRootPath && filePath.startsWith(projectRootPath)) {
1364    relativeFilePath = filePath.replace(projectRootPath + '/', '');
1365  } else if (belongProjectPath) {
1366    relativeFilePath = filePath.replace(belongProjectPath + '/', '');
1367  }
1368
1369  return relativeFilePath;
1370}
1371
1372/**
1373 * 'rootAbsPath\\sdk\\default\\openharmony\\ets\\api',
1374 * 'rootAbsPath\\sdk\\default\\openharmony\\ets\\arkts',
1375 * 'rootAbsPath\\sdk\\default\\openharmony\\ets\\kits',
1376 * 'rootAbsPath\\sdk\\default\\hms\\ets\\api',
1377 * 'rootAbsPath\\sdk\\default\\hms\\ets\\kits',
1378 * 'rootAbsPath\\sdk\\default\\openharmony\\ets\\api'
1379 * obfuscationCacheDir: rootAbsPath\\moduleName\\build\\default\\cache\\default\\default@CompileArkTS\\esmodule\\release\\obfuscation
1380 * exportRulePath: rootAbsPath\\moduleName\\build\\default\\intermediates\\obfuscation\\default\\obfuscation.txt
1381 * dependencies: { libraries: [], hars: [], hsps: [], hspLibraries: [] }
1382 */
1383
1384export interface SourceObConfig {
1385  selfConfig: Obfuscation;
1386  sdkApis: string[];
1387  obfuscationCacheDir: string;
1388  exportRulePath: string;
1389  dependencies: ObfuscationDependencies;
1390}
1391
1392export interface Obfuscation {
1393  ruleOptions?: RuleOptions;
1394  consumerRules?: string[]; // absolute path of consumer-rules.txt
1395  consumerFiles?: string | string[]; // relative path of consumer-rules.txt
1396  libDir?: string; // actual path of local module
1397}
1398
1399export interface RuleOptions {
1400  enable?: boolean;
1401  files?: string | string[]; // should be relative path of obfuscation-rules.txt, but undefined now
1402  rules?: string[]; // absolute path of obfuscation-rules.txt
1403}
1404
1405export interface ObfuscationDependencies {
1406  libraries: Obfuscation[];
1407  hars: string[];
1408  hsps?: string[];
1409  hspLibraries?: Obfuscation[];
1410}
1411