• 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 childProcess from 'child_process';
17import path from 'path';
18import fs from 'fs';
19import {
20  FILESINFO_TXT,
21  MODULES_ABC,
22  SOURCEMAPS
23} from '../../pre_define';
24import {
25  mkDir,
26  writeFileSync
27} from '../../utils';
28import {
29  ES_ANNOTATIONS,
30  SOURCEMAPS_JSON,
31  WIDGETS_ABC,
32  yellow,
33  DECORATOR_WHITE_LIST,
34  BYTECODE_OBFUSCATION_PROPERTY_WHITE_LIST,
35  IDENTIFIER_CACHE,
36  MEMBER_METHOD_CACHE
37} from './common/ark_define';
38import {
39  ReservedNameInfo,
40  separateUniversalReservedItem,
41  UnobfuscationCollections,
42} from 'arkguard';
43import {
44  SourceMapSegmentObj,
45  decodeSourcemap,
46  SourceMapLink,
47  Source
48} from 'arkguard/lib/utils/SourceMapMergingUtil';
49import { MergedConfig } from './common/ob_config_resolver';
50import { ModuleInfo } from './module/module_mode';
51import { initArkConfig } from './common/process_ark_config';
52import esInfo from 'arkguard/lib/configs/preset/es_reserved_properties.json';
53import {
54  CommonLogger,
55  LogData,
56  LogDataFactory
57} from './logger';
58import {
59  ArkTSErrorDescription,
60  ErrorCode
61} from './error_code';
62import { FILE_WHITE_LISTS } from 'arkguard/lib/utils/ProjectCollections';
63import {
64  FileKeepInfo,
65  FileReservedInfo
66} from 'arkguard/lib/utils/ProjectCollections';
67import { SourceMapGenerator } from './generate_sourcemap';
68
69const FILE_NAME_CACHE: string = 'FileNameCache';
70const OBF_NAME_MAP: string = 'ObfNameMap';
71const PROPERTY_CACHE: string = 'ProPertyCache';
72const ENTRY_PACKAGE_INFO: string = 'entryPackageInfo';
73const COMPILE_SDK_VERSION: string = 'compileSdkVersion';
74
75export class BytecodeObfuscator {
76  public static enable = false;
77  private static instance: BytecodeObfuscator | undefined = undefined;
78  public cmdArgs: string[];
79  private projectConfig: Object;
80  private logger: Object;
81  private originDir: string;
82  private obfDir: string;
83  private obfuscationCacheDir: string;
84  private bytecodeObfuscateConfig: BytecodeObfuscationConfig;
85  private debugging: boolean;
86  private enhanced: boolean;
87  private arkConfig: ArkConfig;
88  private configPath: string;
89  private nameCachePath: string;
90  private backupSourceMapPath: string;
91
92  constructor(share: Object, arkProjectConfig: Object) {
93    this.projectConfig = Object.assign(arkProjectConfig, share.projectConfig);
94    const rollup = {
95      share: share,
96    };
97    this.logger = CommonLogger.getInstance(rollup);
98    this.obfuscationCacheDir = this.projectConfig.obfuscationOptions.obfuscationCacheDir;
99    this.originDir = path.join(this.obfuscationCacheDir, 'origin');
100    this.obfDir = path.join(this.obfuscationCacheDir, 'obf');
101    this.configPath = path.join(this.obfuscationCacheDir, 'config.json');
102    this.backupSourceMapPath = path.join(this.originDir, SOURCEMAPS_JSON);
103    /**
104     * When enhanced == true,
105     * a bytecode har will be obfuscated when it is referenced by HAP,
106     * but it is not obfuscated by default
107     */
108    this.enhanced = false;
109    this.debugging = arkProjectConfig.obfuscationMergedObConfig?.options.bytecodeObf?.debugging;
110    this.arkConfig = initArkConfig(this.projectConfig);
111    this.cmdArgs = [];
112  }
113
114  public static cleanBcObfuscatorObject(): void {
115    if (this.instance) {
116      this.instance = undefined;
117    }
118    if (this.enable) {
119      this.enable = null;
120    }
121  }
122
123  static initForTest(share: Object): void {
124    if (!share.arkProjectConfig.isBytecodeObfEnabled) {
125      this.enable = false;
126      return;
127    }
128    this.enable = true;
129    BytecodeObfuscator.instance = new BytecodeObfuscator(share, share.arkProjectConfig);
130    BytecodeObfuscator.instance.bytecodeObfuscateConfig =
131      new BytecodeObfuscationConfig(share, share.arkProjectConfig);
132  }
133
134  static init(share: Object, arkProjectConfig: Object): void {
135    if (!arkProjectConfig.isBytecodeObfEnabled) {
136      this.enable = false;
137      return;
138    }
139    this.enable = true;
140    BytecodeObfuscator.instance = new BytecodeObfuscator(share, arkProjectConfig);
141    if (!isVersionCompatible(
142      BytecodeObfuscator.instance.projectConfig.compatibleSdkVersion,
143      BytecodeObfuscator.instance.projectConfig.compatibleSdkVersionStage)) {
144      const errInfo: LogData = LogDataFactory.newInstance(
145        ErrorCode.BYTECODE_OBFUSCATION_UNSUPPORT_COMPATIBLESDKVERSION,
146        ArkTSErrorDescription,
147        `bytecodeObfuscation only can be used in the beta3 version of API 12 or higher version`,
148        '',
149        [
150          'Check the compatibleSdkVersion and compatibleSdkVersionStage in build-profile.json',
151          'if compatibleSdkVersion is set to API 12,then compatibleSdkVersionStage needs to be configured as beta3',
152        ]
153      );
154      BytecodeObfuscator.instance.logger.printErrorAndExit(errInfo);
155    }
156    if (!fs.existsSync(BytecodeObfuscator.instance.originDir)) {
157      mkDir(BytecodeObfuscator.instance.originDir);
158    }
159    if (!fs.existsSync(BytecodeObfuscator.instance.obfDir)) {
160      mkDir(BytecodeObfuscator.instance.obfDir);
161    }
162
163    BytecodeObfuscator.instance.bytecodeObfuscateConfig =
164      new BytecodeObfuscationConfig(share, arkProjectConfig);
165    this.getInstance().getSystemApiConfigsByCache();
166    this.getInstance().bytecodeObfuscateConfig.addReservedNames(collectReservedLangForProperty());
167  }
168
169  static getInstance(): BytecodeObfuscator {
170    return BytecodeObfuscator.instance;
171  }
172
173  public removeStructProp(): void {
174    if (!BytecodeObfuscator.enable ||
175      !this.bytecodeObfuscateConfig.obfuscationRules.enableDecoratorObfuscation) {
176      return;
177    }
178
179    const reservedNames = this.bytecodeObfuscateConfig.getReservedNames();
180
181    for (const item of [...UnobfuscationCollections.reservedStruct]) {
182      if (!reservedNames?.has(item)) {
183        UnobfuscationCollections.reservedStruct.delete(item);
184      }
185    }
186  }
187
188  public execute(moduleInfos: Map<String, ModuleInfo>): void {
189    if (!BytecodeObfuscator.enable) {
190      return;
191    }
192    this.collectSkipModuleName();
193    this.convertAbstractPathToRecordName(moduleInfos);
194    this.collectWhiteList();
195    const fileWhiteListsCachePath = path.join(this.obfuscationCacheDir, FILE_WHITE_LISTS);
196    this.collectPropertyBySourceScan(fileWhiteListsCachePath);
197    fs.writeFileSync(this.configPath, JSON.stringify(this.bytecodeObfuscateConfig, (key, value) => {
198      if (value instanceof Set) {
199        return Array.from(value);
200      }
201      return value;
202    }, 2));
203    this.generateObfCmd();
204    this.createWarnings();
205    const outPutABC: string = this.projectConfig.widgetCompile ? WIDGETS_ABC : MODULES_ABC;
206    const abcPath = path.join(this.projectConfig.aceModuleBuild, outPutABC);
207    //backup abc file
208    fs.copyFileSync(abcPath, this.bytecodeObfuscateConfig.abcFilePath);
209    const result = childProcess.spawnSync(this.arkConfig.bcObfuscatorPath, this.cmdArgs, {
210      env: { ...process.env }
211    });
212    if (result.status !== 0) {
213      const stderrString = result.stderr ? result.stderr.toString() : '';
214      const errInfo: LogData = LogDataFactory.newInstanceFromBytecodeObfuscation(stderrString, result.status);
215      BytecodeObfuscator.instance.logger.printErrorAndExit(errInfo);
216    }
217    fs.copyFileSync(this.bytecodeObfuscateConfig.obfAbcFilePath, abcPath);
218    if (this.projectConfig.widgetCompile) {
219      return;
220    }
221    this.updateAfterObfuscation();
222  }
223
224  private collectSkipModuleName(): void {
225    const matchedPaths = new Set<string>();
226
227    // collect keep bytecode har module pkgName
228    const regex = /\/oh_modules\/([^/]+)$/;
229    this.projectConfig.obfuscationMergedObConfig.keepSourceOfPaths.forEach(item => {
230      const match = item.match(regex);
231      if (match) {
232        matchedPaths.add(match[1]);
233      }
234    });
235    // collect remote har pkgName,which shouldn't to be obfuscated
236    this.projectConfig.byteCodeHarInfo &&
237      Object.entries(this.projectConfig.byteCodeHarInfo).forEach(([key, value]) => {
238        if (!isVersionCompatible(value?.compatibleSdkVersion, null) || !this.enhanced || matchedPaths.has(key)) {
239          this.bytecodeObfuscateConfig.addSkippedRemoteHarList(key);
240          this.projectConfig.byteCodeHarToDependencyKeys?.get(key)?.forEach(dependentModule => {
241            this.bytecodeObfuscateConfig.addSkippedRemoteHarList(dependentModule);
242          });
243        }
244      });
245  }
246
247  public convertAbstractPathToRecordName(moduleInfos: Map<String, ModuleInfo>): void {
248    if (this.bytecodeObfuscateConfig.obfuscationRules.keepOptions &&
249      this.bytecodeObfuscateConfig.obfuscationRules.keepOptions.keepPaths.size > 0) {
250      const convertedPath: Set<string> = new Set<string>();
251      this.bytecodeObfuscateConfig.obfuscationRules.keepOptions.keepPaths.forEach(path => {
252        if (moduleInfos.get(path)) {
253          convertedPath.add(moduleInfos.get(path).recordName);
254          this.bytecodeObfuscateConfig.removeKeepPaths(path);
255        }
256      });
257      this.bytecodeObfuscateConfig.addKeepPaths(convertedPath);
258    }
259  }
260
261  public generateObfCmd(): void {
262    if (this.debugging) {
263      this.cmdArgs.push('--debug');
264      this.cmdArgs.push('--debug-file');
265      const defaultDebugLogPath: string = path.join(this.obfuscationCacheDir, 'debug.log');
266      if (!fs.existsSync(defaultDebugLogPath)) {
267        writeFileSync(defaultDebugLogPath, '');
268      }
269      this.cmdArgs.push(defaultDebugLogPath);
270    }
271    this.cmdArgs.push(this.configPath);
272  }
273
274  private createWarnings(): void {
275    this.logger.warn(
276      yellow,
277      'ArkTS:WARN When enabling bytecode obfuscation, the following exceptions may occur:\n' +
278      '1. When enabling fileName, export, and property obfuscation, and a HAR is depended on by both HAP and HSP,\n' +
279      'this scenario may result in the error:' +
280      '   \"The requested module \'xxxx\' does not provide an export name \'x\' which is imported by \'xxxxx\'.\"'
281    );
282
283    if (this.enhanced) {
284      this.logger.warn(
285        yellow,
286        'ArkTS:WARN When enabling the enhanced option, the following exceptions may occur:\n' +
287        '1. When enabling export and property obfuscation, and a HAR is depended on by both HAP and HSP,\n' +
288        '   this scenario may result in the error:' +
289        '   \"The requested module \'xxxx\' does not provide an export name \'x\' which is imported by \'xxxxx\'.\"'
290      );
291    }
292  }
293
294  private updateAfterObfuscation(): void {
295    let nameCache: Object;
296    let sourceMap: Object;
297
298    this.nameCachePath = this.bytecodeObfuscateConfig.obfuscationRules.printNameCache ||
299      this.bytecodeObfuscateConfig.defaultNameCachePath;
300
301    const sourceMapPath = path.join(this.projectConfig.cachePath, SOURCEMAPS);
302    const souceMapJsonPath = path.join(this.projectConfig.cachePath, SOURCEMAPS_JSON);
303    nameCache = JSON.parse(fs.readFileSync(this.nameCachePath, 'utf-8'));
304    sourceMap = JSON.parse(fs.readFileSync(sourceMapPath, 'utf-8'));
305    //copy origin sourceMaps for Incremental compilation
306    if (fs.existsSync(souceMapJsonPath)) {
307      fs.copyFileSync(souceMapJsonPath, this.backupSourceMapPath);
308    }
309    const nameMapping: Map<string, string> = generateMapping(nameCache);
310    const sourceMapUpdated: Object = updateSourceMap(sourceMap, nameMapping);
311    fs.writeFileSync(sourceMapPath, JSON.stringify(sourceMapUpdated, null, 2));
312    fs.writeFileSync(souceMapJsonPath, SourceMapGenerator.getInstance().convertSourceMapToCache(sourceMapUpdated));
313    const nameCacheUpdated: Object = this.bytecodeObfuscateConfig.obfuscationRules.compact ?
314      nameCache : processNameCache(nameCache, sourceMap);
315    fs.writeFileSync(this.nameCachePath, JSON.stringify(nameCacheUpdated, null, 2));
316  }
317
318  private collectWhiteList(): void {
319    this.bytecodeObfuscateConfig.addReservedNames(ES_ANNOTATIONS);
320    this.bytecodeObfuscateConfig.addReservedProperties(BYTECODE_OBFUSCATION_PROPERTY_WHITE_LIST);
321  }
322
323  public getBackupSourceMapPath(): string {
324    return this ? this.backupSourceMapPath : '';
325  }
326
327  public setKeepSourceOfPaths(keepPath: Set<string>): void {
328    this.bytecodeObfuscateConfig.addKeepPaths(keepPath);
329  }
330
331  public isDecoratorObfuscationEnabled(): boolean {
332    return this.bytecodeObfuscateConfig?.obfuscationRules?.enableDecoratorObfuscation;
333  }
334
335  private getSystemApiConfigsByCache(): void {
336    const uniqueNames = new Set([
337      ...(UnobfuscationCollections.reservedSdkApiForLocal || []),
338      ...(UnobfuscationCollections.reservedSdkApiForGlobal || []),
339      ...(UnobfuscationCollections.reservedSdkApiForProp || [])
340    ]);
341    this.bytecodeObfuscateConfig.addReservedNames(uniqueNames);
342  }
343
344  collectPropertyBySourceScan(fileWhiteListsCachePath: string): void {
345    const fileContent = fs.readFileSync(fileWhiteListsCachePath, 'utf8');
346    const parsed: Object = JSON.parse(fileContent);
347    const allValues: string[] = [];
348
349    Object.values(parsed).forEach(fileData => {
350      this.processFileKeepInfo(fileData.fileKeepInfo, allValues);
351      this.processFileReservedInfo(fileData.fileReservedInfo, allValues);
352      // only collect decorator when decorator Obfuscation is disabled
353      if (!this.isDecoratorObfuscationEnabled()) {
354        this.processDecoratorMap(fileData.bytecodeObfuscateKeepInfo?.decoratorMap);
355      }
356    });
357
358    this.bytecodeObfuscateConfig.addReservedNames(allValues);
359  }
360
361  private processFileKeepInfo(fileKeepInfo: FileKeepInfo, allValues: string[]): void {
362    if (!fileKeepInfo) {
363      return;
364    }
365
366    let {
367      keepSymbol,
368      keepAsConsumer,
369      structProperties,
370      enumProperties,
371      stringProperties,
372      exported,
373    } = fileKeepInfo;
374
375    const rules = this.bytecodeObfuscateConfig.obfuscationRules;
376    if (rules.enableDecoratorObfuscation) {
377      const reservedNames = this.bytecodeObfuscateConfig.getReservedNames();
378      structProperties = new Set(
379        Array.from(structProperties || []).filter(item => reservedNames?.has(item))
380      );
381    }
382
383    const arrayProperties = [
384      keepSymbol?.propertyNames,
385      keepSymbol?.globalNames,
386      keepAsConsumer?.propertyNames,
387      keepAsConsumer?.globalNames,
388      exported?.propertyNames,
389      exported?.globalNames,
390      structProperties,
391      enumProperties,
392      stringProperties
393    ];
394
395    arrayProperties.forEach(arr => {
396      if (arr) {
397        allValues.push(...arr);
398      }
399    });
400  }
401
402  private processFileReservedInfo(fileReservedInfo: FileReservedInfo, allValues: string[]): void {
403    if (!fileReservedInfo) {
404      return;
405    }
406
407    const { enumProperties, propertyParams } = fileReservedInfo;
408
409    [enumProperties, propertyParams].forEach(props => {
410      if (props) {
411        allValues.push(...props);
412      }
413    });
414  }
415
416  private processDecoratorMap(decoratorMap: Object): void {
417    if (!decoratorMap) {
418      return;
419    }
420    for (const [decoratorName, propertyList] of Object.entries(decoratorMap)) {
421      const processedList = new Set(
422        [...propertyList].flatMap(name =>
423          name.includes('.') ? name.split('.') : [name]
424        )
425      );
426
427      this.bytecodeObfuscateConfig.addReservedNames(processedList);
428      processedList.forEach(name => {
429        UnobfuscationCollections.reservedSdkApiForGlobal.add(name);
430      });
431    }
432  }
433}
434
435export class BytecodeObfuscationConfig {
436  abcFilePath: string;
437  obfAbcFilePath: string;
438  obfPaFilePath: string;
439  compileSdkVersion: string;
440  targetApiVersion: number;
441  targetApiSubVersion: string;
442  filesInfoPath: string;
443  entryPackageInfo: string;
444  sourceMapsPath: string;
445  defaultNameCachePath: string;
446  skippedRemoteHarList: Set<string>;
447  useNormalizedOHMUrl: boolean;
448  obfuscationRules: {
449    disableObfuscation: boolean;
450    compact: boolean;
451    enableExportObfuscation: boolean;
452    enableRemoveLog: boolean;
453    enableDecoratorObfuscation: boolean;
454    printNameCache: string;
455    applyNameCache: string;
456    reservedNames: Set<string>;
457    propertyObfuscation: {
458      enable: boolean;
459      reservedProperties: Set<string>;
460      universalReservedProperties: Set<string>;
461    };
462    toplevelObfuscation: {
463      enable: boolean;
464      reservedToplevelNames: Set<string>;
465      universalReservedToplevelNames: Set<string>;
466    };
467    fileNameObfuscation: {
468      enable: boolean;
469      reservedFileNames: Set<string>;
470      universalReservedFileNames: Set<string>;
471      reservedRemoteHarPkgNames: Set<string>;
472    };
473    keepOptions: {
474      enable: boolean;
475      keepPaths: Set<string>;
476    };
477  };
478
479  constructor(share: Object, arkProjectConfig: Object) {
480    const mergedObConfig: MergedConfig = arkProjectConfig.obfuscationMergedObConfig;
481    const obfuscationCacheDir: string = share.projectConfig.obfuscationOptions.obfuscationCacheDir;
482    const originDir: string = path.join(obfuscationCacheDir, 'origin');
483    const obfDir: string = path.join(obfuscationCacheDir, 'obf');
484    const outPutABC: string = share.projectConfig.widgetCompile ? WIDGETS_ABC : MODULES_ABC;
485    this.abcFilePath = path.join(originDir, outPutABC);
486    this.obfAbcFilePath = path.join(obfDir, outPutABC);
487    this.targetApiVersion = share.projectConfig.compatibleSdkVersion;
488    this.targetApiSubVersion = share.projectConfig.compatibleSdkVersionStage;
489    this.filesInfoPath = path.join(share.projectConfig.cachePath, FILESINFO_TXT);
490    this.sourceMapsPath = path.join(share.projectConfig.cachePath, SOURCEMAPS);
491    this.useNormalizedOHMUrl = share.projectConfig.useNormalizedOHMUrl;
492    this.compileSdkVersion = share.projectConfig.etsLoaderVersion;
493    this.entryPackageInfo = arkProjectConfig.entryPackageInfo;
494    this.defaultNameCachePath = path.join(obfuscationCacheDir, 'nameCache.json');
495    this.skippedRemoteHarList = new Set<string>();
496    if (mergedObConfig.options.bytecodeObf.debugging) {
497      const parsedPath = path.parse(this.obfAbcFilePath);
498      this.obfPaFilePath = path.join(parsedPath.dir, `${parsedPath.name}.pa`);
499    } else {
500      this.obfPaFilePath = '';
501    }
502    const { options, reservedNames, reservedPropertyNames, universalReservedPropertyNames,
503      reservedGlobalNames, universalReservedGlobalNames, reservedFileNames,
504      keepSourceOfPaths } = mergedObConfig;
505    const fileNameReservedInfo: ReservedNameInfo = processReservedFileNames(reservedFileNames);
506
507    this.obfuscationRules = {
508      disableObfuscation: false,
509      compact: options.compact,
510      enableExportObfuscation: options.enableExportObfuscation,
511      enableRemoveLog: options.removeLog,
512      enableDecoratorObfuscation: false,
513      printNameCache: options.printNameCache,
514      applyNameCache: options.applyNameCache,
515      reservedNames: new Set<string>(reservedNames),
516      propertyObfuscation: {
517        enable: options.enablePropertyObfuscation,
518        reservedProperties: new Set<string>(reservedPropertyNames),
519        universalReservedProperties: new Set(
520          universalReservedPropertyNames?.map(regexp => regexp.toString())
521        )
522      },
523      toplevelObfuscation: {
524        enable: options.enableToplevelObfuscation,
525        reservedToplevelNames: new Set<string>(reservedGlobalNames),
526        universalReservedToplevelNames: new Set(
527          universalReservedGlobalNames?.map(regexp => regexp.toString()) || []
528        )
529      },
530      fileNameObfuscation: {
531        enable: options.enableFileNameObfuscation,
532        reservedFileNames: new Set<string>(fileNameReservedInfo.specificReservedArray),
533        universalReservedFileNames: new Set(
534          fileNameReservedInfo.universalReservedArray?.map(regexp => regexp.toString()) || []
535        )
536        ,
537        reservedRemoteHarPkgNames: new Set<string>()
538      },
539      keepOptions: {
540        enable: keepSourceOfPaths.length > 0,
541        keepPaths: new Set<string>(keepSourceOfPaths),
542      }
543    };
544  }
545
546  getReservedNames(): Set<string> {
547    return this.obfuscationRules.reservedNames;
548  }
549
550  addToSet(set: Set<string>, values: string | string[] | Set<string>): void {
551    if (typeof values === 'string') {
552      set.add(values);
553    } else {
554      values.forEach(value => set.add(value));
555    }
556  }
557
558  addReservedProperties(values: string | string[] | Set<string>): void {
559    this.addToSet(this.obfuscationRules.propertyObfuscation.reservedProperties, values);
560  }
561
562  addReservedNames(values: string | string[] | Set<string>): void {
563    this.addToSet(this.obfuscationRules.reservedNames, values);
564  }
565
566  addSkippedRemoteHarList(values: string | string[] | Set<string>): void {
567    this.addToSet(this.skippedRemoteHarList, values);
568  }
569
570  addKeepPaths(values: string | string[] | Set<string>): void {
571    this.addToSet(this.obfuscationRules.keepOptions.keepPaths, values);
572  }
573
574  removeKeepPaths(values: string | string[] | Set<string>): void {
575    this.removeFromSet(this.obfuscationRules.keepOptions.keepPaths, values);
576  }
577
578  removeFromSet(
579    set: Set<string>,
580    values: string | string[] | Set<string>
581  ): void {
582    if (typeof values === 'string') {
583      set.delete(values);
584    } else {
585      values.forEach((value) => set.delete(value));
586    }
587  }
588}
589
590function processReservedFileNames(reservedFileNames: string[]): ReservedNameInfo {
591  const fileNameReservedInfo: ReservedNameInfo = separateUniversalReservedItem(reservedFileNames, false);
592  fileNameReservedInfo.specificReservedArray = fileNameReservedInfo.specificReservedArray.map(fileName => {
593    return fileName.replace(/^(?!\.)[^.]+\.[^.]+$/, match => {
594      return match.replace(/\.[^.]+$/, '');
595    });
596  });
597  return fileNameReservedInfo;
598}
599
600function collectReservedLangForProperty(): Set<string> {
601  let languageSet: Set<string> = new Set<string>;
602  for (const key of Object.keys(esInfo)) {
603    const valueArray = esInfo[key];
604    valueArray.forEach((element: string) => {
605      languageSet.add(element);
606    });
607  }
608  return languageSet;
609}
610
611function isVersionCompatible(
612  compatibleSdkVersion: number | null | undefined,
613  compatibleSdkVersionStage: string | null | undefined
614): boolean {
615  if (compatibleSdkVersion == null) {
616    return false;
617  }
618
619  if (compatibleSdkVersion >= 13) {
620    return true;
621  }
622
623  if (compatibleSdkVersion === 12) {
624    return compatibleSdkVersionStage === 'beta3';
625  }
626
627  return false;
628}
629
630function generateMapping(nameCache: Object): Map<string, string> {
631  const whiteList = new Set<string>([
632    FILE_NAME_CACHE,
633    OBF_NAME_MAP,
634    PROPERTY_CACHE,
635    ENTRY_PACKAGE_INFO,
636    COMPILE_SDK_VERSION
637  ]);
638  const obfSourceFileMapping = new Map<string, string>();
639
640  Object.entries(nameCache).forEach(([key, value]) => {
641    if (!whiteList.has(key)) {
642      const { ObfSourceFile: obfSourceFile, OriSourceFile: originSouceFile } = value;
643      if (obfSourceFile && originSouceFile) {
644        obfSourceFileMapping.set(originSouceFile, obfSourceFile);
645      }
646    }
647  });
648  return obfSourceFileMapping;
649}
650
651function updateSourceMap(sourceMap: Object, obfSourceFileMapping: Map<string, string>): Object {
652  const newSourceMap: Object = {};
653  Object.entries(sourceMap).forEach(([key, value]) => {
654    const obfSourceKey = obfSourceFileMapping.get(key) || key;
655    newSourceMap[obfSourceKey] = value;
656  });
657  return newSourceMap;
658}
659
660function processNameCache(nameCache: Object, sourceMap: Object): Object {
661  const nameCacheUpdated = {};
662  const entries = Object.entries(nameCache);
663  for (let i = 0; i < entries.length; i++) {
664    const [nameCacheKey, nameCacheValue] = entries[i];
665    processNameCacheEntry(nameCacheKey, nameCacheValue, sourceMap, nameCacheUpdated);
666  }
667  return nameCacheUpdated;
668}
669
670function processNameCacheEntry(nameCacheKey: string, nameCacheValue: Object, sourceMap: Object, nameCacheUpdated: Object): void {
671  if (!nameCacheValue.OriSourceFile) {
672    nameCacheUpdated[nameCacheKey] = nameCacheValue;
673    return;
674  }
675  const sourceMapKey = nameCacheValue.OriSourceFile;
676  const sourcemap = sourceMap[sourceMapKey];
677  if (!sourcemap) {
678    return;
679  }
680  const sourceFileName = sourcemap.sources?.length === 1 ? sourcemap.sources[0] : '';
681  const source = new Source(sourceFileName, null);
682  const decodedSourceMap = decodeSourcemap(sourcemap);
683  const sourceMapLink = new SourceMapLink(decodedSourceMap, [source]);
684
685  if (!nameCacheUpdated[nameCacheKey]) {
686    nameCacheUpdated[nameCacheKey] = {};
687  }
688  for (const [itemKey, itemValue] of Object.entries(nameCacheValue)) {
689    if (itemKey !== IDENTIFIER_CACHE && itemKey !== MEMBER_METHOD_CACHE) {
690      nameCacheUpdated[nameCacheKey][itemKey] = itemValue;
691      continue;
692    }
693    processItemKey(nameCacheKey, itemKey, itemValue, nameCacheUpdated, sourceMapLink);
694  }
695}
696/**
697
698 * @param itemKey IdentifierCache || MemberMethodCache
699 * @param itemValue
700 */
701function processItemKey(nameCacheKey, itemKey, itemValue, nameCacheUpdated, sourceMapLink): void {
702  if (!nameCacheUpdated[nameCacheKey][itemKey]) {
703    nameCacheUpdated[nameCacheKey][itemKey] = {};
704  }
705  /**
706   * key=>originName eg.#fun1:3:24
707   * value => obfuscated Name
708   */
709  for (const [key, value] of Object.entries(itemValue)) {
710    const [scopeName, oldStartLine, oldEndLine] = key.split(':');
711    const startPosition: SourceMapSegmentObj = sourceMapLink.traceSegment(Number(oldStartLine) - 1, 0, '');
712    const endPosition: SourceMapSegmentObj = sourceMapLink.traceSegment(
713      Number(oldEndLine) - 1, 0, '');
714    if (!startPosition || !endPosition) {
715      nameCacheUpdated[nameCacheKey][itemKey][key] = value;
716      continue;
717    }
718    const startLine = startPosition.line + 1;
719    const endLine = endPosition.line + 1;
720    const newKey = `${scopeName}:${startLine}:${endLine}`;
721    nameCacheUpdated[nameCacheKey][itemKey][newKey] = value;
722  }
723}