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