• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (c) 2025 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 { ApiExtractor } from '../common/ApiExtractor';
17import { FileUtils } from './FileUtils';
18import {
19  AtIntentCollections,
20  AtKeepCollections,
21  BytecodeObfuscationCollections,
22  UnobfuscationCollections
23} from './CommonCollections';
24import * as crypto from 'crypto';
25import * as ts from 'typescript';
26import fs from 'fs';
27import path from 'path';
28
29export function addToSet<T>(targetSet: Set<T>, sourceSet: Set<T>): void {
30  sourceSet.forEach((element) => targetSet.add(element));
31}
32
33export function arrayToSet<T>(array: T[]): Set<T> {
34  return new Set(array);
35}
36
37export function setToArray<T>(set: Set<T>): T[] {
38  return Array.from(set);
39}
40
41export function areSetsEqual<T>(setA: Set<T>, setB: Set<T>): boolean {
42  if (setA.size !== setB.size) {
43    return false;
44  }
45  for (const setItem of setA) {
46    if (!setB.has(setItem)) {
47      return false;
48    }
49  }
50  return true;
51}
52
53export const SOURCE_FILE_PATHS: string = 'sourceFilePaths.cache';
54export const TRANSFORMED_PATH: string = 'transformed';
55export const FILE_NAMES_MAP: string = 'transformedFileNamesMap.json';
56export const FILE_WHITE_LISTS: string = 'fileWhiteLists.json';
57export const PROJECT_WHITE_LIST: string = 'projectWhiteList.json';
58
59// this while list is only used for bytecode obfuscation
60export const DECORATOR_WHITE_LIST = [
61  'Monitor',
62  'Track',
63  'Trace',
64  'AnimatableExtend'
65];
66
67export interface KeepInfo {
68  propertyNames: Set<string>;
69  globalNames: Set<string>;
70}
71
72/**
73 * Informantion of build files
74 */
75export interface ModuleInfo {
76  content: string;
77  /**
78   * The path in build cache dir
79   */
80  buildFilePath: string;
81  /**
82   * The `originSourceFilePath` relative to project root dir.
83   */
84  relativeSourceFilePath: string;
85  /**
86   * The origin source file path will be set with rollup moduleId when obfuscate intermediate js source code,
87   * whereas be set with tsc node.fileName when obfuscate intermediate ts source code.
88   */
89  originSourceFilePath?: string;
90  rollupModuleId?: string;
91}
92
93export interface FileContent {
94  moduleInfo: ModuleInfo;
95  previousStageSourceMap?: ts.RawSourceMap;
96}
97
98/**
99 * We have these structure to store project white list and file white lists,
100 * project white list is merged by each file white list.
101 *
102 * We have keepinfo and reservedinfo in white lists:
103 *  KeepInfo: names that we cannot obfuscate or obfuscate as.
104 *  ReservedInfo: names that we cannot obfuscate as.
105 *
106 * ProjectWhiteList
107 * ├── projectKeepInfo: ProjectKeepInfo
108 * │   ├── propertyNames: Set<string>
109 * │   └── globalNames: Set<string>
110 * └── projectReservedInfo: ProjectReservedInfo
111 *     ├── enumProperties: Set<string>
112 *     └── propertyParams: Set<string>
113 * FileWhiteList
114 * ├── fileKeepInfo: FileKeepInfo
115 * │   ├── keepSymbol?: KeepInfo (optional)
116 * │   │   ├── propertyNames: Set<string>
117 * │   │   └── globalNames: Set<string>
118 * │   ├── keepAsConsumer?: KeepInfo (optional)
119 * │   │   ├── propertyNames: Set<string>
120 * │   │   └── globalNames: Set<string>
121 * │   ├── structProperties: Set<string>
122 * │   ├── exported: KeepInfo
123 * │   │   ├── propertyNames: Set<string>
124 * │   │   └── globalNames: Set<string>
125 * │   ├── enumProperties: Set<string>
126 * │   └── stringProperties: Set<string>
127 * │   └── arkUIKeepInfo: KeepInfo
128 * │       ├── propertyNames: Set<string>
129 * │       └── globalNames: Set<string>
130 * └── bytecodeObfuscateKeepInfo: BytecodeObfuscateKeepInfo
131 *     └── decoratorMap?: Map<string, string[]>
132 * └── fileReservedInfo: FileReservedInfo
133 *     ├── enumProperties: Set<string>
134 *     └── propertyParams: Set<string>
135 */
136export interface FileKeepInfo {
137  keepSymbol?: KeepInfo; // Names marked with "@KeepSymbol".
138  keepAsConsumer?: KeepInfo; // Names marked with "@KeepAsConsumer".
139  structProperties: Set<string>; // Struct properties collected from struct.
140  exported: KeepInfo; // Exported names and properties.
141  enumProperties: Set<string>; // Enum properties.
142  stringProperties: Set<string>; // String properties.
143  arkUIKeepInfo?: KeepInfo; // Collecting classes and members
144}
145
146export interface FileReservedInfo {
147  enumProperties: Set<string>; // Enum members, collected when has initializer.
148  propertyParams: Set<string>; // Properties parameters in constructor.
149}
150
151export interface BytecodeObfuscateKeepInfo {
152  decoratorMap?: Object; // collect DecoratorMap
153}
154
155export interface FileWhiteList {
156  fileKeepInfo: FileKeepInfo;
157  fileReservedInfo: FileReservedInfo;
158  bytecodeObfuscateKeepInfo?: BytecodeObfuscateKeepInfo
159}
160
161export interface ProjectKeepInfo {
162  propertyNames: Set<string>;
163  globalNames: Set<string>;
164}
165
166export interface ProjectReservedInfo {
167  enumProperties: Set<string>;
168  propertyParams: Set<string>;
169}
170
171export interface ProjectWhiteList {
172  projectKeepInfo: ProjectKeepInfo;
173  projectReservedInfo: ProjectReservedInfo;
174}
175
176export interface ProjectWhiteListJsonData {
177  projectKeepInfo: {
178    propertyNames: Array<string>;
179    globalNames: Array<string>;
180  };
181  projectReservedInfo: {
182    enumProperties: Array<string>;
183    propertyParams: Array<string>;
184  };
185}
186
187
188// The object to manage project white lists, should be initialized in arkObfuscator
189export let projectWhiteListManager: ProjectWhiteListManager | undefined;
190
191// Initialize projectWhiteListManager
192export function initProjectWhiteListManager(cachePath: string, isIncremental: boolean, enableAtKeep: boolean): void {
193  projectWhiteListManager = new ProjectWhiteListManager(cachePath, isIncremental, enableAtKeep);
194}
195
196// Clear projectWhiteListManager
197export function clearProjectWhiteListManager(): void {
198  projectWhiteListManager = undefined;
199}
200
201/**
202 * This class is used to manage project white lists.
203 * Used to collect white list of each file and merge them into project white lists.
204 */
205export class ProjectWhiteListManager {
206  // If atKeep is enabled
207  private enableAtKeep: boolean;
208
209  // Cache path for file white lists
210  private fileWhiteListsCachePath: string;
211
212  // Cache path for project white lists
213  private projectWhiteListCachePath: string;
214
215  // If it is incremental compliation
216  private isIncremental: boolean = false;
217
218  // If we should reObfuscate all files
219  private shouldReObfuscate: boolean = false;
220
221  // White lists of each file
222  private fileWhiteListMap: Map<string, FileWhiteList>;
223
224  // White lists for one file
225  public fileWhiteListInfo: FileWhiteList | undefined;
226
227  public getEnableAtKeep(): boolean {
228    return this.enableAtKeep;
229  }
230
231  public getFileWhiteListsCachePath(): string {
232    return this.fileWhiteListsCachePath;
233  }
234
235  public getProjectWhiteListCachePath(): string {
236    return this.projectWhiteListCachePath;
237  }
238
239  public getIsIncremental(): boolean {
240    return this.isIncremental;
241  }
242
243  public getShouldReObfuscate(): boolean {
244    return this.shouldReObfuscate;
245  }
246
247  public getFileWhiteListMap(): Map<string, FileWhiteList> {
248    return this.fileWhiteListMap;
249  }
250
251  constructor(cachePath: string, isIncremental: boolean, enableAtKeep: boolean) {
252    this.fileWhiteListsCachePath = path.join(cachePath, FILE_WHITE_LISTS);
253    this.projectWhiteListCachePath = path.join(cachePath, PROJECT_WHITE_LIST);
254    this.isIncremental = isIncremental;
255    this.enableAtKeep = enableAtKeep;
256    this.fileWhiteListMap = new Map();
257  }
258
259  private createDefaultFileKeepInfo(): FileKeepInfo {
260    return {
261      structProperties: new Set<string>(),
262      exported: {
263        propertyNames: new Set<string>(),
264        globalNames: new Set<string>(),
265      },
266      enumProperties: new Set<string>(),
267      stringProperties: new Set<string>(),
268      arkUIKeepInfo: {
269        propertyNames: new Set<string>(),
270        globalNames: new Set<string>(),
271      },
272    };
273  }
274
275  private createDefaultFileReservedInfo(): FileReservedInfo {
276    return {
277      enumProperties: new Set<string>(),
278      propertyParams: new Set<string>(),
279    };
280  }
281
282  // Create one fileWhilteList object, with keepSymbol & keepAsConsumer if atKeep is enabled
283  public createFileWhiteList(): FileWhiteList {
284    const fileKeepInfo = this.enableAtKeep
285      ? {
286          keepSymbol: {
287            propertyNames: new Set<string>(),
288            globalNames: new Set<string>(),
289          },
290          keepAsConsumer: {
291            propertyNames: new Set<string>(),
292            globalNames: new Set<string>(),
293          },
294          ...this.createDefaultFileKeepInfo(),
295        }
296      : this.createDefaultFileKeepInfo();
297    return {
298      fileKeepInfo,
299      fileReservedInfo: this.createDefaultFileReservedInfo(),
300    };
301  }
302
303  // Initialize current collector,
304  // should be called before we collect fileWhiteLists.
305  public setCurrentCollector(path: string): void {
306    const unixPath = FileUtils.toUnixPath(path);
307    let fileWhiteListInfo: FileWhiteList | undefined = this.fileWhiteListMap.get(unixPath);
308    if (!fileWhiteListInfo) {
309      fileWhiteListInfo = this.createFileWhiteList();
310      this.fileWhiteListMap.set(unixPath, fileWhiteListInfo);
311    }
312    this.fileWhiteListInfo = fileWhiteListInfo;
313  }
314
315  private readFileWhiteLists(filePath: string): Map<string, FileWhiteList> {
316    const fileContent = fs.readFileSync(filePath, 'utf8');
317    const parsed: Object = JSON.parse(fileContent);
318
319    const map = new Map<string, FileWhiteList>();
320    for (const key in parsed) {
321      if (Object.prototype.hasOwnProperty.call(parsed, key)) {
322        const fileKeepInfo: FileKeepInfo = {
323          keepSymbol: parsed[key].fileKeepInfo.keepSymbol
324            ? {
325                propertyNames: arrayToSet(parsed[key].fileKeepInfo.keepSymbol.propertyNames),
326                globalNames: arrayToSet(parsed[key].fileKeepInfo.keepSymbol.globalNames),
327              }
328            : undefined,
329          keepAsConsumer: parsed[key].fileKeepInfo.keepAsConsumer
330            ? {
331                propertyNames: arrayToSet(parsed[key].fileKeepInfo.keepAsConsumer.propertyNames),
332                globalNames: arrayToSet(parsed[key].fileKeepInfo.keepAsConsumer.globalNames),
333              }
334            : undefined,
335          structProperties: arrayToSet(parsed[key].fileKeepInfo.structProperties),
336          exported: {
337            propertyNames: arrayToSet(parsed[key].fileKeepInfo.exported.propertyNames),
338            globalNames: arrayToSet(parsed[key].fileKeepInfo.exported.globalNames),
339          },
340          enumProperties: arrayToSet(parsed[key].fileKeepInfo.enumProperties),
341          stringProperties: arrayToSet(parsed[key].fileKeepInfo.stringProperties),
342          arkUIKeepInfo: parsed[key].fileKeepInfo.arkUIKeepInfo
343            ? {
344                propertyNames: arrayToSet(parsed[key].fileKeepInfo.arkUIKeepInfo.propertyNames),
345                globalNames: arrayToSet(parsed[key].fileKeepInfo.arkUIKeepInfo.globalNames),
346              }
347            : undefined,
348        };
349
350        const fileReservedInfo: FileReservedInfo = {
351          enumProperties: arrayToSet(parsed[key].fileReservedInfo.enumProperties),
352          propertyParams: arrayToSet(parsed[key].fileReservedInfo.propertyParams),
353        };
354        const bytecodeObfuscateKeepInfo: BytecodeObfuscateKeepInfo = {
355          decoratorMap: parsed[key].bytecodeObfuscateKeepInfo?.decoratorMap,
356        };
357        map.set(key, { fileKeepInfo, fileReservedInfo, bytecodeObfuscateKeepInfo });
358      }
359    }
360
361    return map;
362  }
363
364  private writeFileWhiteLists(filePath: string, fileWhiteLists: Map<string, FileWhiteList>): void {
365    const jsonData: Object = {};
366    for (const [key, value] of fileWhiteLists) {
367      jsonData[key] = {
368        fileKeepInfo: {
369          keepSymbol: value.fileKeepInfo.keepSymbol
370            ? {
371                propertyNames: setToArray(value.fileKeepInfo.keepSymbol.propertyNames),
372                globalNames: setToArray(value.fileKeepInfo.keepSymbol.globalNames),
373              }
374            : undefined,
375          keepAsConsumer: value.fileKeepInfo.keepAsConsumer
376            ? {
377                propertyNames: setToArray(value.fileKeepInfo.keepAsConsumer.propertyNames),
378                globalNames: setToArray(value.fileKeepInfo.keepAsConsumer.globalNames),
379              }
380            : undefined,
381          structProperties: setToArray(value.fileKeepInfo.structProperties),
382          exported: {
383            propertyNames: setToArray(value.fileKeepInfo.exported.propertyNames),
384            globalNames: setToArray(value.fileKeepInfo.exported.globalNames),
385          },
386          enumProperties: setToArray(value.fileKeepInfo.enumProperties),
387          stringProperties: setToArray(value.fileKeepInfo.stringProperties),
388          arkUIKeepInfo: value.fileKeepInfo.arkUIKeepInfo
389            ? {
390                propertyNames: setToArray(value.fileKeepInfo.arkUIKeepInfo.propertyNames),
391                globalNames: setToArray(value.fileKeepInfo.arkUIKeepInfo.globalNames),
392              }
393            : undefined,
394        },
395        fileReservedInfo: {
396          enumProperties: setToArray(value.fileReservedInfo.enumProperties),
397          propertyParams: setToArray(value.fileReservedInfo.propertyParams),
398        },
399      };
400      if (value.bytecodeObfuscateKeepInfo?.decoratorMap) {
401        jsonData[key].bytecodeObfuscateKeepInfo = {
402          decoratorMap: value.bytecodeObfuscateKeepInfo.decoratorMap,
403        };
404      }
405    }
406
407    const jsonString = JSON.stringify(jsonData, null, 2);
408    fs.writeFileSync(filePath, jsonString, 'utf8');
409  }
410
411  private readProjectWhiteList(filePath: string): ProjectWhiteList {
412    const fileContent = fs.readFileSync(filePath, 'utf8');
413    const parsed: ProjectWhiteListJsonData = JSON.parse(fileContent);
414
415    const projectKeepInfo: ProjectKeepInfo = {
416      propertyNames: arrayToSet(parsed.projectKeepInfo.propertyNames),
417      globalNames: arrayToSet(parsed.projectKeepInfo.globalNames),
418    };
419
420    const projectReservedInfo: ProjectReservedInfo = {
421      enumProperties: arrayToSet(parsed.projectReservedInfo.enumProperties),
422      propertyParams: arrayToSet(parsed.projectReservedInfo.propertyParams),
423    };
424
425    return {
426      projectKeepInfo,
427      projectReservedInfo,
428    };
429  }
430
431  private writeProjectWhiteList(filePath: string, projectWhiteList: ProjectWhiteList): void {
432    const jsonData: ProjectWhiteListJsonData = {
433      projectKeepInfo: {
434        propertyNames: setToArray(projectWhiteList.projectKeepInfo.propertyNames),
435        globalNames: setToArray(projectWhiteList.projectKeepInfo.globalNames),
436      },
437      projectReservedInfo: {
438        enumProperties: setToArray(projectWhiteList.projectReservedInfo.enumProperties),
439        propertyParams: setToArray(projectWhiteList.projectReservedInfo.propertyParams),
440      },
441    };
442
443    const jsonString = JSON.stringify(jsonData, null, 2);
444    fs.writeFileSync(filePath, jsonString, 'utf8');
445  }
446
447  public updateFileWhiteListMap(deletedFilePath?: Set<string>): void {
448    const lastFileWhiteLists: Map<string, FileWhiteList> = this.readFileWhiteLists(this.fileWhiteListsCachePath);
449
450    deletedFilePath?.forEach((path) => {
451      lastFileWhiteLists.delete(path);
452    });
453    this.fileWhiteListMap.forEach((value, key) => {
454      lastFileWhiteLists.set(key, value);
455    });
456    this.writeFileWhiteLists(this.fileWhiteListsCachePath, lastFileWhiteLists);
457    this.fileWhiteListMap = lastFileWhiteLists;
458  }
459
460  public createProjectWhiteList(fileWhiteLists: Map<string, FileWhiteList>): ProjectWhiteList {
461    const projectKeepInfo: ProjectKeepInfo = {
462      propertyNames: new Set(),
463      globalNames: new Set(),
464    };
465
466    const projectReservedInfo: ProjectReservedInfo = {
467      enumProperties: new Set(),
468      propertyParams: new Set(),
469    };
470
471    fileWhiteLists.forEach((fileWhiteList) => {
472      // 1. Collect fileKeepInfo
473      // Collect keepSymbol
474      fileWhiteList.fileKeepInfo.keepSymbol?.globalNames.forEach((globalName) => {
475        projectKeepInfo.globalNames.add(globalName);
476      });
477      fileWhiteList.fileKeepInfo.keepSymbol?.propertyNames.forEach((propertyName) => {
478        projectKeepInfo.propertyNames.add(propertyName);
479      });
480
481      // Collect keepAsConsumer
482      fileWhiteList.fileKeepInfo.keepAsConsumer?.globalNames.forEach((globalName) => {
483        projectKeepInfo.globalNames.add(globalName);
484      });
485      fileWhiteList.fileKeepInfo.keepAsConsumer?.propertyNames.forEach((propertyName) => {
486        projectKeepInfo.propertyNames.add(propertyName);
487      });
488
489      // Collect structProperties
490      fileWhiteList.fileKeepInfo.structProperties.forEach((propertyName) => {
491        projectKeepInfo.propertyNames.add(propertyName);
492      });
493
494      // Collect exportedNames
495      fileWhiteList.fileKeepInfo.exported.globalNames.forEach((globalName) => {
496        projectKeepInfo.globalNames.add(globalName);
497      });
498      fileWhiteList.fileKeepInfo.exported.propertyNames.forEach((propertyName) => {
499        projectKeepInfo.propertyNames.add(propertyName);
500      });
501
502      // Collect enumProperties
503      fileWhiteList.fileKeepInfo.enumProperties.forEach((propertyName) => {
504        projectKeepInfo.propertyNames.add(propertyName);
505      });
506
507      // Collect stringProperties
508      fileWhiteList.fileKeepInfo.stringProperties.forEach((propertyName) => {
509        projectKeepInfo.propertyNames.add(propertyName);
510      });
511
512      // Collect arkUIKeepInfo
513      fileWhiteList.fileKeepInfo.arkUIKeepInfo?.globalNames.forEach((globalName) => {
514        projectKeepInfo.globalNames.add(globalName);
515        AtIntentCollections.globalNames.add(globalName);
516      });
517      fileWhiteList.fileKeepInfo.arkUIKeepInfo?.propertyNames.forEach((propertyName) => {
518        projectKeepInfo.propertyNames.add(propertyName);
519        AtIntentCollections.propertyNames.add(propertyName);
520      });
521
522      // 2. Collect fileReservedInfo
523      // Collect enumProperties
524      fileWhiteList.fileReservedInfo.enumProperties.forEach((enumPropertyName) => {
525        projectReservedInfo.enumProperties.add(enumPropertyName);
526      });
527
528      // Collect propertyParams
529      fileWhiteList.fileReservedInfo.propertyParams.forEach((propertyParam) => {
530        projectReservedInfo.propertyParams.add(propertyParam);
531      });
532
533      const decoratorMap = fileWhiteList.bytecodeObfuscateKeepInfo?.decoratorMap;
534      for (const key in decoratorMap) {
535        if (Object.prototype.hasOwnProperty.call(decoratorMap, key)) {
536          decoratorMap[key]?.forEach(item => projectKeepInfo.globalNames.add(item));
537        }
538      }
539    });
540
541    const projectWhiteList = {
542      projectKeepInfo: projectKeepInfo,
543      projectReservedInfo: projectReservedInfo,
544    };
545
546    return projectWhiteList;
547  }
548
549  // Determine if the project's white list has been updated.
550  private areProjectWhiteListsEqual(whiteList1: ProjectWhiteList, whiteList2: ProjectWhiteList): boolean {
551    const projectKeepInfoEqual =
552      areSetsEqual(whiteList1.projectKeepInfo.propertyNames, whiteList2.projectKeepInfo.propertyNames) &&
553      areSetsEqual(whiteList1.projectKeepInfo.globalNames, whiteList2.projectKeepInfo.globalNames);
554
555    if (!projectKeepInfoEqual) {
556      return false;
557    }
558
559    const projectReservedInfoEqual =
560      areSetsEqual(whiteList1.projectReservedInfo.enumProperties, whiteList2.projectReservedInfo.enumProperties) &&
561      areSetsEqual(whiteList1.projectReservedInfo.propertyParams, whiteList2.projectReservedInfo.propertyParams);
562
563    return projectReservedInfoEqual;
564  }
565
566  // We only scan updated files or newly created files in incremental compliation,
567  // so we need to update the UnobfuscationCollections and reserved names use all file white lists.
568  // This should be called in incremental compliation after we updated file white list.
569  private updateUnobfuscationCollections(): void {
570    this.fileWhiteListMap.forEach((fileWhiteList) => {
571      if (this.enableAtKeep) {
572        addToSet(AtKeepCollections.keepSymbol.propertyNames, fileWhiteList.fileKeepInfo.keepSymbol.propertyNames);
573        addToSet(AtKeepCollections.keepSymbol.globalNames, fileWhiteList.fileKeepInfo.keepSymbol.globalNames);
574        addToSet(AtKeepCollections.keepAsConsumer.propertyNames, fileWhiteList.fileKeepInfo.keepAsConsumer.propertyNames);
575        addToSet(AtKeepCollections.keepAsConsumer.globalNames, fileWhiteList.fileKeepInfo.keepAsConsumer.globalNames);
576      }
577      addToSet(UnobfuscationCollections.reservedStruct, fileWhiteList.fileKeepInfo.structProperties);
578      addToSet(UnobfuscationCollections.reservedEnum, fileWhiteList.fileKeepInfo.enumProperties);
579      addToSet(UnobfuscationCollections.reservedExportName, fileWhiteList.fileKeepInfo.exported.globalNames);
580      addToSet(UnobfuscationCollections.reservedExportNameAndProp, fileWhiteList.fileKeepInfo.exported.propertyNames);
581      addToSet(UnobfuscationCollections.reservedStrProp, fileWhiteList.fileKeepInfo.stringProperties);
582      if (fileWhiteList.fileKeepInfo.arkUIKeepInfo) {
583        addToSet(AtIntentCollections.propertyNames, fileWhiteList.fileKeepInfo.arkUIKeepInfo.propertyNames);
584        addToSet(AtIntentCollections.globalNames, fileWhiteList.fileKeepInfo.arkUIKeepInfo.globalNames);
585      }
586      addToSet(ApiExtractor.mConstructorPropertySet, fileWhiteList.fileReservedInfo.propertyParams);
587      addToSet(ApiExtractor.mEnumMemberSet, fileWhiteList.fileReservedInfo.enumProperties);
588      const decoratorMap = fileWhiteList.bytecodeObfuscateKeepInfo?.decoratorMap;
589      for (const key in decoratorMap) {
590        if (Object.prototype.hasOwnProperty.call(decoratorMap, key)) {
591          decoratorMap[key]?.forEach(item => BytecodeObfuscationCollections.decoratorProp.add(item));
592        }
593      }
594    });
595  }
596
597  private updateProjectWhiteList(): ProjectWhiteList {
598    const lastProjectWhiteList: ProjectWhiteList = this.readProjectWhiteList(this.projectWhiteListCachePath);
599    const newestProjectWhiteList: ProjectWhiteList = this.createProjectWhiteList(this.fileWhiteListMap);
600    if (!this.areProjectWhiteListsEqual(lastProjectWhiteList, newestProjectWhiteList)) {
601      this.writeProjectWhiteList(this.projectWhiteListCachePath, newestProjectWhiteList);
602      this.shouldReObfuscate = true;
603    }
604    return newestProjectWhiteList;
605  }
606
607  public createOrUpdateWhiteListCaches(deletedFilePath?: Set<string>): void {
608    let projectWhiteList: ProjectWhiteList;
609    if (!this.isIncremental) {
610      this.writeFileWhiteLists(this.fileWhiteListsCachePath, this.fileWhiteListMap);
611      projectWhiteList = this.createProjectWhiteList(this.fileWhiteListMap);
612      this.writeProjectWhiteList(this.projectWhiteListCachePath, projectWhiteList);
613    } else {
614      this.updateFileWhiteListMap(deletedFilePath);
615      projectWhiteList = this.updateProjectWhiteList();
616      this.updateUnobfuscationCollections();
617    }
618    this.fileWhiteListMap.clear();
619  }
620}
621
622/**
623 * This class is used to manage sourceFilePath.json file.
624 * Will be created when initialize arkObfuscator.
625 */
626export class FilePathManager {
627  // Stores all source files paths
628  private sourceFilePaths: Set<string>;
629
630  // Files deleted in incremental build
631  private deletedSourceFilePaths: Set<string>;
632
633  // Files added in incremental build
634  private addedSourceFilePaths: Set<string>;
635
636  // Cache path of sourceFilePaths.cache file
637  private filePathsCache: string;
638
639  public getSourceFilePaths(): Set<string> {
640    return this.sourceFilePaths;
641  }
642
643  public getDeletedSourceFilePaths(): Set<string> {
644    return this.deletedSourceFilePaths;
645  }
646
647  public getAddedSourceFilePaths(): Set<string> {
648    return this.addedSourceFilePaths;
649  }
650
651  public getFilePathsCache(): string {
652    return this.filePathsCache;
653  }
654
655  constructor(cachePath: string) {
656    this.filePathsCache = path.join(cachePath, SOURCE_FILE_PATHS);
657    this.sourceFilePaths = new Set();
658    this.deletedSourceFilePaths = new Set();
659    this.addedSourceFilePaths = new Set();
660  }
661
662  private setSourceFilePaths(sourceFilePaths: Set<string>): void {
663    sourceFilePaths.forEach((filePath) => {
664      if (!FileUtils.isReadableFile(filePath) || !ApiExtractor.isParsableFile(filePath)) {
665        return;
666      }
667      this.sourceFilePaths.add(filePath);
668    });
669  }
670
671  private writeSourceFilePaths(): void {
672    const content = Array.from(this.sourceFilePaths).join('\n');
673    FileUtils.writeFile(this.filePathsCache, content);
674  }
675
676  // Update sourceFilePaths.cache and get deleted file list and added file list
677  private updateSourceFilePaths(): void {
678    const cacheContent = FileUtils.readFile(this.filePathsCache).split('\n');
679    const cacheSet = new Set(cacheContent);
680
681    for (const path of cacheSet) {
682      if (!this.sourceFilePaths.has(path)) {
683        this.deletedSourceFilePaths.add(path);
684      }
685    }
686
687    for (const path of this.sourceFilePaths) {
688      if (!cacheSet.has(path)) {
689        this.addedSourceFilePaths.add(path);
690      }
691    }
692
693    this.writeSourceFilePaths();
694  }
695
696  // Create or update sourceFilePaths.cache
697  public createOrUpdateSourceFilePaths(sourceFilePaths: Set<string>): void {
698    this.setSourceFilePaths(sourceFilePaths);
699    if (this.isIncremental()) {
700      this.updateSourceFilePaths();
701    } else {
702      this.writeSourceFilePaths();
703    }
704  }
705
706  public isIncremental(): boolean {
707    return fs.existsSync(this.filePathsCache);
708  }
709}
710
711/**
712 * This class is used to manage transfromed file content.
713 * Will be created when initialize arkObfuscator.
714 */
715export class FileContentManager {
716  // Path that stores all transfromed file content
717  private transformedFilesDir: string;
718
719  // Path of fileNamesMap
720  private fileNamesMapPath: string;
721
722  // Stores [originpath -> transformed cache path]
723  public fileNamesMap: Map<string, string>;
724
725  // If it is incremental compilation
726  private isIncremental: boolean;
727
728  public getTransformedFilesDir(): string {
729    return this.transformedFilesDir;
730  }
731
732  public getFileNamesMapPath(): string {
733    return this.fileNamesMapPath;
734  }
735
736  public getIsIncremental(): boolean {
737    return this.isIncremental;
738  }
739
740  constructor(cachePath: string, isIncremental: boolean) {
741    this.transformedFilesDir = path.join(cachePath, TRANSFORMED_PATH);
742    this.fileNamesMapPath = path.join(this.transformedFilesDir, FILE_NAMES_MAP);
743    this.fileNamesMap = new Map();
744    this.isIncremental = isIncremental;
745    FileUtils.createDirectory(this.transformedFilesDir);
746  }
747
748  // Generate hash from filePah
749  private generatePathHash(filePath: string): string {
750    const hash = crypto.createHash('md5');
751    hash.update(filePath);
752    return hash.digest('hex').slice(0, 16);
753  }
754
755  // Generate new file name for transfromed sourcefiles.
756  // Generated from origin filePath's hash & time stamp.
757  private generateFileName(filePath: string): string {
758    const hash = this.generatePathHash(filePath);
759    const timestamp = Date.now().toString();
760    return `${hash}_${timestamp}`;
761  }
762
763  // Delete a set of files, used in incremental compliation if we have deleted some files
764  public deleteFileContent(deletedSourceFilePaths: Set<string>): void {
765    deletedSourceFilePaths.forEach((filePath) => {
766      this.deleteTransformedFile(filePath);
767    });
768  }
769
770  // Delete transfromed file
771  private deleteTransformedFile(deletedSourceFilePath: string): void {
772    if (this.fileNamesMap.has(deletedSourceFilePath)) {
773      const transformedFilePath: string = this.fileNamesMap.get(deletedSourceFilePath);
774      this.fileNamesMap.delete(deletedSourceFilePath);
775      FileUtils.deleteFile(transformedFilePath);
776    }
777  }
778
779  public readFileNamesMap(): void {
780    const jsonObject = FileUtils.readFileAsJson(this.fileNamesMapPath);
781    this.fileNamesMap = new Map<string, string>(Object.entries(jsonObject));
782  }
783
784  public writeFileNamesMap(): void {
785    const jsonObject = Object.fromEntries(this.fileNamesMap.entries());
786    const jsonString = JSON.stringify(jsonObject, null, 2);
787    FileUtils.writeFile(this.fileNamesMapPath, jsonString);
788    this.fileNamesMap.clear();
789  }
790
791  public readFileContent(transformedFilePath: string): FileContent {
792    const fileContentJson = FileUtils.readFile(transformedFilePath);
793    const fileConstent: FileContent = JSON.parse(fileContentJson);
794    return fileConstent;
795  }
796
797  private writeFileContent(filePath: string, fileContent: FileContent): void {
798    const jsonString = JSON.stringify(fileContent, null, 2);
799    FileUtils.writeFile(filePath, jsonString);
800  }
801
802  // Update file content for newly created files or updated files
803  public updateFileContent(fileContent: FileContent): void {
804    const originPath = FileUtils.toUnixPath(fileContent.moduleInfo.originSourceFilePath);
805    if (this.isIncremental) {
806      this.deleteTransformedFile(originPath);
807    }
808    const fileName = this.generateFileName(originPath);
809    const transformedFilePath = path.join(this.transformedFilesDir, fileName);
810    this.fileNamesMap.set(originPath, transformedFilePath);
811    this.writeFileContent(transformedFilePath, fileContent);
812  }
813
814  public getSortedFiles(): string[] {
815    return (this.fileNamesMap && Array.from(this.fileNamesMap.keys())).sort((a, b) => a.localeCompare(b)) || [];
816  }
817}
818