1# ArkGuard源码混淆工具
2
3## 代码混淆简介
4
5针对工程源码的混淆可以降低工程被破解攻击的风险,缩短代码的类与成员的名称,减小应用的大小。
6
7>**说明:**
8>
9> 1. 在 DevEco Studio5.0.3.600之前,新建工程的默认设置是开启代码混淆功能,它会自动对 API10及更高版本的 Stage 模型进行代码混淆。此操作仅适用于以[release模式](#说明)编译的代码,并且混淆仅限于参数名和局部变量名。
10> 2. 在 DevEco Studio5.0.3.600及之后,新建工程的默认设置已更改为关闭代码混淆功能。如果开发者希望开启代码混淆,需要将模块的`build-profile.json5`文件中的`ruleOptions.enable`字段的值设置为 true。此外,混淆规则配置文件`obfuscation-rules.txt`默认开启了四项推荐的混淆选项:`-enable-property-obfuscation`、`-enable-toplevel-obfuscation`、`-enable-filename-obfuscation`和`-enable-export-obfuscation`,开发者可以根据需要进一步修改混淆配置。需要注意的是,开启这四项规则可能会导致应用在运行时崩溃,因此建议开发者参考[开启指导](#代码混淆开启指导)来修正应用功能。
11
12### 使用约束
13
14* 仅支持Stage工程
15* 编译模式为release
16
17### 混淆范围
18
19在应用工程中,代码混淆支持以下格式文件混淆,混淆后的缓存文件保存在模块目录下的build/[...]/release目录下。
20
21* ArkTS文件
22* TS文件
23* JS文件
24
25### 局限性
26
271.语言的限制
28
29代码混淆工具在处理不同编程语言时,其类型分析机制、混淆策略和执行效率都会因目标语言的特性而呈现差异。以业界常用的ProGuard为例,其主要面向Java这类强类型语言进行混淆。由于强类型语言具有严格的类型系统,每个类型都有明确的定义来源。这种特性使得混淆过程中的类型关系追踪和处理更为精确,从而大幅减少了需要配置保留规则的场景。
30
31相比之下,Arkguard混淆工具主要针对JS、TS和ArkTS语言。JS支持运行时动态修改对象、函数,而混淆是在编译阶段进行的静态处理,这种差异可能导致混淆后的名称在运行时无法被正确解析,进而引发运行时异常。TS和ArkTS虽然引入了静态类型系统,但采用了结构性类型机制,即具有相同结构的不同命名类型会被视为等价类型。因此,在TS和ArkTS中仍然无法追溯类型的确切来源。基于这些特性,使用Arkguard时需要对更多的语法场景进行白名单配置,同时,Arkguard采用全局生效的属性保留机制,根据白名单统一保留所有同名属性,而无法支持针对特性类型的精确保留配置。
32
33具体而言,可以参考以下示例:
34
35假设Arkguard支持配置指定类型的白名单,配置类A1作为白名单,类A1的属性prop1在白名单中,而A2中的prop1属性不在白名单中。此时,a2作为参数被传入test函数中,调用prop1属性时会导致功能异常。
36
37```typescript
38// 混淆前
39class A1 {
40  prop1: string = '';
41}
42
43class A2 {
44  prop1: string = '';
45}
46
47function test(input: A1) {
48  console.log(input.prop1);
49}
50
51let a2 = new A2();
52a2.prop1 = 'prop a2';
53test(a2);
54```
55
56```typescript
57// 混淆后
58class A1 {
59  prop1: string = '';
60}
61
62class A2 {
63  a: string = '';
64}
65
66function test(input: A1) {
67  console.log(input.prop1);
68}
69
70let a2 = new A2();
71a2.a = 'prop a2';
72test(a2);
73```
74
75综上所述,开发者应了解这种语言差异带来的混淆效果差异,并尽量使用不重复的名称,以使在各种场景下的混淆效果更好。
76
772.安全保证的有限性
78
79与其他代码混淆工具一样,混淆只能在一定程度上增加逆向过程的难度,并不能真正阻止逆向工程。
80
81并且,由于ArkGuard混淆工具仅支持基础混淆能力,开发者不应只依赖ArkGuard来保证应用的安全性,对于源码安全有高要求的开发者,应考虑使用[应用加密](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/code-protect-V5)、第三方安全加固等安全措施来保护代码。
82
83## 开启代码混淆
84
85### 开启混淆步骤
86代码混淆能力已在系统中集成,可通过以下方式在DevEco Studio开启使用。
87
88* 开启混淆开关
89在本模块`build-profile.json5`配置文件中的`arkOptions.obfuscation.ruleOptions`字段中,通过`enable`字段配置是否开启混淆。使用不同版本的DevEco Studio,`enable`字段的默认值可能会有所不同,具体可以参考[版本变更说明](#代码混淆简介)。
90    ```
91    "arkOptions": {
92      "obfuscation": {
93        "ruleOptions": {
94          "enable": true,
95          "files": ["./obfuscation-rules.txt"],
96        }
97      }
98    }
99    ```
100
101* 配置混淆规则
102打开混淆开关,仅开启默认混淆功能,默认混淆范围为局部变量和参数。若需要开启更多混淆功能,需要在`files`字段对应的混淆配置文件`obfuscation-rules.txt`中进行选项配置。使用不同版本的DevEco Studio,`obfuscation-rules.txt`文件中的默认值可能会有所不同,具体可以参考[版本变更说明](#代码混淆简介)。
103
104* 指定release编译
105代码混淆当前仅支持release编译,不支持debug编译。即开启混淆开关后,若为release编译则会进行混淆,若为debug编译则不会进行混淆。开发者可参考[指定构建模式](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/ide-hvigor-compilation-options-customizing-guide-V5#section192461528194916)查看和修改构建模式。
106
107    > **注意:**
108    >
109    > release编译与debug编译的区别并不只包含混淆,若需要明确应用行为差异是否由于混淆,应该通过开启或关闭混淆开关排查,而不是仅通过切换release或debug编译来区分。
110
111### 三种混淆配置文件
112* `obfuscation-rules.txt`
113不论是HAP、HAR还是HSP,在本模块的`build-profile.json5`配置文件中都有`arkOptions.obfuscation.ruleOptions.files`字段,用于指定在编译本模块时需要生效的混淆规则,新建工程时会创建默认文件`obfuscation-rules.txt`。
114
115* `consumer-rules.txt`
116对于HAR模块,在`build-profile.json5`中额外有一个`arkOptions.obfuscation.consumerFiles`字段,**用于指定当本包被依赖时,期望在其他模块生效的混淆规则**,新建HAR模块时会创建默认文件`consumer-rules.txt`。它与`obfuscation-rules`字段的区别是:**`obfuscation-rules`在编译本模块时生效,`consumer-rules`在编译依赖本模块的其他模块时生效**。
117    ```
118    "arkOptions": {
119      "obfuscation": {
120        "ruleOptions": {
121          "enable": true,
122          "files": ["./obfuscation-rules.txt"],
123        }
124        "consumerFiles": ["./consumer-rules.txt"]
125      }
126    }
127    ```
128
129* `obfuscation.txt`
130不同于以上两种开发者可自行修改的配置文件,`obfuscation.txt`是在编译构建HAR时根据`consumer-rules.txt`和依赖模块的混淆规则文件自动生成的文件,它作为一种编译产物存在于发布的HAR包中,用于在其他应用使用该发布包时应用相应的混淆规则。obfuscation.txt内容的生成逻辑请参考[混淆规则合并策略](#混淆规则合并策略)。
131
132下表简要总结了三种配置文件的差异:
133
134| 配置文件(示例) | 配置类型 |  是否可修改配置  |  是否影响本模块的混淆  |  是否影响其他模块的混淆  |
135| --- | --- | --- | --- | --- |
136| obfuscation-rules.txt | 自定义  | 是 | 是 | 否 |
137| consumer-rules.txt    | 自定义  | 是 | 否 | 是 |
138| obfuscation.txt       | 编译产物 | 不涉及,构建HAR时自动生成 | 不涉及 | 是 |
139
140## 混淆规则
141
142混淆规则分为两种类型,一种是[混淆选项](#混淆选项),一种是[保留选项](#保留选项);前者是提供顶层作用域名称、属性名称、文件名称等多种混淆功能配置开关,后者是提供各种混淆功能的白名单配置能力。
143
144**注意**
145
146若修改应用的混淆配置文件,新配置需要重新全量编译才能生效。
147
148### 混淆选项
149
150#### -disable-obfuscation
151
152关闭所有混淆。如果使用这个选项,那么构建出来的HAP、HSP或HAR将不会被混淆。
153
154#### -enable-property-obfuscation
155
156开启属性混淆。 如果使用这个选项,那么所有的属性名都会被混淆,除了下面场景:
157
158* 被`import/export`直接导入或导出的类、对象的属性名不会被混淆。例如下面例子中的属性名`data`不会被混淆。
159
160    ```
161    export class MyClass {
162       data: string;
163    }
164    ```
165
166* ArkUI组件中的属性名不会被混淆。例如下面例子中的`message`和`data`不会被混淆。
167
168    ```
169    @Component struct MyExample {
170        @State message: string = "hello";
171        data: number[] = [];
172        // ...
173    }
174    ```
175
176* 被[保留选项](#保留选项)指定的属性名不会被混淆。
177* SDK API列表中的属性名不会被混淆。SDK API列表是构建时从SDK中自动提取出来的一个名称列表,其缓存文件为systemApiCache.json,路径为工程目录下build/default/cache/{...}/release/obfuscation中。
178* 字符串字面量属性名不会被混淆。例如下面例子中的`"name"`和`"age"`不会被混淆。
179
180    ```
181    let person = {"name": "abc"};
182    person["age"] = 22;
183    ```
184
185    如果想混淆字符串字面量属性名,需要在该选项的基础上再使用`-enable-string-property-obfuscation`选项。例如:
186
187    ```
188    -enable-property-obfuscation
189    -enable-string-property-obfuscation
190    ```
191
192    **注意**:
193
194    **1.** 如果代码里面有字符串属性名包含特殊字符(除了`a-z,A-Z,0-9,_`之外的字符),例如`let obj = {"\n": 123, "": 4, " ": 5}`,建议不要开启`-enable-string-property-obfuscation`选项,因为可能无法通过[保留选项](#保留选项)来指定保留这些名字。
195    **2.** SDK API的属性白名单中不包含声明文件中使用的字符串常量值,例如示例中的字符串'ohos.want.action.home'未包含在属性白名单中。
196
197    ```
198    // SDK API文件@ohos.app.ability.wantConstant片段:
199    export enum Params {
200      ACTION_HOME = 'ohos.want.action.home'
201    }
202    // 开发者源码示例:
203    let params = obj['ohos.want.action.home'];
204    ```
205
206    因此在开启了`-enable-string-property-obfuscation`选项时,如果想保留代码中使用的SDK API字符串常量的属性不被混淆,例如obj['ohos.want.action.home'],那么需要使用keep选项保留。
207
208#### -enable-toplevel-obfuscation
209
210开启顶层作用域名称混淆。如果使用这个选项,那么所有的顶层作用域的名称都会被混淆,除了下面场景:
211
212* 被`import/export`的名称不会被混淆。
213* 当前文件找不到声明的名称不会被混淆。
214* 被[保留选项](#保留选项)指定的顶层作用域名称不会被混淆。
215* SDK API列表中的顶层作用域名称不会被混淆。
216
217#### -enable-filename-obfuscation
218
219开启文件/文件夹名称混淆。如果使用这个选项,那么所有的文件/文件夹名称都会被混淆,例如:
220
221```
222// directory和filename都会混淆
223import func from '../directory/filename';
224import { foo } from '../directory/filename';
225const module = import('../directory/filename');
226```
227
228除了下面场景:
229
230* oh-package.json5文件中'main'、'types'字段配置的文件/文件夹名称不会被混淆。
231* 模块内module.json5文件中'srcEntry'字段配置的文件/文件夹名称不会被混淆。
232* 被[-keep-file-name](#保留选项)指定的文件/文件夹名称不会被混淆。
233* 非ECMAScript模块引用方式(例如:`const module = require('./module')`)。
234* 非路径引用方式,例如例子中的json5不会被混淆 `import module from 'json5'`。
235
236**注意**:
237
238由于系统会在应用运行时加载某些指定的文件,针对这类文件,开发者需要手动在[-keep-file-name](#保留选项)选项中配置相应的白名单,防止指定文件被混淆,导致运行失败。
239
240编译入口、Ability组件、Worker多线程,这三种不能混淆的文件名在DevEco Studio 5.0.3.500版本已被自动收集进白名单中,无需再手动配置,其它不能混淆文件名的场景仍需开发者手动配置。
241
242#### -enable-export-obfuscation
243
244开启直接导入或导出的类或对象的名称和属性名混淆。如果使用这个选项,那么模块中的直接导入或导出的名称都会被混淆,除了下面场景:
245
246* 远程HAR(真实路径在oh_modules中的包)中导出的类或对象的名称和属性名不会被混淆。
247* 被[保留选项](#保留选项)指定的名称与属性名不会被混淆。
248* SDK API列表中的名称不会被混淆。
249
250**注意**:
251
2521. 混淆导入或导出的类中属性名称需要同时开启`-enable-property-obfuscation`与`-enable-export-obfuscation`选项。
2532. 编译HSP时,如果开启`-enable-export-obfuscation`选项,需要在模块中的混淆配置文件`obfuscation-rules.txt`中保留对外暴露的接口。
2543. HAP/HSP/HAR依赖HSP场景下,编译时如果开启`-enable-export-obfuscation`选项,需要在模块中的混淆配置文件`obfuscation-rules.txt`中保留HSP导入的接口。
255
256    ```
257    // 代码示例(HSP中入口文件Index.ets):
258    export { add, customApiName } from './src/main/ets/utils/Calc'
259
260    // 保留接口名称配置示例:
261    // HSP以及依赖此HSP的模块中obfuscation-rules.txt文件配置:
262    -keep-global-name
263    add
264    customApiName
265    ```
266
267#### -compact
268
269去除不必要的空格符和所有的换行符。如果使用这个选项,那么所有代码会被压缩到一行。
270
271**注意**:
272
273release模式构建的应用栈信息仅包含代码行号,不包含列号,因此compact功能开启后无法依据报错栈中的行号定位到源码具体位置。
274
275#### -remove-log
276
277删除以下场景中对 console.*语句的调用,要求console.*语句返回值未被调用。
278
2791. 文件顶层的调用
2802. 代码块中的调用
281   例如:
282   ```
283   function foo() {
284    console.log('in block');
285   }
286   ```
2873. module或namespace中的调用
288   例如:
289   ```
290   namespace ns {
291    console.log('in ns');
292   }
293   ```
2944. switch语句中的调用
295
296#### -print-namecache *filepath*
297
298将名称缓存保存到指定的文件路径。名称缓存包含名称混淆前后的映射。
299
300**注意**:
301
302每次全量构建工程时都会生成新的namecache.json文件,因此您每次发布新版本时都要注意保存一个该文件的副本。
303
304#### -apply-namecache *filepath*
305
306复用指定的名称缓存文件。名字将会被混淆成缓存映射对应的名字,如果没有对应,将会被混淆成新的随机段名字。
307该选项应该在增量编译场景中被使用。
308
309默认情况下,DevEco Studio会在临时的缓存目录中保存缓存文件,并且在增量编译场景中自动应用该缓存文件。
310缓存目录:build/default/cache/{...}/release/obfuscation311
312#### -remove-comments
313
314删除编译生成的声明文件中的JsDoc注释。
315
316**注意**:
317
318编译生成的源码文件中的注释默认会被全部删除,不支持配置保留。
319可通过`keep-comments`配置来保留编译生成的声明文件中的JsDoc注释。
320
321#### -print-kept-names *filepath*
322
323该选项支持输出未混淆名单和全量白名单。其中,*filepath*为可选参数。
324
325当*filepath*参数缺省时,未混淆名单(keptNames.json)和全量白名单(whitelist.json)默认输出到缓存路径`build/default/cache/{...}/release/obfuscation`中。
326
327当*filepath*配置参数时,未混淆名单还会输出到该参数指定的路径中。其中,*filepath*仅支持相对路径,相对路径的起始位置为混淆配置文件的当前目录。*filepath*参数中的文件名请以`.json`为后缀。
328
329全量白名单(whitelist.json)包含本次模块编译流程中收集到的全部白名单,分为以下七种:
330
331(1)'sdk':表示系统api。
332
333(2)'lang':表示语言中的关键字。
334
335(3)'conf':表示用户配置的保留选项中的白名单。
336
337(4)'struct':表示ArkUI的struct中的属性。
338
339(5)'exported':表示被导出的名称及其属性。
340
341(6)'strProp':表示字符串属性。
342
343(7)'enum':表示enum中的成员。
344
345未混淆名单(keptNames.json)中包含未混淆的名称及未混淆的原因。其中,未混淆原因有以下七种:与sdk白名单重名、与语言白名单重名、与用户配置白名单重名、与struct白名单重名、与导出白名单重名、与字符串属性白名单重名(未开启字符串属性混淆的情况下)以及与enum白名单重名。
346
347**注意**:
348
3491.在编译har模块且开启属性混淆的情况下,'enum'白名单将收集enum中的成员名称。
350
351例如:
352
353```
354enum Test {
355  member1,
356  member2
357}
358```
359
360enum白名单内容为['member1', 'member2']。这是由于历史版本的har模块的编译中间产物为js文件,在js文件中enum类型会转换为一个立即执行函数,而enum成员会被转化为一个字符串属性和一个字符串常量。因此,为了保证开启属性混淆的情况下功能正常,需要将enum成员名称收集为白名单。在编译新版字节码har模块时,此特性仍然被保留。
361
3622.在编译hap/hsp/字节码har模块且开启属性混淆的情况下,当enum的成员被初始化时,'enum'白名单收集初始化表达式中包含的变量名称。
363
364例如:
365
366```
367let outdoor = 1;
368enum Test {
369  member1,
370  member2 = outdoor + member1 + 2
371}
372```
373
374其中,编译hap/hsp模块的情况下,enum白名单内容为['outdoor', 'member1'];编译字节码har模块的情况下,enum白名单内容为['outdoor', 'member1', 'member2']。
375
376### 保留选项
377
378#### -keep-property-name *[,identifiers,...]*
379
380指定想保留的属性名,支持使用名称类通配符。例如下面的例子:
381
382```
383-keep-property-name
384age
385firstName
386lastName
387```
388
389> **注意**:
390>
391> - 该选项在开启`-enable-property-obfuscation`时生效。
392>
393> - 属性白名单作用于全局。即代码中出现多个重名属性,只要与`-keep-property-name`配置白名单名称相同,均不会被混淆。
394
395**哪些属性名应该被保留?**
396
3971.为了保障混淆的正确性,建议保留所有不通过点语法访问的属性。例如,通过字符串访问的对象属性:
398
399```
400var obj = {x0: 0, x1: 0, x2: 0};
401for (var i = 0; i <= 2; i++) {
402    console.info(obj['x' + i]);  // x0, x1, x2应该被保留
403}
404
405Object.defineProperty(obj, 'y', {});  // y应该被保留
406Object.getOwnPropertyDescriptor(obj, 'y');  // y应该被保留
407console.info(obj.y);
408
409obj.s = 0;
410let key = 's';
411console.info(obj[key]);        // key对应的变量值s应该被保留
412
413obj.t1 = 0;
414console.info(obj['t' + '1']);        // t1应该被保留
415```
416
417对于如下的字符串常量形式的属性调用,可以选择性保留:
418
419```
420// 混淆配置:
421// -enable-property-obfuscation
422// -enable-string-property-obfuscation
423
424obj.t = 0;
425console.info(obj['t']); // 此时,'t'会被正确混淆,t可以选择性保留
426
427obj.['v'] = 0;
428console.info(obj['v']); // 此时,'v'会被正确混淆,v可以选择性保留
429```
430
4312.对于间接导出的场景,例如`export MyClass`和`let a = MyClass; export {a};`,如果不想混淆它们的属性名,那么需要使用[保留选项](#保留选项)来保留这些属性名。另外,对于直接导出的类或对象的属性的属性名,例如下面例子中的`name`和`age`,如果不想混淆它们,那么也需要使用[保留选项](#保留选项)来保留这些属性名。
432
433```
434export class MyClass {
435    person = {name: "123", age: 100};
436}
437```
438
4393.so库的API(例如示例中的foo),如果要在ArkTS/TS/JS文件中使用需手动保留API名称。
440
441```
442import testNapi from 'library.so'
443testNapi.foo() // foo需要保留,示例如:-keep-property-name foo
444```
445
4464.JSON数据解析及对象序列化时,需要保留使用到的字段,例如:
447
448```
449// 示例JSON文件结构(test.json):
450/*
451{
452  "jsonProperty": "value",
453  "otherProperty": "value2"
454}
455*/
456
457const jsonData = fs.readFileSync('./test.json', 'utf8');
458let jsonObj = JSON.parse(jsonData);
459let jsonProp = jsonObj.jsonProperty; // jsonProperty应该被保留
460
461class jsonTest {
462  prop1: string = '';
463  prop2: number = 0
464}
465
466let obj = new jsonTest();
467const jsonStr = JSON.stringify(obj); // prop1、prop2会被混淆,应该被保留
468```
469
4705.使用到的数据库相关的字段,需要手动保留。例如,数据库键值对类型(ValuesBucket)中的属性:
471
472```
473const valueBucket: ValuesBucket = {
474  'ID1': ID1, // ID1应该被保留
475  'NAME1': name, // NAME1应该被保留
476  'AGE1': age, // AGE1应该被保留
477  'SALARY1': salary // SALARY1应该被保留
478}
479```
480
4816.源码中自定义装饰器修饰了成员变量、成员方法、参数,同时其源码编译的中间产物为js文件时(如编译release源码HAR或者源码包含@ts-ignore、@ts-nocheck),这些装饰器所在的成员变量/成员方法名称需要被保留。这是由于ts高级语法特性转换为js标准语法时,将上述装饰器所在的成员变量/成员方法名称硬编码为字符串常量。
482
483示例:
484
485```
486class A {
487  // 1.成员变量装饰器
488  @CustomDecoarter
489  propetyName: string = ""   // propetyName 需要被保留
490  // 2.成员方法装饰器
491  @MethodDecoarter
492  methodName1(){} // methodName1 需要被保留
493  // 3.方法参数装饰器
494  methodName2(@ParamDecorator param: string): void { // methodName2 需要被保留
495  }
496}
497```
498
499#### -keep-global-name *[,identifiers,...]*
500
501指定要保留的顶层作用域或导入和导出元素的名称,支持使用名称类通配符。例如:
502
503```
504-keep-global-name
505Person
506printPersonName
507```
508
509`namespace`中导出的名称可以通过`-keep-global-name`选项保留,示例如下:
510
511```
512export namespace Ns {
513  export const age = 18; // -keep-global-name age 保留变量age
514  export function myFunc () {}; // -keep-global-name myFunc 保留函数myFunc
515}
516```
517
518> **注意**
519>
520> `-keep-global-name`指定的白名单作用于全局。即代码中出现多个顶层作用域名称或者导出名称,只要与`-keep-global-name`配置的白名单名称相同,均不会被混淆。
521
522**哪些顶层作用域的名称应该被保留?**
523
524在JavaScript中全局变量是`globalThis`的属性。如果在代码中使用`globalThis`去访问全局变量,那么该变量名应该被保留。
525
526示例:
527
528```
529var a = 0;
530console.info(globalThis.a);  // a 应该被保留
531
532function foo(){}
533globalThis.foo();           // foo 应该被保留
534
535var c = 0;
536console.info(c);             // c 可以被正确地混淆
537
538function bar(){}
539bar();                      // bar 可以被正确地混淆
540
541class MyClass {}
542let d = new MyClass();      // MyClass 可以被正确地混淆
543```
544
545当以命名导入的方式导入 so 库的 API时,若同时开启`-enable-toplevel-obfuscation`和`-enable-export-obfuscation`选项,需要手动保留 API 的名称。
546
547```
548import { testNapi, testNapi1 as myNapi } from 'library.so' // testNapi 和 testNapi1 应该被保留
549```
550
551#### -keep-file-name *[,identifiers,...]*
552
553指定要保留的文件/文件夹的名称(不需要写文件后缀),支持使用名称类通配符。例如:
554
555```
556-keep-file-name
557index
558entry
559```
560
561**哪些文件名应该被保留?**
562
5631.在使用`require`引入文件路径时,由于`ArkTS`不支持[CommonJS](../arkts-utils/module-principle.md#commonjs模块)语法,因此这种情况下路径应该被保留。
564
565```
566const module1 = require('./file1')   // file1 应该被保留
567```
568
5692.对于动态导入的路径名,由于无法识别`import`函数中的参数是否为路径,因此这种情况下路径应该被保留。
570
571```
572const moduleName = './file2'         // moduleName对应的路径名file2应该被保留
573const module2 = import(moduleName)
574```
575
5763.在使用[动态路由](../ui/arkts-navigation-navigation.md#跨包动态路由)进行路由跳转时,传递给动态路由的路径应该被保留。动态路由提供系统路由表和自定义路由表两种方式。若采用自定义路由表进行跳转,配置白名单的方式与上述第二种动态引用场景一致。而若采用系统路由表进行跳转,则需要将模块下`resources/base/profile/route_map.json`文件中`pageSourceFile`字段对应的路径添加到白名单中。
577
578```
579  {
580    "routerMap": [
581      {
582        "name": "PageOne",
583        "pageSourceFile": "src/main/ets/pages/directory/PageOne.ets",  // 路径都应该被保留
584        "buildFunction": "PageOneBuilder",
585        "data": {
586          "description" : "this is PageOne"
587        }
588      }
589    ]
590  }
591```
592
593#### -keep-comments *[,identifiers,...]*
594
595保留编译生成的声明文件中class,function,namespace,enum,struct,interface,module,type及属性上方的JsDoc注释,支持使用名称类通配符。例如想保留声明文件中Human类上方的JsDoc注释,可进行以下配置:
596
597```
598-keep-comments
599Human
600```
601
602**注意**:
603
6041. 该选项在开启`-remove-comments`时生效。
6052. 当编译生成的声明文件中class,function,namespace,enum,struct,interface,module,type及属性的名称被混淆时,该元素上方的JsDoc注释无法通过`-keep-comments`保留。例如当在`-keep-comments`中配置了exportClass时,如果exportClass类名被混淆,其JsDoc注释无法被保留:
606
607```
608/*
609 * @class exportClass
610 */
611export class exportClass {}
612```
613
614#### -keep-dts *filepath*
615
616指定路径的`.d.ts`文件中的名称(例如变量名、类名、属性名等)会被添加至`-keep-global-name`和`-keep-property-name`白名单中。请注意,`filepath`仅支持绝对路径,并且可以指定为一个目录。在这种情况下,该目录中所有`.d.ts`文件中的名称都将被保留。
617
618#### -keep *filepath*
619
620保留指定相对路径中的所有名称(例如变量名、类名、属性名等)不被混淆。这个路径可以是文件与文件夹,若是文件夹,则文件夹下的文件及子文件夹中文件都不混淆。
621路径仅支持相对路径,`./`与`../`为相对于混淆配置文件所在目录,支持使用路径类通配符。
622
623```
624-keep
625./src/main/ets/fileName.ts   // fileName.ts中的名称不混淆
626../folder                    // folder目录下文件及子文件夹中的名称都不混淆
627../oh_modules/json5          // 引用的三方库json5里所有文件中的名称都不混淆
628```
629
630**如何在模块中保留远程HAR包**
631
632方式一:指定远程`HAR`包在模块级`oh_modules`中的具体路径(该路径为软链接路径,真实路径为工程级`oh_modules`中的文件路径)。因为在配置模块级`oh_modules`中的路径作为白名单时,需要具体到包名或之后的目录才能正确地软链接到真实的目录路径,所以不能仅配置`HAR`包的上级目录名称。
633
634```
635// 正例
636-keep
637./oh_modules/harName1         // harName1目录下所有文件及子文件夹中的名称都不混淆
638./oh_modules/harName1/src     // src目录下所有文件及子文件夹中的名称都不混淆
639./oh_modules/folder/harName2  // harName2目录下所有文件及子文件夹中的名称都不混淆
640
641// 反例
642-keep
643./oh_modules                  // 保留模块级oh_modules里HAR包时,不支持配置HAR包的上级目录名称
644```
645
646方式二:指定远程`HAR`包在工程级`oh_modules`中的具体路径。因为工程级`oh_modules`中的文件路径都为真实路径,所以其路径均可配置。
647
648```
649-keep
650../oh_modules                  // 工程级oh_modules目录下所有文件及子文件夹中的名称都不混淆
651../oh_modules/harName3          // harName3目录下所有文件及子文件夹中的名称都不混淆
652```
653
654模块级`oh_moudles`和工程级`oh_modules`在`DevEco Studio`中的目录结构如下图所示:
655
656![oh_modules](./figures/oh_modules.png)
657
658**注意**:
659
6601. 被`-keep filepath`所保留的文件,其依赖链路上的文件中导出名称及其属性都会被保留。
6612. 该功能不影响文件名混淆`-enable-filename-obfuscation`的功能。
662
663#### 保留选项支持的通配符
664
665##### 名称类通配符
666
667名称类通配符使用方式如下:
668
669| 通配符 | 含义                   | 示例                                       |
670| ------ | ---------------------- | ------------------------------------------ |
671| ?      | 匹配任意单个字符       | "AB?"能匹配"ABC"等,但不能匹配"AB"         |
672| \*     | 匹配任意数量的任意字符 | "\*AB\*"能匹配"AB"、"aABb"、"cAB"、"ABc"等 |
673
674**使用示例**:
675
676保留所有以a开头的属性名称:
677
678```
679-keep-property-name
680a*
681```
682
683保留所有单个字符的属性名称:
684
685```
686-keep-property-name
687?
688```
689
690保留所有属性名称:
691
692```
693-keep-property-name
694*
695```
696
697##### 路径类通配符
698
699路径类通配符使用方式如下:
700
701| 通配符 | 含义                                                                     | 示例                                              |
702| ------ | ------------------------------------------------------------------------ | ------------------------------------------------- |
703| ?     | 匹配任意单个字符,除了路径分隔符`/`                                      | "../a?"能匹配"../ab"等,但不能匹配"../a/"         |
704| \*      | 匹配任意数量的任意字符,除了路径分隔符`/`                                | "../a*/c"能匹配"../ab/c",但不能匹配"../ab/d/s/c" |
705| \*\*   | 匹配任意数量的任意字符                                                   | "../a**/c"能匹配"../ab/c",也能匹配"../ab/d/s/c"  |
706| !      | 表示非,只能写在某个路径最前端,用来排除用户配置的白名单中已有的某种情况 | "!../a/b/c.ets"表示除"../a/b/c.ets"以外           |
707
708**使用示例**:
709
710表示路径../a/b/中所有文件夹(不包含子文件夹)中的c.ets文件不会被混淆:
711
712```
713-keep
714../a/b/*/c.ets
715```
716
717表示路径../a/b/中所有文件夹(包含子文件夹)中的c.ets文件不会被混淆:
718
719```
720-keep
721../a/b/**/c.ets
722```
723
724表示路径../a/b/中,除了c.ets文件以外的其它文件都不会被混淆。其中,`!`不可单独使用,只能用来排除白名单中已有的情况:
725
726```
727-keep
728../a/b/
729!../a/b/c.ets
730```
731
732表示路径../a/中的所有文件(不包含子文件夹)不会被混淆:
733
734```
735-keep
736../a/*
737```
738
739表示路径../a/下的所有文件夹(包含子文件夹)中的所有文件不会被混淆:
740
741```
742-keep
743../a/**
744```
745
746表示模块内的所有文件不会被混淆:
747
748```
749-keep
750./**
751```
752
753**注意**:
754
755(1)以上选项,不支持配置通配符`*`、`?`、`!`作其它含义使用。
756例如:
757
758```
759class A {
760  '*'= 1
761}
762
763-keep-property-name
764*
765```
766
767此时`*`表示匹配任意数量的任意字符,配置效果为所有属性名称都不混淆,而不是只有`*`属性不被混淆。
768
769(2)-keep选项中只允许使用`/`路径格式,不支持`\`或`\\`。
770
771### 注释
772
773可以使用`#`在混淆规则文件中进行注释。每行以`#`开头的文本会被当做是注释,例如下面的例子:
774
775```
776# white list for MainAbility.ets
777-keep-global-name
778MyComponent
779GlobalFunction
780
781-keep-property-name # white list for dynamic property names
782firstName
783lastName
784age
785```
786
787构建HAR时,注释不会被合并到最后的`obfuscation.txt`文件中。
788
789### 混淆规则合并策略
790
791编译工程中的某个模块时,其最终所应用的混淆规则是以下文件中配置的混淆规则的合并:
792
793* 本模块的build-profile.json5文件中`ruleOptions.files`字段指定的文件
794* 本地依赖的library中的`consumerFiles`选项中指定的文件
795* 远程依赖的HAR包中的`obfuscation.txt`文件
796
797上述文件中的混淆规则的优先级是一致的。构建模块时,这些规则文件将按照以下合并策略(伪代码)进行合并。
798
799```
800let `listRules` 表示上面提到的所有混淆规则文件的列表
801let finalRule = {
802    disableObfuscation: false,
803    enablePropertyObfuscation: false,
804    enableToplevelObfuscation: false,
805    compact: false,
806    removeLog: false,
807    keepPropertyName: [],
808    keepGlobalName: [],
809    keepDts: [],
810    printNamecache: string,
811    applyNamecache: string
812}
813for each file in `listRules`:
814    for each option in file:
815        switch(option) {
816            case -disable-obfuscation:
817                finalRule.disableObfuscation = true;
818                continue;
819            case -enable-property-obfuscation:
820                finalRule.enablePropertyObfuscation = true;
821                continue;
822            case -enable-toplevel-obfuscation:
823                finalRule.enableToplevelObfuscation = true;
824                continue;
825            case -compact:
826                finalRule.compact = true;
827                continue;
828            case -remove-log:
829                finalRule.removeLog = true;
830                continue;
831            case -print-namecache:
832                finalRule.printNamecache = #{指定的路径名};
833                continue;
834            case -apply-namecache:
835                finalRule.applyNamecache = #{指定的路径名};
836                continue;
837            case -keep-property-name:
838                finalRule.keepPropertyName.push(#{指定的名称});
839                continue;
840            case -keep-global-name:
841                finalRule.keepGlobalName.push(#{指定的名称});
842                continue;
843            case -keep-dts:
844                finalRule.keepDts.push(#{指定的路径});
845                continue;
846        }
847    end-for
848end-for
849```
850
851最后使用的混淆规则来自于对象`finalRule`。
852
853如果构建的是HAR,那么最终的`obfuscation.txt`文件内容来自于自身和本地依赖的library的`consumerFiles`选项,以及依赖的HAR的`obfuscation.txt`文件的合并。
854
855当`consumerFiles`指定的混淆配置文件中包含以下混淆规则时,这些混淆规则会被合并到HAR包的`obfuscation.txt`文件中,而其他混淆规则不会。
856
857```
858// 混淆选项
859-enable-property-obfuscation
860-enable-string-property-obfuscation
861-enable-toplevel-obfuscation
862-compact
863-remove-log
864
865// 保留选项
866-keep-property-name
867-keep-global-name
868```
869
870**library中混淆注意事项**
871
8721. 如果`consumerFiles`指定的混淆配置文件中包含上述混淆选项,当其他模块依赖该HAR包时,这些混淆选项会与主模块的混淆规则合并,从而影响主模块。因此不建议开发者在`consumer-rules.txt`文件中配置混淆选项,建议仅配置保留选项。
873
8742. 如果在`consumerFiles`指定的混淆配置文件中添加`-keep-dts`选项,会被转换成`-keep-global-name`和`-keep-property-name`。
875
876## 报错栈还原
877
878经过混淆的应用程序中代码名称会发生更改,crash时打印的报错栈更难以理解,因为报错栈与源码不完全一致。开发人员可使用DevEco Studio命令工具Command Line Tools中的hstack插件来还原源码堆栈,进而分析问题。反混淆工具需要使用应用编译过程中生成的sourceMaps.map文件以及混淆名称映射文件nameCache.json文件,因此请本地备份它们。
879
880![obfuscation-product](figures/obfuscation-product.png)
881
882## 代码混淆开启指导
883
8841. 使用混淆的前置条件:
885    1. 查看当前模块是否开启混淆开关及选择release构建模式。
886    2. 阅读文档,了解[混淆选项的能力](#混淆选项)与[混淆选项所需要保留白名单的场景](#保留选项)。
8873. 依次开启混淆选项,逐一适配与验证应用功能:
888    1. 开启`-enable-toplevel-obfuscation`选项,如果代码中有globalThis访问全局变量,会出现访问失败的情况,需要使用`-keep-global-name`来保留此全局变量名称。
889    2. 待上述选项应用适配成功后,开启`-enable-property-obfuscation`选项。此选项开启后以下场景需要适配:
890        1. 若代码中存在静态定义,动态访问的情况或者动态定义,静态访问的情况,需要使用`-keep-property-name`保留属性名称。示例:
891            ```
892            // 静态定义,动态访问:属性名在对象定义时是静态的,但访问时通过动态构建属性名(通常使用字符串拼接)来访问
893            const obj = {
894              staticName: value  // 静态定义属性
895            };
896            const fieldName = 'static' + 'Name';  // 动态构建属性名
897            console.log(obj[fieldName]);  // 使用方括号语法动态访问属性
898            ```
899            ```
900            // 动态定义,静态访问:属性名通过动态表达式在对象定义时确定,但访问时直接使用点语法(假设你知道属性名的结果)
901            const obj = {
902              [dynamicExpression]: value  // 动态定义属性
903            };
904            console.log(obj.dynamicPropertyName);  // 使用点语法静态访问属性
905            ```
906        2. 若代码中使用点语法访问未在ArkTS/TS/JS代码中定义的字段,比如访问native实现的so库,字段固定的json文件与数据库等场景:
907            1. 若在代码中引用so库的api,如```import testNapi from 'library.so';testNapi.foo();```需要使用`-keep-property-name` foo保留属性名称。
908            2. 若在代码中使用json文件中的字段,需要使用`-keep-property-name`保留json文件中的字段名称。
909            3. 若在代码中使用数据库相关的字段,需要使用`-keep-property-name`保留数据库中的字段名称。
910        3. 若构建HAR模块并发布给其他模块使用的场景,要在HAR模块中的consumer-rules.txt文件中将不能被二次混淆的属性使用`-keep-property-name`保留。consumer-rules.txt文件在构建HAR时会生成obfuscation.txt文件。此HAR被其它模块依赖时,Deveco Studio会解析obfuscation.txt文件,读取文件中的白名单。
911        4. 验证应用功能,排查遗漏的场景。若应用出现功能异常,依据混淆后的报错栈从对应的[中间产物](#如何查看混淆效果)中找到报错行的代码,排查需要配置的白名单并使用`-keep-property-name`进行保留。
912    3. 待上述选项应用适配成功后,开启`-enable-export-obfuscation`选项。此选项开启后以下场景需要适配:
913        1. 若构建HSP模块,它会提供接口及其属性给其它模块调用,因此需要将对外接口使用`-keep-global-name`来保留、将对外暴露的class/interface等语法中的属性使用`-keep-property-name`保留。
914        2. 若构建HAR模块并发布给其他模块使用的场景,要在HAR模块中的obfuscation-rules.txt文件中将对外接口使用`-keep-global-name`来保留、将对外暴露的class/interface等语法中的属性使用`-keep-property-name`保留。
915        3. 若在代码中引用so库的api,如```import { napiA } from 'library.so'```;需要使用`-keep-global-name` napiA保留so接口名称。
916        4. 验证应用功能以及模块被依赖时的接口调用功能,排查遗漏的场景。若应用出现功能异常,依据混淆后的报错栈从对应的[中间产物](#如何查看混淆效果)中找到报错行的代码,排查需要配置的白名单并进行保留。
917    4. 待上述选项应用适配成功后,开启`-enable-filename-obfuscation`选项。此选项开启后以下场景需要适配:
918        1. 若代码中有动态import语句,如```const path = './filePath'; import(path)```,会出现文件引用失败的情况,需要使用`-keep-file-name` filePath来保留这个文件名。
919        2. 若应用中有描述路由表信息的[routerMap配置](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/module-configuration-file-V5#routermap%E6%A0%87%E7%AD%BE),其中的pageSourceFile字段标记页面在模块的路径,需要使用`-keep-file-name`来保留这个路径。
920        3. 若代码中有传入ohmUrl进行页面跳转,如```router.pushUrl({url: '@bundle:com.example.routerPage/Library/Index')```,使用`-keep-file-name`来保留这个路径。
921        4. 验证应用功能,排查遗漏的场景。若应用出现功能异常,且报错栈中的路径为混淆后的路径,可以在模块中的`build/default/[...]/release/obfuscation/nameCache.json`文件中查询到原始路径,进而找到源码文件。另外,[插件hstack](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/ide-command-line-hstack-V5)支持自动还原混淆后的报错堆栈。在定位到需要保留的路径后,使用`-keep-file-name`来保留此路径。
922
923## 说明
924
925* 目前不支持在hvigor构建流程中插入自定义混淆插件。
926* 混淆的HAR包被模块依赖,若模块开启混淆,则HAR包会被二次混淆。
927* DevEco Studio右上角Product选项,将其中Build Mode选择release,可开启release编译模式。
928![product-release](figures/product-release.png)
929
930## FAQ
931
932### 混淆各功能上线SDK版本
933
934| 混淆选项 | 功能描述  | 最低版本号 |
935| ------- | --------- | ------ |
936| -disable-obfuscation         | 关闭混淆 | 4.0.9.2 |
937| -enable-property-obfuscation | 属性混淆 | 4.0.9.2 |
938| -enable-string-property-obfuscation | 字符串字面量属性名混淆 | 4.0.9.2 |
939| -enable-toplevel-obfuscation | 顶层作用域名称混淆 | 4.0.9.2 |
940| -enable-filename-obfuscation | HAR包文件/文件夹名称混淆 <br> HAP/HSP文件/文件夹名称混淆 | 4.1.5.3 <br> 5.0.0.19 |
941| -enable-export-obfuscation   | 向外导入或导出的名称混淆 | 4.1.5.3 |
942| -compact                     | 去除不必要的空格符和所有的换行符 | 4.0.9.2 |
943| -remove-log                  | 删除特定场景中的console.* | 4.0.9.2 |
944| -print-namecache             | 将名称缓存保存到指定的文件路径 | 4.0.9.2 |
945| -apply-namecache             | 复用指定的名称缓存文件 | 4.0.9.2 |
946| -remove-comments             | 删除文件中所有注释 | 4.1.5.3 |
947| -keep-property-name          | 保留属性名 | 4.0.9.2 |
948| -keep-global-name            | 保留顶层作用域的名称 | 4.0.9.2 |
949| -keep-file-name              | 保留HAR包的文件/文件夹的名称 <br> 保留HAP/HSP包的文件/文件夹的名称 | 4.1.5.3 <br> 5.0.0.19 |
950| -keep-dts                    | 保留指定路径的.d.ts文件中的名称 | 4.0.9.2 |
951| -keep-comments               | 保留编译生成的声明文件中class,function,namespace,enum,struct,interface,module,type及属性上方的JsDoc注释 | 4.1.5.3 |
952| -keep                        | 保留指定路径中的所有名称 | 5.0.0.18 |
953| 通配符                       | 名称类和路径类的保留选项支持通配符 | 5.0.0.24 |
954
955### 如何查看混淆效果
956下图为应用编译的简要流程图:
957
958![compilation-process](figures/compilation-process.png)
959
960在混淆结束后会将中间产物落盘,因此可以在编译产物build目录中找到混淆后的中间产物以查看混淆效果,同时可以找到混淆生成的名称映射表及系统API白名单文件。
961
962* 混淆后的文件目录:`build/default/[...]/release/模块名`
963* 混淆名称映射表及系统API白名单目录:`build/default/[...]/release/obfuscation`
964  * 名称映射表文件:nameCache.json,该文件记录了源码名称混淆的映射关系。
965  * 系统API白名单文件:systemApiCache.json,该文件记录了SDK中的接口与属性名称,与其重名的源码不会被混淆。
966
967  ![build-product](figures/build-product.png)
968
969### 如何排查功能异常
970#### 排查功能异常步骤
9711. 先在obfuscation-rules.txt配置-disable-obfuscation选项关闭混淆,确认问题是否由混淆引起。
9722. 若确认是开启混淆后功能出现异常,请先阅读文档了解[-enable-property-obfuscation](#混淆选项)、[-enable-toplevel-obfuscation](#混淆选项)、[-enable-filename-obfuscation](#混淆选项)、[-enable-export-obfuscation](#混淆选项)等混淆规则的能力以及哪些语法场景需要配置白名单来保证应用功能正常。下文简要介绍默认开启的四项选项功能,细节还请阅读对应选项的完整描述。
973    1. [-enable-toplevel-obfuscation](#混淆选项)为顶层作用域名称混淆开关。
974    2. [-enable-property-obfuscation](#混淆选项)为属性混淆开关,配置白名单的主要场景为网络数据访问、json字段访问、动态属性访问、调用so库接口等不能混淆场景,需要使用[-keep-property-name](#保留选项)来保留指定的属性名称。
975    3. [-enable-export-obfuscation](#混淆选项)为导出名称混淆,一般与`-enable-toplevel-obfuscation`和`-enable-property-obfuscation`选项配合使用;配置白名单的主要场景为模块对外接口不能混淆,需要使用[-keep-global-name](#保留选项)来指定保留导出/导入名称。
976    4. [-enable-filename-obfuscation](#混淆选项)为文件名混淆,配置白名单的主要场景为动态import或运行时直接加载的文件路径,需要使用[-keep-file-name](#保留选项)来保留这些文件路径及名称。
9773. 参考FAQ中的[常见报错案例](#常见报错案例),若是相似场景可参考对应的解决方法快速解决。
9784. 若常见案例中未找到相似案例,建议依据各项配置功能正向定位(若不需要相应功能,可删除对应配置项)。
9795. 应用运行时崩溃分析方法:
980    1.打开应用运行日志或者点击DevEco Studio中出现的Crash弹窗,找到运行时崩溃栈。
981    2.应用运行时崩溃栈中的行号为[编译产物](#如何查看混淆效果)的行号,方法名也可能为混淆后名称;因此排查时建议直接根据崩溃栈查看编译产物,进而分析哪些名称不能被混淆,然后将其配置进白名单中。
9826. 应用在运行时未崩溃但出现功能异常的分析方法(比如白屏):
983    1.打开应用运行日志:选择HiLog,检索与功能异常直接相关的日志,定位问题发生的上下文。
984    2.定位异常代码段:通过分析日志,找到导致功能异常的具体代码块。
985    3.增强日志输出:在疑似异常的功能代码中,对处理的数据字段增加日志记录。
986    4.分析并确定关键字段:通过对新增日志输出的分析,识别是否由于混淆导致该字段的数据异常。
987    5.配置白名单保护关键字段:将确认在混淆后对应用功能产生直接影响的关键字段添加到白名单中。
988
989#### 排查非预期的混淆能力
990若出现预期外的混淆效果,检查是否是依赖的本地模块/三方库开启了某些混淆选项。
991
992示例:
993假设当前模块未配置`-compact`,但是混淆的中间产物中代码都被压缩成一行,可按照以下步骤排查混淆选项:
994
9951. 查看当前模块的oh-package.json5中的dependencies,此字段记录了当前模块的依赖信息。
9962. 在依赖的模块/三方库中的混淆配置文件内检索"-compact":
997    * 在本地依赖的library中的consumer-rules.txt文件中检索"-compact"。
998    * 在工程目录下的oh_modules文件夹中,对全部的obfuscation.txt文件检索"-compact"。
999
1000**说明**:
1001三方库中obfuscation.txt不建议配置如下开关选项,这些选项会在主模块开启混淆时生效,进而出现预期外的混淆效果,甚至会出现应用运行时崩溃。因此建议联系发布此三方库方删除这些选项并重新出包。
1002```
1003-enable-property-obfuscation
1004-enable-string-property-obfuscation
1005-enable-toplevel-obfuscation
1006-remove-log
1007-compact
1008```
1009
1010### 常见报错案例
1011
1012#### 开启-enable-property-obfuscation选项可能出现的问题
1013
1014**案例一:报错内容为 Cannot read property 'xxx' of undefined**
1015
1016```
1017// 混淆前
1018const jsonData = ('./1.json')
1019let jsonStr = JSON.parse(jsonData)
1020let jsonObj = jsonStr.jsonProperty
1021
1022// 混淆后
1023const jsonData = ('./1.json')
1024let jsonStr = JSON.parse(jsonData)
1025let jsonObj = jsonStr.i
1026```
1027
1028开启属性混淆后,"jsonProperty" 被混淆成随机字符 "i",但json文件中为原始名称,从而导致值为undefined。
1029
1030**解决方案:** 使用`-keep-property-name`选项将json文件里的字段配置到白名单。
1031
1032**案例二:使用了数据库相关的字段,开启属性混淆后,出现报错**
1033
1034报错内容为 `table Account has no column named a23 in 'INSET INTO Account(a23)'`
1035
1036代码里使用了数据库字段,混淆时该SQL语句中字段名称被混淆,但数据库中字段为原始名称,从而导致报错。
1037
1038**解决方案:** 使用`-keep-property-name`选项将使用到的数据库字段配置到白名单。
1039
1040#### 同时开启-enable-export-obfuscation和-enable-toplevel-obfuscation选项可能出现的问题
1041
1042**当开启这两个选项时,主模块调用其他模块方法时涉及的方法名称混淆情况如下:**
1043
1044| 主模块 | 依赖模块 | 导入与导出的名称混淆情况 |
1045| ------- | ------- | ----------------------------|
1046| HAP/HSP | HSP     | HSP和主模块是独立编译的,混淆后名称会不一致,因此都需要配置白名单 |
1047| HAP/HSP | 本地HAR | 本地HAR与主模块一起编译,混淆后名称一致 |
1048| HAP/HSP | 三方库  | 三方库中导出的名称及其属性会被收集到白名单,因此导入和导出时都不会被混淆 |
1049
1050HSP需要将给其他模块用的方法配置到白名单中。因为主模块里也需要配置相同的白名单,所以推荐将HSP配置了白名单的混淆文件(假设名称为hsp-white-list.txt)添加到依赖它的模块的混淆配置项里,即下图files字段里。
1051
1052![obfuscation-config](figures/obfuscation-config.png)
1053
1054**案例一:动态导入某个类,类定义的地方被混淆,导入类名时却没有混淆,导致报错**
1055
1056```
1057// 混淆前
1058export class Test1 {}
1059
1060let mytest = (await import('./file')).Test1
1061
1062// 混淆后
1063export class w1 {}
1064
1065let mytest = (await import('./file')).Test1
1066```
1067
1068导出的类 "Test1" 是一个顶层作用域名,当 "Test1" 被动态使用时,它是一个属性。因为没有开启`-enable-property-obfuscation`选项,所以名称混淆了,但属性没有混淆。
1069
1070**解决方案:** 使用`-keep-global-name`选项将 "Test1" 配置到白名单。
1071
1072**案例二:在使用namespace中的方法时,该方法定义的地方被混淆了,但使用的地方却没有被混淆,导致报错**
1073
1074```
1075// 混淆前
1076export namespace ns1 {
1077  export class person1 {}
1078}
1079
1080import {ns1} from './file1'
1081let person1 = new ns1.person1()
1082
1083// 混淆后
1084export namespace a3 {
1085  export class b2 {}
1086}
1087
1088import {a3} from './file1'
1089let person1 = new a3.person1()
1090```
1091
1092namespace里的 "person1" 属于export元素,当通过 "ns1.person1" 调用时,它被视为一个属性。由于未开`-enable-property-obfuscation`选项,导致在使用时未对其进行混淆。
1093
1094**解决方案:**
1095
10961. 开启`-enable-property-obfuscation`选项。
10972. 将namespace里导出的方法使用`-keep-global-name`选项添加到白名单。
1098
1099**案例三:使用了`declare global`,混淆后报语法错误**
1100
1101```
1102// 混淆前
1103declare global {
1104  var age : string
1105}
1106
1107// 混淆后
1108declare a2 {
1109  var b2 : string
1110}
1111```
1112
1113报错内容为 `SyntaxError: Unexpected token`。
1114
1115**解决方案:** 使用`-keep-global-name`选项将`__global`配置到白名单中。
1116
1117#### 未开启-enable-string-property-obfuscation混淆选项,字符串字面量属性名却被混淆,导致字符串字面量属性名的值为undefined
1118
1119```
1120person["age"] = 22; // 混淆前
1121
1122person["b"] = 22; // 混淆后
1123```
1124
1125**解决方案:**
1126
11271. 确认是否有依赖的HAR包开启了字符串属性名混淆,若开启了,则会影响主工程,需将其关闭。
11282. 若不能关闭`-enable-string-property-obfuscation`选项,将属性名配置到白名单中。
11293. 若依赖HAR包未开启字符串属性名混淆,同时SDK版本小于4.1.5.3,请更新SDK。
1130
1131#### 开启-enable-filename-obfuscation选项后,可能会出现的问题
1132
1133**案例一:报错为 Error Failed to get a resolved OhmUrl for 'D:code/MyApplication/f12/library1/pages/d.ets' imported by 'undefined'**
1134
1135工程的目录结构如下图所示,模块library1的外层还有目录 "directory",开启文件名混淆后,"directory" 被混淆为f12,导致路径找不到。
1136
1137![directory-offuscation](figures/directory-obfuscation.png)
1138
1139**解决方案:**
1140
11411. 如果工程的目录结构和报错内容都相似,请将SDK更新至最低5.0.0.26版本。
11422. 使用`-keep-file-name`将模块外层的目录名 "directory" 配置到白名单中。
1143
1144**案例二:报错为 Cannot find module 'ets/appability/AppAbility' which is application Entry Point**
1145
1146由于系统会在应用运行时加载ability文件,用户需要手动配置相应的白名单,防止指定文件被混淆,导致运行失败。
1147
1148**解决方案:** 使用`-keep-file-name`选项,将`src/main/module.json5`文件中,'srcEntry'字段所对应的路径配置到白名单中。
1149
1150```
1151-keep-file-name
1152appability
1153AppAbility
1154```
1155
1156#### 使用-keep-global-name选项配置白名单时,可能会出现的问题
1157
1158报错内容为 `Cannot read properties of undefined (reading 'has')`。
1159
1160**解决方案:** 将SDK更新至最低4.1.6.3版本。
1161
1162#### HAP与HSP依赖相同的本地源码HAR模块,可能会出现的问题
1163
1164* 若开启文件名混淆,会出现以下问题:
1165  * 问题一:单例功能异常问题。原因是HAP与HSP独立执行构建与混淆流程,本地源码HAR模块在HAP与HSP的包中可能会出现相同的文件名被混淆成不同文件名的情况。
1166  * 问题二:接口调用失败问题。原因是HAP与HSP独立执行构建与混淆流程,本地源码HAR模块在HAP与HSP的包中可能会出现不同的文件名被混淆成相同的文件名的情况。
1167* 若开启`-enable-export-obfuscation`和`-enable-toplevel-obfuscation`选项,在应用运行时会出现加载接口失败的问题。
1168原因是HAP与HSP独立执行构建与混淆流程,本地源码HAR模块中暴露的接口在HAP与HSP中被混淆成不同的名称。
1169
1170**解决方案:**
1171
11721. 将HAP与HSP共同依赖的本地源码HAR改造为[字节码HAR](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/ide-hvigor-build-har-V5#section179161312181613),这样此HAR在被依赖时不会被二次混淆。
11732. 将HAP与HSP共同依赖的本地源码HAR以[release模式构建打包](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/ide-hvigor-build-har-V5#section19788284410),这样此HAR在被依赖时,其文件名与对外接口不会被混淆。
1174
1175#### 同时开启-enable-property-obfuscation和-keep选项可能会出现的问题
1176
1177**问题现象**
1178
1179使用如下混淆配置:
1180
1181```
1182-enable-property-obfuscation
1183-keep
1184./file1.ts
1185```
1186
1187并且在`file2.ts`中导入`file1.ts`的接口。此时,接口中有属性的类型为对象类型,该对象类型的属性在`file1.ts`中被保留,在`file2.ts`中被混淆,从而导致调用时引发功能异常。示例如下:
1188
1189```
1190// 混淆前
1191// file1.ts
1192export interface MyInfo {
1193  age: number;
1194  address: {
1195    city1: string;
1196  }
1197}
1198
1199// file2.ts
1200import { MyInfo } from './file1';
1201const person: MyInfo = {
1202  age: 20,
1203  address: {
1204    city1: "shanghai"
1205  }
1206}
1207
1208// 混淆后,file1.ts的代码被保留
1209// file2.ts
1210import { MyInfo } from './file1';
1211const person: MyInfo = {
1212  age: 20,
1213  address: {
1214    i: "shanghai"
1215  }
1216}
1217```
1218
1219**问题原因**
1220
1221`-keep`选项保留`file1.ts`文件时,`file1.ts`中代码不会被混淆。对于导出属性(如address)所属类型内的属性,不会被自动收集在属性白名单中。因此,该类型内的属性在其他文件中被使用时,会被混淆。
1222
1223**解决方案**
1224
1225方案一:使用`interface`定义该属性的类型,并使用`export`进行导出,这样该属性会被自动被收集到属性白名单中。示例如下:
1226
1227```
1228// file1.ts
1229export interface AddressType {
1230  city1: string
1231}
1232export interface MyInfo {
1233  age: number;
1234  address: AddressType;
1235}
1236```
1237
1238方案二:使用`-keep-property-name`选项,将未直接导出的类型内的属性配置到属性白名单中。示例如下:
1239
1240```
1241-keep-property-name
1242city1
1243```
1244