• 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 {
17  ArkObfuscator,
18  ObfuscationResultType,
19  PropCollections,
20  performancePrinter,
21  renameIdentifierModule
22} from './ArkObfuscator';
23import { readProjectProperties } from './common/ApiReader';
24import { FileUtils } from './utils/FileUtils';
25import { EventList } from './utils/PrinterUtils';
26import { handleReservedConfig } from './utils/TransformUtil';
27import {
28  IDENTIFIER_CACHE,
29  NAME_CACHE_SUFFIX,
30  PROPERTY_CACHE_FILE,
31  deleteLineInfoForNameString,
32  getMapFromJson,
33  readCache,
34  writeCache
35} from './utils/NameCacheUtil';
36
37import * as fs from 'fs';
38import path from 'path';
39import filterFileArray from './configs/test262filename/filterFilenameList.json';
40
41import type { IOptions } from './configs/IOptions';
42
43const JSON_TEXT_INDENT_LENGTH: number = 2;
44
45export class ArkObfuscatorForTest extends ArkObfuscator {
46  // A list of source file path
47  private readonly mSourceFiles: string[];
48
49  // Path of obfuscation configuration file.
50  private readonly mConfigPath: string;
51
52  constructor(sourceFiles?: string[], configPath?: string) {
53    super();
54    this.mSourceFiles = sourceFiles;
55    this.mConfigPath = configPath;
56  }
57
58  public get configPath(): string {
59    return this.mConfigPath;
60  }
61
62  /**
63   * init ArkObfuscator according to user config
64   * should be called after constructor
65   */
66  public init(config: IOptions | undefined): boolean {
67    if (!config) {
68        console.error('obfuscation config file is not found and no given config.');
69        return false;
70    }
71
72    handleReservedConfig(config, 'mNameObfuscation', 'mReservedProperties', 'mUniversalReservedProperties');
73    handleReservedConfig(config, 'mNameObfuscation', 'mReservedToplevelNames', 'mUniversalReservedToplevelNames');
74    return super.init(config);
75  }
76
77  /**
78   * Obfuscate all the source files.
79   */
80  public async obfuscateFiles(): Promise<void> {
81    if (!path.isAbsolute(this.mCustomProfiles.mOutputDir)) {
82      this.mCustomProfiles.mOutputDir = path.join(path.dirname(this.mConfigPath), this.mCustomProfiles.mOutputDir);
83    }
84    if (this.mCustomProfiles.mOutputDir && !fs.existsSync(this.mCustomProfiles.mOutputDir)) {
85      fs.mkdirSync(this.mCustomProfiles.mOutputDir);
86    }
87
88    performancePrinter?.filesPrinter?.startEvent(EventList.ALL_FILES_OBFUSCATION);
89    readProjectProperties(this.mSourceFiles, this.mCustomProfiles);
90    const propertyCachePath = path.join(this.mCustomProfiles.mOutputDir,
91                                        path.basename(this.mSourceFiles[0])); // Get dir name
92    this.readPropertyCache(propertyCachePath);
93
94    // support directory and file obfuscate
95    for (const sourcePath of this.mSourceFiles) {
96      if (!fs.existsSync(sourcePath)) {
97        console.error(`File ${FileUtils.getFileName(sourcePath)} is not found.`);
98        return;
99      }
100
101      if (fs.lstatSync(sourcePath).isFile()) {
102        await this.obfuscateFile(sourcePath, this.mCustomProfiles.mOutputDir);
103        continue;
104      }
105
106      const dirPrefix: string = FileUtils.getPrefix(sourcePath);
107      await this.obfuscateDir(sourcePath, dirPrefix);
108    }
109
110    this.producePropertyCache(propertyCachePath);
111    performancePrinter?.filesPrinter?.endEvent(EventList.ALL_FILES_OBFUSCATION);
112    performancePrinter?.timeSumPrinter?.print('Sum up time of processes');
113    performancePrinter?.timeSumPrinter?.summarizeEventDuration();
114  }
115
116  /**
117   * obfuscate directory
118   * @private
119   */
120  private async obfuscateDir(dirName: string, dirPrefix: string): Promise<void> {
121    const currentDir: string = FileUtils.getPathWithoutPrefix(dirName, dirPrefix);
122    let newDir: string = this.mCustomProfiles.mOutputDir;
123    // there is no need to create directory because the directory names will be obfuscated.
124    if (!this.mCustomProfiles.mRenameFileName?.mEnable) {
125      newDir = path.join(this.mCustomProfiles.mOutputDir, currentDir);
126      if (!fs.existsSync(newDir)) {
127        fs.mkdirSync(newDir);
128      }
129    }
130
131    const fileNames: string[] = fs.readdirSync(dirName);
132    for (let fileName of fileNames) {
133      const filePath: string = path.join(dirName, fileName);
134      if (fs.lstatSync(filePath).isFile()) {
135        await this.obfuscateFile(filePath, newDir);
136        continue;
137      }
138
139      await this.obfuscateDir(filePath, dirPrefix);
140    }
141  }
142
143  /**
144   * Obfuscate single source file with path provided
145   *
146   * @param sourceFilePath single source file path
147   * @param outputDir
148   */
149  public async obfuscateFile(sourceFilePath: string, outputDir: string): Promise<void> {
150    const fileName: string = FileUtils.getFileName(sourceFilePath);
151    if (this.isObfsIgnoreFile(fileName)) {
152      fs.copyFileSync(sourceFilePath, path.join(outputDir, fileName));
153      return;
154    }
155
156    const test262Filename = this.getPathAfterTest262SecondLevel(sourceFilePath);
157    const isFileInArray = filterFileArray.includes(test262Filename);
158    // To skip the path where 262 test will fail.
159    if (isFileInArray) {
160      return;
161    }
162
163    // Add the whitelist of file name obfuscation for ut.
164    if (this.mCustomProfiles.mRenameFileName?.mEnable) {
165      const reservedArray = this.mCustomProfiles.mRenameFileName.mReservedFileNames;
166      FileUtils.collectPathReservedString(this.mConfigPath, reservedArray);
167    }
168    let content: string = FileUtils.readFile(sourceFilePath);
169    this.readNameCache(sourceFilePath, outputDir);
170    performancePrinter?.filesPrinter?.startEvent(sourceFilePath);
171    const mixedInfo: ObfuscationResultType = await this.obfuscate(content, sourceFilePath);
172    performancePrinter?.filesPrinter?.endEvent(sourceFilePath, undefined, true);
173
174    if (this.mWriteOriginalFile && mixedInfo) {
175      // Write the obfuscated content directly to orignal file.
176      fs.writeFileSync(sourceFilePath, mixedInfo.content);
177      return;
178    }
179    if (outputDir && mixedInfo) {
180      // the writing file is for the ut.
181      const testCasesRootPath = path.join(__dirname, '../', 'test/grammar');
182      let relativePath = '';
183      let resultPath = '';
184      if (this.mCustomProfiles.mRenameFileName?.mEnable && mixedInfo.filePath) {
185        relativePath = mixedInfo.filePath.replace(testCasesRootPath, '');
186      } else {
187        relativePath = sourceFilePath.replace(testCasesRootPath, '');
188      }
189      resultPath = path.join(this.mCustomProfiles.mOutputDir, relativePath);
190      fs.mkdirSync(path.dirname(resultPath), { recursive: true });
191      fs.writeFileSync(resultPath, mixedInfo.content);
192
193      if (this.mCustomProfiles.mEnableSourceMap && mixedInfo.sourceMap) {
194        fs.writeFileSync(path.join(resultPath + '.map'),
195          JSON.stringify(mixedInfo.sourceMap, null, JSON_TEXT_INDENT_LENGTH));
196      }
197
198      if (this.mCustomProfiles.mEnableNameCache && this.mCustomProfiles.mEnableNameCache) {
199        this.produceNameCache(mixedInfo.nameCache, resultPath);
200      }
201    }
202  }
203
204  private getPathAfterTest262SecondLevel(fullPath: string): string {
205    const pathParts = fullPath.split('/');
206    const dataIndex = pathParts.indexOf('test262');
207    // 2: Calculate the index of the second-level directory after 'test262'
208    const secondLevelIndex = dataIndex + 2;
209
210    if (dataIndex !== -1 && secondLevelIndex < pathParts.length) {
211      return pathParts.slice(secondLevelIndex).join('/');
212    }
213
214    return fullPath;
215  }
216
217  private produceNameCache(namecache: { [k: string]: string | {} }, resultPath: string): void {
218    const nameCachePath: string = resultPath + NAME_CACHE_SUFFIX;
219    fs.writeFileSync(nameCachePath, JSON.stringify(namecache, null, JSON_TEXT_INDENT_LENGTH));
220  }
221
222  private readNameCache(sourceFile: string, outputDir: string): void {
223    if (!this.mCustomProfiles.mNameObfuscation?.mEnable || !this.mCustomProfiles.mEnableNameCache) {
224      return;
225    }
226
227    const nameCachePath: string = path.join(outputDir, FileUtils.getFileName(sourceFile) + NAME_CACHE_SUFFIX);
228    const nameCache: Object = readCache(nameCachePath);
229    let historyNameCache = new Map<string, string>();
230    let identifierCache = nameCache ? Reflect.get(nameCache, IDENTIFIER_CACHE) : undefined;
231    deleteLineInfoForNameString(historyNameCache, identifierCache);
232
233    renameIdentifierModule.historyNameCache = historyNameCache;
234  }
235
236  private producePropertyCache(outputDir: string): void {
237    if (this.mCustomProfiles.mNameObfuscation &&
238      this.mCustomProfiles.mNameObfuscation.mRenameProperties &&
239      this.mCustomProfiles.mEnableNameCache) {
240      const propertyCachePath: string = path.join(outputDir, PROPERTY_CACHE_FILE);
241      writeCache(PropCollections.globalMangledTable, propertyCachePath);
242    }
243  }
244
245  private readPropertyCache(outputDir: string): void {
246    if (!this.mCustomProfiles.mNameObfuscation?.mRenameProperties || !this.mCustomProfiles.mEnableNameCache) {
247      return;
248    }
249
250    const propertyCachePath: string = path.join(outputDir, PROPERTY_CACHE_FILE);
251    const propertyCache: Object = readCache(propertyCachePath);
252    if (!propertyCache) {
253      return;
254    }
255
256    PropCollections.historyMangledTable = getMapFromJson(propertyCache);
257  }
258}