1/*
2 * Copyright (c) 2023 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use rollupObject 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
16// Execute this file first to avoid circular dependency problems
17
18import { expect } from 'chai';
19import mocha from 'mocha';
20import fs from "fs";
21import path from "path";
22import MagicString from 'magic-string';
23import sinon from 'sinon';
24
25import {
26  getBuildModeInLowerCase,
27  getPackageInfo,
28  genSourceMapFileName,
29  isOhModules,
30  isEs2Abc,
31  writeArkguardObfuscatedSourceCode,
32  writeMinimizedSourceCode,
33  tryMangleFileName
34} from '../../lib/ark_utils';
35import {
36  DEBUG,
37  RELEASE,
38  OH_MODULES,
39  EXTNAME_TS,
40  EXTNAME_JS,
41  EXTNAME_ETS,
42  OBFUSCATION_TOOL
43} from '../../lib/fast_build/ark_compiler/common/ark_define';
44import RollUpPluginMock from './mock/rollup_mock/rollup_plugin_mock';
45import {
46  BUNDLE_NAME_DEFAULT,
47  ENTRY_MODULE_NAME_DEFAULT,
48  EXTNAME_MAP,
49  ENTRYABILITY_JS
50} from './mock/rollup_mock/common';
51import projectConfig from './utils/processProjectConfig';
52import {
53  ES2ABC,
54  TS2ABC
55} from '../../lib/pre_define';
56import { changeFileExtension } from '../../lib/fast_build/ark_compiler/utils';
57import ModuleSourceFileMock from './mock/class_mock/module_source_files_mock';
58import {
59  genTemporaryPath,
60  toUnixPath
61} from '../../lib/utils';
62import {
63  ObConfigResolver,
64  MergedConfig
65} from '../../lib/fast_build/ark_compiler/common/ob_config_resolver';
66import {
67  utProcessArkConfig
68} from '../../lib/fast_build/ark_compiler/common/process_ark_config';
69import { ModuleSourceFile } from '../../lib/fast_build/ark_compiler/module/module_source_file';
70import { PROJECT_ROOT, TERSER_PROCESSED_EXPECTED_CODE } from './mock/rollup_mock/path_config';
71import { GEN_ABC_PLUGIN_NAME } from '../../lib/fast_build/ark_compiler/common/ark_define';
72import { SourceMapGenerator } from '../../lib/fast_build/ark_compiler/generate_sourcemap';
73import { ArkObfuscator } from 'arkguard';
74
75mocha.describe('test ark_utils file api', function () {
76  mocha.before(function () {
77    this.rollup = new RollUpPluginMock();
78  });
79
80  mocha.after(() => {
81    delete this.rollup;
82  });
83
84  mocha.it('1-1: test getBuildModeInLowerCase under build debug', function () {
85    this.rollup.build();
86    const buildMode = getBuildModeInLowerCase(this.rollup.share.projectConfig);
87    expect(buildMode === DEBUG).to.be.true;
88  });
89
90  mocha.it('1-2: test getBuildModeInLowerCase under build release', function () {
91    this.rollup.build(RELEASE);
92    const buildMode = getBuildModeInLowerCase(this.rollup.share.projectConfig);
93    expect(buildMode === RELEASE).to.be.true;
94  });
95
96  mocha.it('1-3: test getBuildModeInLowerCase under preview debug', function () {
97    this.rollup.preview();
98    const buildMode = getBuildModeInLowerCase(this.rollup.share.projectConfig);
99    expect(buildMode === DEBUG).to.be.true;
100  });
101
102  mocha.it('1-4: test getBuildModeInLowerCase under hot reload debug', function () {
103    this.rollup.hotReload();
104    const buildMode = getBuildModeInLowerCase(this.rollup.share.projectConfig);
105    expect(buildMode === DEBUG).to.be.true;
106  });
107
108  mocha.it('2-1: test getPackageInfo under build debug', function () {
109    this.rollup.build();
110    const returnInfo = getPackageInfo(this.rollup.share.projectConfig.aceModuleJsonPath);
111    expect(returnInfo[0] === BUNDLE_NAME_DEFAULT).to.be.true;
112    expect(returnInfo[1] === ENTRY_MODULE_NAME_DEFAULT).to.be.true;
113  });
114
115  mocha.it('2-2: test getPackageInfo under build release', function () {
116    this.rollup.build(RELEASE);
117    const returnInfo = getPackageInfo(this.rollup.share.projectConfig.aceModuleJsonPath);
118    expect(returnInfo[0] === BUNDLE_NAME_DEFAULT).to.be.true;
119    expect(returnInfo[1] === ENTRY_MODULE_NAME_DEFAULT).to.be.true;
120  });
121
122  mocha.it('2-3: test getPackageInfo under preview debug', function () {
123    this.rollup.preview();
124    const returnInfo = getPackageInfo(this.rollup.share.projectConfig.aceModuleJsonPath);
125    expect(returnInfo[0] === BUNDLE_NAME_DEFAULT).to.be.true;
126    expect(returnInfo[1] === ENTRY_MODULE_NAME_DEFAULT).to.be.true;
127  });
128
129  mocha.it('2-4: test getPackageInfo under hot reload debug', function () {
130    this.rollup.hotReload();
131    const returnInfo = getPackageInfo(this.rollup.share.projectConfig.aceModuleJsonPath);
132    expect(returnInfo[0] === BUNDLE_NAME_DEFAULT).to.be.true;
133    expect(returnInfo[1] === ENTRY_MODULE_NAME_DEFAULT).to.be.true;
134  });
135
136  mocha.it('3-1: test genSourceMapFileName under build debug', function () {
137    this.rollup.build();
138    for (const filePath of this.rollup.share.allFiles) {
139      if (filePath.endsWith(EXTNAME_TS) || filePath.endsWith(EXTNAME_JS)) {
140        const originPath = genSourceMapFileName(filePath);
141        const expectedPath = `${filePath}${EXTNAME_MAP}`;
142        expect(originPath === expectedPath).to.be.true;
143      } else if (filePath.endsWith(EXTNAME_ETS)) {
144        const originPath = genSourceMapFileName(filePath);
145        expect(originPath === filePath).to.be.true;
146      }
147    }
148  });
149
150  mocha.it('3-2: test genSourceMapFileName under build release', function () {
151    this.rollup.build(RELEASE);
152    for (const filePath of this.rollup.share.allFiles) {
153      if (filePath.endsWith(EXTNAME_TS) || filePath.endsWith(EXTNAME_JS)) {
154        const originPath = genSourceMapFileName(filePath);
155        const expectedPath = `${filePath}${EXTNAME_MAP}`;
156        expect(originPath === expectedPath).to.be.true;
157      }
158    }
159  });
160
161  mocha.it('3-3: test genSourceMapFileName under preview debug', function () {
162    this.rollup.preview();
163    for (const filePath of this.rollup.share.allFiles) {
164      if (filePath.endsWith(EXTNAME_TS) || filePath.endsWith(EXTNAME_JS)) {
165        const originPath = genSourceMapFileName(filePath);
166        const expectedPath = `${filePath}${EXTNAME_MAP}`;
167        expect(originPath === expectedPath).to.be.true;
168      }
169    }
170  });
171
172  mocha.it('3-4: test genSourceMapFileName under hot reload debug', function () {
173    this.rollup.hotReload();
174    for (const filePath of this.rollup.share.allFiles) {
175      if (filePath.endsWith(EXTNAME_TS) || filePath.endsWith(EXTNAME_JS)) {
176        const originPath = genSourceMapFileName(filePath);
177        const expectedPath = `${filePath}${EXTNAME_MAP}`;
178        expect(originPath === expectedPath).to.be.true;
179      }
180    }
181  });
182
183  mocha.it('4-1: test isOhModules under build debug', function () {
184    this.rollup.build();
185    const returnInfo = isOhModules(this.rollup.share.projectConfig);
186    expect(returnInfo === false).to.be.true;
187    this.rollup.share.projectConfig.packageDir = OH_MODULES;
188    const returnInfoOh = isOhModules(this.rollup.share.projectConfig);
189    expect(returnInfoOh).to.be.true;
190  });
191
192  mocha.it('4-2: test isOhModules under build release', function () {
193    this.rollup.build(RELEASE);
194    const returnInfo = isOhModules(this.rollup.share.projectConfig);
195    expect(returnInfo === false).to.be.true;
196    this.rollup.share.projectConfig.packageDir = OH_MODULES;
197    const returnInfoOh = isOhModules(this.rollup.share.projectConfig);
198    expect(returnInfoOh).to.be.true;
199  });
200
201  mocha.it('4-3: test isOhModules under preview debug', function () {
202    this.rollup.preview();
203    const returnInfo = isOhModules(this.rollup.share.projectConfig);
204    expect(returnInfo === false).to.be.true;
205    this.rollup.share.projectConfig.packageDir = OH_MODULES;
206    const returnInfoOh = isOhModules(this.rollup.share.projectConfig);
207    expect(returnInfoOh).to.be.true;
208  });
209
210  mocha.it('4-4: test isOhModules under hot reload debug', function () {
211    this.rollup.hotReload();
212    const returnInfo = isOhModules(this.rollup.share.projectConfig);
213    expect(returnInfo === false).to.be.true;
214    this.rollup.share.projectConfig.packageDir = OH_MODULES;
215    const returnInfoOh = isOhModules(this.rollup.share.projectConfig);
216    expect(returnInfoOh).to.be.true;
217  });
218
219  mocha.it('4-5: test isOhModules under hot fix debug', function () {
220    projectConfig.buildMode = DEBUG;
221    const returnInfo = isOhModules(projectConfig);
222    expect(returnInfo).to.be.true;
223  });
224
225  mocha.it('4-6: test isOhModules under hot fix release', function () {
226    projectConfig.buildMode = RELEASE;
227    const returnInfo = isOhModules(projectConfig);
228    expect(returnInfo).to.be.true;
229  });
230
231  mocha.it('5-1: test isEs2Abc under build debug', function () {
232    this.rollup.build();
233    this.rollup.share.projectConfig.pandaMode = ES2ABC;
234    const returnInfo = isEs2Abc(this.rollup.share.projectConfig);
235    expect(returnInfo).to.be.true;
236
237    this.rollup.share.projectConfig.pandaMode = TS2ABC;
238    const returnInfoTS2ABC = isEs2Abc(this.rollup.share.projectConfig);
239    expect(returnInfoTS2ABC === false).to.be.true;
240
241    this.rollup.share.projectConfig.pandaMode = "undefined";
242    const returnInfoUndef = isEs2Abc(this.rollup.share.projectConfig);
243    expect(returnInfoUndef).to.be.true;
244
245    this.rollup.share.projectConfig.pandaMode = undefined;
246    const returnInfoUndefined = isEs2Abc(this.rollup.share.projectConfig);
247    expect(returnInfoUndefined).to.be.true;
248  });
249
250  mocha.it('5-2: test isEs2Abc under build release', function () {
251    this.rollup.build(RELEASE);
252    this.rollup.share.projectConfig.pandaMode = ES2ABC;
253    const returnInfo = isEs2Abc(this.rollup.share.projectConfig);
254    expect(returnInfo).to.be.true;
255
256    this.rollup.share.projectConfig.pandaMode = TS2ABC;
257    const returnInfoTS2ABC = isEs2Abc(this.rollup.share.projectConfig);
258    expect(returnInfoTS2ABC).to.be.false;
259  });
260
261  mocha.it('5-3: test isEs2Abc under preview debug', function () {
262    this.rollup.preview();
263    this.rollup.share.projectConfig.pandaMode = ES2ABC;
264    const returnInfo = isEs2Abc(this.rollup.share.projectConfig);
265    expect(returnInfo).to.be.true;
266
267    this.rollup.share.projectConfig.pandaMode = TS2ABC;
268    const returnInfoTS2ABC = isEs2Abc(this.rollup.share.projectConfig);
269    expect(returnInfoTS2ABC).to.be.false;
270  });
271
272  mocha.it('5-4: test isEs2Abc under hot reload debug', function () {
273    this.rollup.hotReload();
274    this.rollup.share.projectConfig.pandaMode = ES2ABC;
275    const returnInfo = isEs2Abc(this.rollup.share.projectConfig);
276    expect(returnInfo).to.be.true;
277
278    this.rollup.share.projectConfig.pandaMode = TS2ABC;
279    const returnInfoTS2ABC = isEs2Abc(this.rollup.share.projectConfig);
280    expect(returnInfoTS2ABC).to.be.false;
281  });
282
283  mocha.it('5-5: test isEs2Abc under hot fix debug', function () {
284    projectConfig.buildMode = DEBUG;
285    projectConfig.pandaMode = ES2ABC;
286    const returnInfo = isEs2Abc(projectConfig);
287    expect(returnInfo).to.be.true;
288
289    projectConfig.pandaMode = TS2ABC;
290    const returnInfoTS2ABC = isEs2Abc(projectConfig);
291    expect(returnInfoTS2ABC).to.be.false;
292  });
293
294  mocha.it('5-6: test isEs2Abc under hot fix release', function () {
295    projectConfig.buildMode = RELEASE;
296    projectConfig.pandaMode = ES2ABC;
297    const returnInfo = isEs2Abc(projectConfig);
298    expect(returnInfo).to.be.true;
299
300    projectConfig.pandaMode = TS2ABC;
301    const returnInfoTS2ABC = isEs2Abc(projectConfig);
302    expect(returnInfoTS2ABC).to.be.false;
303  });
304
305  mocha.it('6-1: test the error message of writeArkguardObfuscatedSourceCode', async function () {
306    this.rollup.build(RELEASE);
307    SourceMapGenerator.initInstance(this.rollup);
308    const logger = this.rollup.share.getLogger(GEN_ABC_PLUGIN_NAME);
309    const stub = sinon.stub(logger, 'error');
310    const red: string = '\x1B[31m';
311    try {
312      await writeArkguardObfuscatedSourceCode(
313        {content: undefined, buildFilePath: '', relativeSourceFilePath: '', originSourceFilePath: ''},
314        logger, this.rollup.share.projectConfig, {});
315    } catch (e) {
316    }
317    expect(stub.calledWith(red,
318      `ArkTS:INTERNAL ERROR: Failed to obfuscate file '' with arkguard. TypeError: Cannot read properties of undefined (reading 'obfuscate')`
319    )).to.be.true;
320    stub.restore();
321    SourceMapGenerator.cleanSourceMapObject();
322  });
323
324  mocha.it('7-1: test the error message of writeMinimizedSourceCode', async function () {
325    this.rollup.build(RELEASE);
326    const logger = this.rollup.share.getLogger(GEN_ABC_PLUGIN_NAME);
327    const stub = sinon.stub(logger, 'error');
328    const red: string = '\x1B[31m';
329    const reset: string = '\x1B[39m';
330    try {
331      await writeMinimizedSourceCode(undefined, '', logger);
332    } catch (e) {
333    }
334    expect(stub.calledWith(red,
335      'ArkTS:INTERNAL ERROR: Failed to obfuscate source code for ', reset
336    )).to.be.true;
337    stub.restore();
338  });
339
340  mocha.it('8-1: test tryMangleFileName when obfuscation is disabled', async function () {
341    const filePath = '/mnt/application/entry/build/default/cache/default/default@CompileArkTS/esmodule/release/src/main/ets/entryability/EntryAbility.ts';
342    const originalFilePath = '/mnt/application/entry/src/main/ets/entryability/EntryAbility.ets';
343    const projectConfig = {
344      projectRootPath: '/mnt/application',
345      packageDir: 'oh_modules',
346      modulePathMap: {
347        entry: '/mnt/application/entry'
348      },
349      obfuscationMergedObConfig: {
350        options: {
351          enableFileNameObfuscation: false
352        }
353      }
354    }
355    const result = tryMangleFileName(filePath, projectConfig, originalFilePath);
356    expect(result === filePath).to.be.true;
357  });
358
359  mocha.it('8-2: test tryMangleFileName when obfuscation is enabled', async function () {
360    const filePath = '/mnt/application/entry/build/default/cache/default/default@CompileArkTS/esmodule/release/src/main/ets/entryability/EntryAbility.ts';
361    const originalFilePath = '/mnt/application/entry/src/main/ets/entryability/EntryAbility.ets';
362    const newFilePath = '/mnt/application/entry/build/default/cache/default/default@CompileArkTS/esmodule/release/src/main/ets/a/b.ts';
363    const projectConfig = {
364      projectRootPath: '/mnt/application',
365      packageDir: 'oh_modules',
366      modulePathMap: {
367        entry: '/mnt/application/entry'
368      },
369      obfuscationMergedObConfig: {
370        options: {
371          enableFileNameObfuscation: true
372        }
373      }
374    }
375
376    const printerConfig = {
377      //Print obfuscation time&memory usage of all files and obfuscation processes
378      mFilesPrinter: false,
379      //Print time&memory usage of a single file obfuscation in transform processes
380      mSingleFilePrinter: false,
381      //Print sum up time of transform processes during obfuscation
382      mSumPrinter: false,
383      //Output path of printer
384      mOutputPath: "" 
385    }
386
387    const arkguardConfig = {
388      mRenameFileName: {
389        mEnable: true,
390        mNameGeneratorType: 1,
391        mReservedFileNames: [
392          'entry',
393          'mnt',
394          'application',
395          'entry',
396          'src',
397          'main',
398          'ets',
399          'build',
400          'default',
401          'cache',
402          'default',
403          'default@CompileArkTS',
404          'esmodule',
405          'release'
406        ],
407      },
408      mPerformancePrinter: printerConfig
409    }
410
411    let arkObfuscator: ArkObfuscator = new ArkObfuscator();
412    arkObfuscator.init(arkguardConfig);
413    const content = `function foo() {}`
414    const obfuscateResult = arkObfuscator.obfuscate(content, filePath, undefined);
415    obfuscateResult.then(result => {
416      const afterObfuscateFilePath = result.filePath;
417      expect(afterObfuscateFilePath === newFilePath).to.be.true;
418    })
419
420    const result = tryMangleFileName(filePath, projectConfig, originalFilePath);
421    expect(result === newFilePath).to.be.true;
422  });
423
424  mocha.it('9-1: test writeArkguardObfuscatedSourceCode when obfuscation is enabled', async function () {
425    this.rollup.build(RELEASE);
426    const logger = this.rollup.share.getLogger(GEN_ABC_PLUGIN_NAME);
427    const arkguardConfig = {
428      mCompact: false,
429      mDisableConsole: false,
430      mSimplify: false,
431      mRemoveComments: true,
432      mNameObfuscation: {
433        mEnable: true,
434        mNameGeneratorType: 1,
435        mReservedNames: [],
436        mRenameProperties: false,
437        mReservedProperties: [],
438        mKeepStringProperty: true,
439        mTopLevel: false,
440        mReservedToplevelNames: [],
441        mUniversalReservedProperties: [],
442        mUniversalReservedToplevelNames: [],
443      },
444      mRemoveDeclarationComments: {
445        mEnable: true,
446        mReservedComments: []
447      },
448      mEnableSourceMap: true,
449      mEnableNameCache: true,
450      mRenameFileName: {
451        mEnable: false,
452        mNameGeneratorType: 1,
453        mReservedFileNames: [],
454      },
455      mExportObfuscation: false,
456      mPerformancePrinter: {
457        mFilesPrinter: false,
458        mSingleFilePrinter: false,
459        mSumPrinter: false,
460        mOutputPath: ""
461      },
462      mKeepFileSourceCode: {
463        mKeepSourceOfPaths: new Set(),
464        mkeepFilesAndDependencies: new Set(),
465      },
466    };
467
468    const sourceMapGenerator: SourceMapGenerator = SourceMapGenerator.initInstance(this.rollup);
469    sourceMapGenerator.setNewSoureMaps(false);
470    const arkObfuscator: ArkObfuscator = new ArkObfuscator();
471    arkObfuscator.init(arkguardConfig);
472
473    const projectConfig = {
474      projectRootPath: PROJECT_ROOT,
475      arkObfuscator: arkObfuscator,
476      packageDir: 'oh_modules',
477      localPackageSet: new Set(),
478      useTsHar: false,
479      useNormalized: false
480    };
481
482    const sourceFileName = 'sourceFile.ts';
483    const originalSourceFilePath = path.join(__dirname, '../../test/ark_compiler_ut/testdata/har_obfuscation', sourceFileName);
484    const moduleInfo = {
485      content: fs.readFileSync(originalSourceFilePath, 'utf-8'),
486      buildFilePath: `${PROJECT_ROOT}/har_obfuscation/build/default/cache/${sourceFileName}`,
487      relativeSourceFilePath: `har_obfuscation/${sourceFileName}`,
488      originSourceFilePath: originalSourceFilePath,
489      rollupModuleId: originalSourceFilePath
490    };
491
492    try {
493      await writeArkguardObfuscatedSourceCode(moduleInfo, logger, projectConfig, {});
494    } catch (e) {
495    }
496    const expectedResult = `export function add(d: number, e: number): number {
497    return d + e;
498}
499export function findElement<a>(b: a[], c: (item: a) => boolean): a | undefined {
500    return b.find(c);
501}
502`;
503    const result = fs.readFileSync(moduleInfo.buildFilePath, 'utf-8');
504    expect(result === expectedResult).to.be.true;
505
506    const declFileName = 'sourceFile.d.ts';
507    const originalDeclFilePath = path.join(__dirname, '../../test/ark_compiler_ut/testdata/har_obfuscation/', declFileName);
508
509    const declModuleInfo = {
510      content: fs.readFileSync(originalDeclFilePath, 'utf-8'),
511      buildFilePath: `${PROJECT_ROOT}/har_obfuscation/build/default/cache/${declFileName}`,
512      relativeSourceFilePath: `har_obfuscation/build/default/cache/${declFileName}`,
513      originSourceFilePath: originalSourceFilePath,
514      rollupModuleId: originalSourceFilePath
515    };
516
517    try {
518      await writeArkguardObfuscatedSourceCode(declModuleInfo, logger, projectConfig, {});
519    } catch (e) {
520    }
521
522    const expectedDeclResult = `export declare function add(d: number, e: number): number;
523export declare function findElement<a>(b: a[], c: (item: a) => boolean): a | undefined;
524`;
525    const declResult = fs.readFileSync(declModuleInfo.buildFilePath, 'utf-8');
526    expect(declResult === expectedDeclResult).to.be.true;
527  });
528});