• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# ArkGuard混淆原理及功能
2
3## 术语清单
4
5| 名词 | 释义 |
6| --- | --- |
7| [HAP](../quick-start/hap-package.md) | HAP(Harmony Ability Package)是应用安装和运行的基本单元。HAP包是由代码、资源、第三方库、配置文件等打包生成的模块包。 |
8| [HAR](../quick-start/har-package.md) | HAR(Harmony Archive)是静态共享包,通过HAR可以实现多个模块或多个工程共享ArkUI组件、资源等相关代码。通过Static Library创建HAR模块。 |
9| [HSP](../quick-start/in-app-hsp.md) | HSP(Harmony Shared Package)是动态共享包,通过HSP可以实现代码和资源的共享。通过Shared Library创建HSP模块。 |
10| 本地HAR | 源码形式的HAR模块。 |
11| 远程HAR | 构建后打包生成的HAR包。 |
12| 本地HSP | 源码形式的HSP模块。 |
13| 远程HSP | 构建后打包生成的HSP包。 |
14| 三方库 | 由第三方开发并发布的库,发布到OHPM中心仓,供其他应用使用。 |
15| 名称混淆 | 将代码中的类名、方法名、变量名等标识符修改为无意义的名称。 |
16
17## 混淆能力范围
18
19### 适用语言
20ArkGuard支持ArkTS/TS/JS语言,不支持C/C++、JSON、资源文件等。
21
22### 混淆能力
23ArkGuard支持基础的名称混淆、代码压缩和注释删除功能,不支持控制混淆、数据混淆等高级混淆功能。
24
25对于名称混淆,主要提供**名称重命名**和**配置保留白名单**的能力。
26
27### 混淆能力局限性
28
29**1.语言的限制**
30
31代码混淆工具在处理不同编程语言时,其类型分析机制、混淆策略和执行效率都会因目标语言的特性而呈现差异。以业界常用的ProGuard为例,其主要面向Java这类强类型语言进行混淆。由于强类型语言具有严格的类型系统,每个类型都有明确的定义来源。这种特性使得混淆过程中的类型关系追踪和处理更为精确,从而大幅减少了需要配置保留规则的场景。
32
33相比之下,ArkgGuard混淆工具主要针对JS、TS和ArkTS语言。JS支持运行时动态修改对象、函数,而混淆是在编译阶段进行的静态处理,这种差异可能导致混淆后的名称在运行时无法被正确解析,进而引发运行时异常。TS和ArkTS虽然引入了静态类型系统,但采用了结构性类型机制,即具有相同结构的不同命名类型会被视为等价类型。因此,在TS和ArkTS中仍然无法追溯类型的确切来源。基于这些特性,使用ArkGuard时需要对更多的语法场景进行白名单配置,同时,ArkGuard采用全局生效的属性保留机制,根据白名单统一保留所有同名属性,而无法支持针对特定类型的精确保留配置。
34
35具体而言,可以参考以下示例:
36
37假设ArkGuard支持配置指定类型的白名单,配置类A1作为白名单,类A1的属性prop1在白名单中,而A2中的prop1属性不在白名单中。此时,a2作为参数被传入test函数中,调用prop1属性时会导致功能异常。
38
39```typescript
40// 混淆前
41class A1 {
42  prop1: string = '';
43}
44
45class A2 {
46  prop1: string = '';
47}
48
49function test(input: A1) {
50  console.log(input.prop1);
51}
52
53let a2 = new A2();
54a2.prop1 = 'prop a2';
55test(a2);
56```
57
58```typescript
59// 混淆后
60class A1 {
61  prop1: string = '';
62}
63
64class A2 {
65  a: string = '';
66}
67
68function test(input: A1) {
69  console.log(input.prop1);
70}
71
72let a2 = new A2();
73a2.a = 'prop a2';
74test(a2);
75```
76
77综上所述,开发者应了解这种语言差异带来的混淆效果差异,并尽量使用不重复的名称,以使在各种场景下的混淆效果更好。
78
79**2.安全保证的有限性**
80
81与其他代码混淆工具一样,混淆只能在一定程度上增加逆向过程的难度,并不能真正阻止逆向工程。
82
83并且,由于ArkGuard混淆工具仅支持基础混淆能力,开发者不应只依赖ArkGuard来保证应用的安全性,对于源码安全有高要求的开发者,应考虑使用[应用加密](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/code-protect-V5)、第三方安全加固等安全措施来保护代码。
84
85## 混淆机制及流程
86
87下图为应用编译的简要流程图:
88
89![compilation-process](figures/compilation-process.png)
90
91开发者可以在模块的`build-profile.json5`配置文件中开启混淆功能,从而在编译打包的过程中自动对源码进行混淆处理。
92
93混淆过程中,首先读取混淆开关。在开关开启的情况下,解析混淆配置文件,并依据[混淆规则合并策略](#混淆规则合并策略)合并混淆规则。然后按照混淆规则对经过语法转换的中间文件进行混淆,最后将混淆后的中间文件落盘至build目录。开发者可以通过build目录中混淆后的产物,确认混淆效果。
94
95在使用混淆功能前,建议开发者先通过文档了解[混淆选项的能力](source-obfuscation.md#混淆选项)与[混淆选项所需要保留白名单的场景](source-obfuscation.md#保留选项),再根据开发需求选择对应的混淆功能。
96
97
98## 混淆选项
99
100各混淆选项能力加入系统的SDK版本不同,详见[各功能上线SDK版本](#混淆各功能上线sdk版本)
101
102### 已有混淆选项汇总
103
104| 功能 | 选项 |
105| --- | --- |
106| 关闭混淆 | [`-disable-obfuscation`](#-disable-obfuscation) |
107| 属性名称混淆 | [`-enable-property-obfuscation`](#-enable-property-obfuscation) |
108| 字符串属性名称混淆 | [`-enable-string-property-obfuscation`](#-enable-string-property-obfuscation) |
109| 顶层作用域名称混淆 | [`-enable-toplevel-obfuscation`](#-enable-toplevel-obfuscation) |
110| 导入导出名称混淆 | [`-enable-export-obfuscation`](#-enable-export-obfuscation) |
111| 文件名混淆 | [`-enable-filename-obfuscation`](#-enable-filename-obfuscation) |
112| 代码压缩 | [`-compact`](#-compact) |
113| 声明文件注释删除 | [`-remove-comments`](#-remove-comments) |
114| console打印删除 | [`-remove-log`](#-remove-log) |
115| 名称缓存输出 | [`-print-namecache`](#-print-namecache) |
116| 名称缓存复用 | [`-apply-namecache`](#-apply-namecache) |
117
118### -disable-obfuscation
119
120关闭所有混淆。
121
122若配置该选项,那么默认混淆(局部变量及参数名)以及所有已配置的混淆、保留选项的功能将全部失效。
123
124### -enable-property-obfuscation
125
126开启属性名称混淆,效果如下:
127
128  ```typescript
129  // 混淆前:
130  class TestA {
131    static prop1: number = 0;
132  }
133  TestA.prop1;
134  ```
135
136  ```typescript
137  // 混淆后:
138  class TestA {
139    static i: number = 0;
140  }
141  TestA.i;
142  ```
143
144若配置该选项,那么所有的属性名都会被混淆,除了下面场景:
145
146* 被`import/export`直接导入或导出的类、对象的属性名不会被混淆。例如下面例子中的属性名`data`不会被混淆。
147
148    ```
149    export class MyClass {
150       data: string;
151    }
152    ```
153
154* ArkUI组件中的属性名不会被混淆。例如下面例子中的`message`和`data`不会被混淆。
155
156    ```
157    @Component struct MyExample {
158        @State message: string = "hello";
159        data: number[] = [];
160        // ...
161    }
162    ```
163
164* 被[保留选项](#-keep-property-name)指定的属性名不会被混淆。
165* SDK API列表中的属性名不会被混淆。SDK API列表是构建时从SDK中自动提取出来的一个名称列表,其缓存文件为systemApiCache.json,路径为工程目录下build/default/cache/{...}/release/obfuscation中。
166* 字符串字面量属性名不会被混淆。例如下面例子中的`"name"`和`"age"`不会被混淆。
167
168    ```
169    let person = {"name": "abc"};
170    person["age"] = 22;
171    ```
172
173### -enable-string-property-obfuscation
174
175开启字符串属性混淆,仅在已开启属性混淆的基础上生效。
176
177若想混淆字符串字面量属性名,需要在已配置`-enable-property-obfuscation`的基础上使用。例如:
178
179  ```
180  -enable-property-obfuscation
181  -enable-string-property-obfuscation
182  ```
183
184根据上述配置,下面例子中的`"name"`和`"age"`混淆效果如下:
185
186  ```typescript
187  // 混淆前:
188  let person = {"name": "abc"};
189  person["age"] = 22;
190  ```
191
192  ```typescript
193  // 混淆后:
194  let person = {"a": "abc"};
195  person["b"] = 22;
196  ```
197
198**使用该选项时,需要注意以下事项:**
199
200**1.** 如果代码里面有字符串属性名包含特殊字符(除了`a-z、A-Z、0-9、_`之外的字符),例如`let obj = {"\n": 123, "": 4, " ": 5}`,建议不要开启`-enable-string-property-obfuscation`选项,因为可能无法通过[保留选项](#-keep-property-name)来指定保留这些名字。
201
202**2.** SDK API的属性白名单中不包含声明文件中使用的字符串常量值,例如示例中的字符串'ohos.want.action.home'未包含在属性白名单中:
203```
204// SDK API文件@ohos.app.ability.wantConstant片段:
205export enum Params {
206  ACTION_HOME = 'ohos.want.action.home'
207}
208// 开发者源码示例:
209let params = obj['ohos.want.action.home'];
210```
211
212因此在开启了`-enable-string-property-obfuscation`选项时,如果想保留代码中使用的SDK API字符串常量的属性不被混淆,例如obj['ohos.want.action.home'],那么需要使用[-keep-property-name选项](#-keep-property-name)保留。
213
214### -enable-toplevel-obfuscation
215
216开启顶层作用域名称混淆,效果如下:
217
218  ```typescript
219  // 混淆前:
220  let count = 0;
221  ```
222
223  ```typescript
224  // 混淆后:
225  let s = 0;
226  ```
227
228若配置该选项,那么所有的顶层作用域的名称都会被混淆,除了下面场景:
229
230* 被`import/export`直接导入或导出的名称不会被混淆。
231* 当前文件找不到声明的名称不会被混淆。
232* 被[保留选项](#-keep-global-name)指定的顶层作用域名称不会被混淆。
233* SDK API列表中的顶层作用域名称不会被混淆。
234
235### -enable-export-obfuscation
236
237开启直接导入或导出的名称混淆,效果如下:
238
239  ```typescript
240  // 混淆前:
241  namespace ns {
242    export type customT = string;
243  }
244  ```
245
246  ```typescript
247  // 混淆后:
248  namespace ns {
249    export type h = string;
250  }
251  ```
252
253若配置该选项,那么非顶层作用域中导入或导出的名称会被混淆。**若想混淆顶层作用域中导入或导出的名称,需要在已配置`-enable-toplevel-obfuscation`的基础上使用;若想混淆导入或导出的属性名,需要在已配置`-enable-property-obfuscation`的基础上使用。** 开启此选项时,以下特殊场景不会被混淆:
254
255* 远程HAR(真实路径在oh_modules中的包)中导出的名称和属性名不会被混淆。
256* 被[保留选项](#保留选项)指定的名称与属性名不会被混淆。
257* SDK API列表中的名称不会被混淆。
258
259### -enable-filename-obfuscation
260
261开启文件/文件夹名称混淆,效果如下:
262
263  ```
264  // 混淆前:
265  import * as m from '../test1/test2';
266  import { foo } from '../test1/test2';
267  const module = import('../test1/test2');
268  ```
269
270
271  ```
272  // 混淆后:
273  import * as m from '../a/b';
274  import { foo } from '../a/b';
275  const module = import('../a/b');
276  ```
277
278若配置该选项,那么所有的文件/文件夹名称都会被混淆,除了下面场景:
279
280* oh-package.json5文件中'main'、'types'字段配置的文件/文件夹名称不会被混淆。
281* 模块内module.json5文件中'srcEntry'字段配置的文件/文件夹名称不会被混淆。
282* 被[-keep-file-name](#-keep-file-name)指定的文件/文件夹名称不会被混淆。
283* 非ECMAScript模块引用方式(例如:`const module = require('./module')`)。
284* 非路径引用方式,例如例子中的json5不会被混淆 `import module from 'json5'`。
285
286>**注意**:
287>
288>由于系统会在应用运行时加载某些指定的文件,针对这类文件,开发者需要手动在[-keep-file-name](#-keep-file-name)选项中配置相应的白名单,防止指定文件被混淆,导致运行失败。
289>
290>编译入口、Ability组件、Worker多线程,这三种不能混淆的文件名在DevEco Studio 5.0.3.500版本已被自动收集进白名单中,无需再手动配置,其它不能混淆文件名的场景仍需开发者手动配置。
291
292### -compact
293
294去除不必要的空格符和所有的换行符。
295
296若配置该选项,那么所有代码会被压缩到一行。效果如下:
297
298  ```
299  // 混淆前:
300  class TestA {
301    static prop1: number = 0;
302  }
303  TestA.prop1;
304  ```
305
306  ```
307  // 混淆后:
308  class TestA { static prop1: number = 0; } TestA.prop1;
309  ```
310
311>**注意**:
312>
313>release模式构建的应用栈信息仅包含代码行号,不包含列号,因此compact功能开启后无法依据报错栈中的行号定位到源码具体位置。
314
315
316### -remove-comments
317
318删除编译生成的声明文件中的JsDoc注释,效果如下:
319
320混淆前:
321  ```
322  /**
323   * @todo
324   */
325  declare let count: number;
326  ```
327
328混淆后:
329  ```
330  declare let count: number;
331  ```
332
333可以通过[`-keep-comments`](#-keep-comments)配置来保留编译生成的声明文件中的JsDoc注释。
334
335>**注意**:
336>
337>编译生成的源码文件中的注释默认会被全部删除,不支持配置保留。
338
339### -remove-log
340
341删除对`console.*`语句的调用,要求`console.*`语句返回值未被调用,效果如下:
342
343  ```
344  // 混淆前:
345  if (flag) {
346    console.log("hello");
347  }
348  ```
349
350  ```
351  // 混淆后:
352  if (flag) {
353  }
354  ```
355
356若配置该选项,以下场景中的`console.*`语句会被删除:
357
3581. 文件顶层的调用
3592. 代码块中的调用
360   例如:
361   ```
362   function foo() {
363    console.log('in block');
364   }
365   ```
3663. module或namespace中的调用
367   例如:
368   ```
369   namespace ns {
370    console.log('in ns');
371   }
372   ```
3734. switch语句中的调用
374
375### -print-namecache
376
377将名称缓存保存到指定的文件路径*filepath*中,名称缓存包含名称混淆前后的映射。其中,*filepath*为必选参数,支持相对路径和绝对路径,相对路径的起始位置为混淆配置文件的当前目录。*filepath*参数中的文件名请以`.json`为后缀。
378
379例如:
380```
381-print-namecache
382./customCache/nameCache.json
383```
384
385>**注意**:
386>
387>每次全量构建工程时都会生成新的namecache.json文件,因此您每次发布新版本时都要注意保存一个该文件的副本。
388
389### -apply-namecache
390
391复用指定的名称缓存文件*filepath*。其中,*filepath*为必选参数,支持相对路径和绝对路径,相对路径的起始位置为混淆配置文件的当前目录。*filepath*参数中的文件名请以`.json`为后缀。
392该选项应该在增量编译场景中被使用。开启该选项后,名称将会被混淆成缓存映射对应的名称,若找不到对应的缓存,则会被混淆成新的随机名称。
393
394例如:
395```
396-apply-namecache
397./customCache/nameCache.json
398```
399
400默认情况下,DevEco Studio会在临时的缓存目录中保存缓存文件,并且在增量编译场景中自动应用该缓存文件。
401缓存目录:build/default/cache/{...}/release/obfuscation402
403## 保留选项
404
405各保留选项能力加入系统的SDK版本不同,详见[各功能上线SDK版本](#混淆各功能上线sdk版本)
406
407### 已有保留选项汇总
408
409| 功能 | 选项 |
410| --- | --- |
411| 指定保留属性名称 | [`-keep-property-name`](#-keep-property-name) |
412| 指定保留顶层作用域或导入导出元素名称 | [`-keep-global-name`](#-keep-global-name) |
413| 指定保留文件/文件夹名称 | [`-keep-file-name`](#-keep-file-name) |
414| 指定保留注释 | [`-keep-comments`](#-keep-comments) |
415| 指定保留声明文件中的所有名称 | [`-keep-dts`](#-keep-dts) |
416| 指定保留源码文件中的所有名称 | [`-keep`](#-keep) |
417
418### -keep-property-name
419
420指定想保留的属性名,支持使用[名称类通配符](#名称类通配符)。按如下方式进行配置,表示保留名称为`age`、`firstName`和`lastName`的属性:
421
422```
423-keep-property-name
424age
425firstName
426lastName
427```
428
429**使用该选项时,需要注意以下事项:**
430
431**1.** 该选项在开启`-enable-property-obfuscation`时生效。
432
433**2.** 属性白名单作用于全局。即代码中出现多个重名属性,只要与`-keep-property-name`配置白名单名称相同,均不会被混淆。
434
435**哪些属性名应该被保留?**
436
4371.为了保障混淆的正确性,建议保留所有不通过点语法访问的属性。例如,通过字符串访问的对象属性:
438
439```
440var obj = {x0: 0, x1: 0, x2: 0};
441for (var i = 0; i <= 2; i++) {
442    console.info(obj['x' + i]);  // x0, x1, x2应该被保留
443}
444
445Object.defineProperty(obj, 'y', {});  // y应该被保留
446Object.getOwnPropertyDescriptor(obj, 'y');  // y应该被保留
447console.info(obj.y);
448
449obj.s = 0;
450let key = 's';
451console.info(obj[key]);        // key对应的变量值s应该被保留
452
453obj.t1 = 0;
454console.info(obj['t' + '1']);        // t1应该被保留
455```
456
457对于如下的字符串常量形式的属性调用,可以选择性保留:
458
459```
460// 混淆配置:
461// -enable-property-obfuscation
462// -enable-string-property-obfuscation
463
464obj.t = 0;
465console.info(obj['t']); // 此时,'t'会被正确混淆,t可以选择性保留
466
467obj.['v'] = 0;
468console.info(obj['v']); // 此时,'v'会被正确混淆,v可以选择性保留
469```
470
4712.对于间接导出的场景,例如`export MyClass`和`let a = MyClass; export {a};`,如果不想混淆它们的属性名,那么需要使用[保留选项](#保留选项)来保留这些属性名。另外,对于直接导出的类或对象的属性的属性名,例如下面例子中的`name`和`age`,如果不想混淆它们,那么也需要使用[保留选项](#保留选项)来保留这些属性名。
472
473```
474export class MyClass {
475    person = {name: "123", age: 100};
476}
477```
478
4793.so库的API(例如示例中的foo),如果要在ArkTS/TS/JS文件中使用需手动保留API名称。
480
481```
482import testNapi from 'library.so'
483testNapi.foo() // foo需要保留,示例如:-keep-property-name foo
484```
485
4864.JSON数据解析及对象序列化时,需要保留使用到的字段,例如:
487
488```
489// 示例JSON文件结构(test.json):
490/*
491{
492  "jsonProperty": "value",
493  "otherProperty": "value2"
494}
495*/
496
497const jsonData = fs.readFileSync('./test.json', 'utf8');
498let jsonObj = JSON.parse(jsonData);
499let jsonProp = jsonObj.jsonProperty; // jsonProperty应该被保留
500
501class jsonTest {
502  prop1: string = '';
503  prop2: number = 0
504}
505
506let obj = new jsonTest();
507const jsonStr = JSON.stringify(obj); // prop1、prop2会被混淆,应该被保留
508```
509
5105.使用到的数据库相关的字段,需要手动保留。例如,数据库键值对类型(ValuesBucket)中的属性:
511
512```
513const valueBucket: ValuesBucket = {
514  'ID1': ID1, // ID1应该被保留
515  'NAME1': name, // NAME1应该被保留
516  'AGE1': age, // AGE1应该被保留
517  'SALARY1': salary // SALARY1应该被保留
518}
519```
520
5216.源码中自定义装饰器修饰了成员变量、成员方法、参数,同时其源码编译的中间产物为js文件时(如编译release源码HAR或者源码包含@ts-ignore、@ts-nocheck),这些装饰器所在的成员变量/成员方法名称需要被保留。这是由于ts高级语法特性转换为js标准语法时,将上述装饰器所在的成员变量/成员方法名称硬编码为字符串常量。
522
523示例:
524
525```
526class A {
527  // 1.成员变量装饰器
528  @CustomDecoarter
529  propertyName: string = ""   // propertyName 需要被保留
530  // 2.成员方法装饰器
531  @MethodDecoarter
532  methodName1(){} // methodName1 需要被保留
533  // 3.方法参数装饰器
534  methodName2(@ParamDecorator param: string): void { // methodName2 需要被保留
535  }
536}
537```
538
539### -keep-global-name
540
541指定要保留的顶层作用域或导入和导出元素的名称,支持使用[名称类通配符](#名称类通配符)。可按如下方式进行配置:
542
543```
544-keep-global-name
545Person
546printPersonName
547```
548
549`namespace`中导出的名称也可以通过`-keep-global-name`选项保留,示例如下:
550
551```
552export namespace Ns {
553  export const age = 18; // -keep-global-name age 保留变量age
554  export function myFunc () {}; // -keep-global-name myFunc 保留函数myFunc
555}
556```
557
558> **注意**
559>
560> `-keep-global-name`指定的白名单作用于全局。即代码中出现多个顶层作用域名称或者导出名称,只要与`-keep-global-name`配置的白名单名称相同,均不会被混淆。
561
562**哪些顶层作用域的名称应该被保留?**
563
5641.在JavaScript中全局变量是`globalThis`的属性。如果在代码中使用`globalThis`去访问全局变量,那么该变量名应该被保留。
565
566示例:
567
568```
569var a = 0;
570console.info(globalThis.a);  // a 应该被保留
571
572function foo(){}
573globalThis.foo();           // foo 应该被保留
574
575var c = 0;
576console.info(c);             // c 可以被正确地混淆
577
578function bar(){}
579bar();                      // bar 可以被正确地混淆
580
581class MyClass {}
582let d = new MyClass();      // MyClass 可以被正确地混淆
583```
584
5852.当以命名导入的方式导入 so 库的 API时,若同时开启`-enable-toplevel-obfuscation`和`-enable-export-obfuscation`选项,需要手动保留 API 的名称。
586
587```
588import { testNapi, testNapi1 as myNapi } from 'library.so' // testNapi 和 testNapi1 应该被保留
589```
590
591### -keep-file-name
592
593指定要保留的文件/文件夹的名称(不需要写文件后缀),支持使用[名称类通配符](#名称类通配符)。例如:
594
595```
596-keep-file-name
597index
598entry
599```
600
601**哪些文件名应该被保留?**
602
6031.在使用`require`引入文件路径时,由于`ArkTS`不支持[CommonJS](../arkts-utils/module-principle.md#commonjs模块)语法,因此这种情况下路径应该被保留。
604
605```
606const module1 = require('./file1')   // file1 应该被保留
607```
608
6092.对于动态导入的路径名,由于无法识别`import`函数中的参数是否为路径,因此这种情况下路径应该被保留。
610
611```
612const moduleName = './file2'         // moduleName对应的路径名file2应该被保留
613const module2 = import(moduleName)
614```
615
6163.在使用[动态路由](../ui/arkts-navigation-navigation.md#跨包动态路由)进行路由跳转时,传递给动态路由的路径应该被保留。动态路由提供系统路由表和自定义路由表两种方式。若采用自定义路由表进行跳转,配置白名单的方式与上述第二种动态引用场景一致。而若采用系统路由表进行跳转,则需要将模块下`resources/base/profile/route_map.json`文件中`pageSourceFile`字段对应的路径添加到白名单中。
617
618```
619  {
620    "routerMap": [
621      {
622        "name": "PageOne",
623        "pageSourceFile": "src/main/ets/pages/directory/PageOne.ets",  // 路径都应该被保留
624        "buildFunction": "PageOneBuilder",
625        "data": {
626          "description" : "this is PageOne"
627        }
628      }
629    ]
630  }
631```
632
633### -keep-comments
634
635保留编译生成的声明文件中class、function、namespace、enum、struct、interface、module、type及属性上方的JsDoc注释,支持使用[名称类通配符](#名称类通配符)。例如想保留声明文件中Human类上方的JsDoc注释,可进行以下配置:
636
637```
638-keep-comments
639Human
640```
641
642**使用该选项时,需要注意以下事项:**
643
644**1.** 该选项在开启`-remove-comments`时生效。
645
646**2.** 当编译生成的声明文件中class、function、namespace、enum、 struct、interface、module、type及属性的名称被混淆时,该元素上方的JsDoc注释无法通过`-keep-comments`保留。例如当在`-keep-comments`中配置了exportClass时,如果exportClass类名被混淆,其JsDoc注释无法被保留:
647
648```
649/*
650 * @class exportClass
651 */
652export class exportClass {}
653```
654
655### -keep-dts
656
657指定路径*filepath*的`.d.ts`文件中的名称(例如变量名、类名、属性名等)会被添加至`-keep-global-name`和`-keep-property-name`白名单中。请注意,*filepath*仅支持绝对路径,并且可以指定为一个目录。在这种情况下,该目录中所有`.d.ts`文件中的名称都将被保留。
658
659### -keep
660
661保留指定相对路径*filepath*中的所有名称(例如变量名、类名、属性名等)不被混淆。*filepath*可以是文件与文件夹,若是文件夹,则文件夹下的文件及子文件夹中文件都不混淆。
662*filepath*仅支持相对路径,`./`与`../`为相对于混淆配置文件所在目录,支持使用[路径类通配符](#路径类通配符)。
663
664```
665-keep
666./src/main/ets/fileName.ts   // fileName.ts中的名称不混淆
667../folder                    // folder目录下文件及子文件夹中的名称都不混淆
668../oh_modules/json5          // 引用的三方库json5里所有文件中的名称都不混淆
669```
670
671**如何在模块中保留远程HAR包**
672
673**方式一**:指定远程`HAR`包在模块级`oh_modules`中的具体路径(该路径为软链接路径,真实路径为工程级`oh_modules`中的文件路径)。因为在配置模块级`oh_modules`中的路径作为白名单时,需要具体到包名或之后的目录才能正确地软链接到真实的目录路径,所以不能仅配置`HAR`包的上级目录名称。
674
675```
676// 正例
677-keep
678./oh_modules/harName1         // harName1目录下所有文件及子文件夹中的名称都不混淆
679./oh_modules/harName1/src     // src目录下所有文件及子文件夹中的名称都不混淆
680./oh_modules/folder/harName2  // harName2目录下所有文件及子文件夹中的名称都不混淆
681
682// 反例
683-keep
684./oh_modules                  // 保留模块级oh_modules里HAR包时,不支持配置HAR包的上级目录名称
685```
686
687**方式二**:指定远程`HAR`包在工程级`oh_modules`中的具体路径。因为工程级`oh_modules`中的文件路径都为真实路径,所以其路径均可配置。
688
689```
690-keep
691../oh_modules                  // 工程级oh_modules目录下所有文件及子文件夹中的名称都不混淆
692../oh_modules/harName3          // harName3目录下所有文件及子文件夹中的名称都不混淆
693```
694
695模块级`oh_moudles`和工程级`oh_modules`在`DevEco Studio`中的目录结构如下图所示:
696
697![oh_modules](./figures/oh_modules.png)
698
699**使用该选项时,需要注意以下事项:**
700
701**1.** 被`-keep filepath`所保留的文件,其依赖链路上的文件中导出名称及其属性都会被保留。
702
703**2.** 该功能不影响文件名混淆`-enable-filename-obfuscation`的功能。
704
705### 保留选项支持的通配符
706
707#### 名称类通配符
708
709名称类通配符使用方式如下:
710
711| 通配符 | 含义                   | 示例                                       |
712| ------ | ---------------------- | ------------------------------------------ |
713| ?      | 匹配任意单个字符       | "AB?"能匹配"ABC"等,但不能匹配"AB"         |
714| \*     | 匹配任意数量的任意字符 | "\*AB\*"能匹配"AB"、"aABb"、"cAB"、"ABc"等 |
715
716**使用示例**:
717
718保留所有以a开头的属性名称:
719
720```
721-keep-property-name
722a*
723```
724
725保留所有单个字符的属性名称:
726
727```
728-keep-property-name
729?
730```
731
732保留所有属性名称:
733
734```
735-keep-property-name
736*
737```
738
739#### 路径类通配符
740
741路径类通配符使用方式如下:
742
743| 通配符 | 含义                                                                     | 示例                                              |
744| ------ | ------------------------------------------------------------------------ | ------------------------------------------------- |
745| ?     | 匹配任意单个字符,除了路径分隔符`/`                                      | "../a?"能匹配"../ab"等,但不能匹配"../a/"         |
746| \*      | 匹配任意数量的任意字符,除了路径分隔符`/`                                | "../a*/c"能匹配"../ab/c",但不能匹配"../ab/d/s/c" |
747| \*\*   | 匹配任意数量的任意字符                                                   | "../a**/c"能匹配"../ab/c",也能匹配"../ab/d/s/c"  |
748| !      | 表示非,只能写在某个路径最前端,用来排除用户配置的白名单中已有的某种情况 | "!../a/b/c.ets"表示除"../a/b/c.ets"以外           |
749
750**使用示例**:
751
752表示路径../a/b/中所有文件夹(不包含子文件夹)中的c.ets文件不会被混淆:
753
754```
755-keep
756../a/b/*/c.ets
757```
758
759表示路径../a/b/中所有文件夹(包含子文件夹)中的c.ets文件不会被混淆:
760
761```
762-keep
763../a/b/**/c.ets
764```
765
766表示路径../a/b/中,除了c.ets文件以外的其它文件都不会被混淆。其中,`!`不可单独使用,只能用来排除白名单中已有的情况:
767
768```
769-keep
770../a/b/
771!../a/b/c.ets
772```
773
774表示路径../a/中的所有文件(不包含子文件夹)不会被混淆:
775
776```
777-keep
778../a/*
779```
780
781表示路径../a/下的所有文件夹(包含子文件夹)中的所有文件不会被混淆:
782
783```
784-keep
785../a/**
786```
787
788表示模块内的所有文件不会被混淆:
789
790```
791-keep
792./**
793```
794
795**使用通配符时,需要注意以下事项:**
796
797**1.** 以上选项,不支持配置通配符`*`、`?`、`!`作其它含义使用。
798例如:
799
800```
801class A {
802  '*'= 1
803}
804
805-keep-property-name
806*
807```
808
809此时`*`表示匹配任意数量的任意字符,配置效果为所有属性名称都不混淆,而不是只有`*`属性不被混淆。
810
811**2.** -keep选项中只允许使用`/`路径格式,不支持`\`或`\\`。
812
813## 混淆规则合并策略
814
815在编译一个模块时,生效的混淆规则是**当前编译模块混淆规则**和**依赖模块混淆规则**的合并结果,具体规则如下:
816
817**当前编译模块混淆规则**
818指当前模块配置文件`build-profile.json5`中`arkOptions.obfuscation.ruleOptions.files`字段指定的混淆配置文件内容。
819
820**依赖模块混淆规则**
821根据依赖模块的类型,混淆规则分为以下两个来源:
822
823- **本地HAR模块**
824  指该模块配置文件`build-profile.json5`中`arkOptions.obfuscation.consumerFiles`字段指定的混淆配置文件内容。
825
826- **远程HAR包**
827  指该远程HAR包中`obfuscation.txt`文件内容。
828
829当构建HAP、HSP和HAR的时候,最终的混淆规则是下列文件的合并:
830* 当前构建模块的ruleOptions.files属性
831* 依赖的本地HAR的consumerFiles属性
832* 依赖的远程HAR中的obfuscation.txt文件
833
834如果构建的是HAR,生成的远程HAR中的obfuscation.txt是下列文件的合并:
835* 自身的consumerFiles属性
836* 依赖的本地HAR的consumerFiles属性
837* 依赖的远程HAR中的obfuscation.txt文件
838
839如果构建的是HAP或HSP,则不会生成obfuscation.txt840
841#### 混淆规则合并逻辑
842
843混淆选项:使用或运算进行合并,即开关选项只要在参与合并的任意一个规则文件中存在,最终的合并结果中就会包含该开关选项。
844保留选项:合并时,对于白名单选项,其内容取并集。
845
846最终的合并对象为当前模块的所有混淆规则与依赖模块的所有混淆规则。
847
848当`consumerFiles`指定的混淆配置文件中包含以下混淆规则时,这些混淆规则会被合并到远程HAR的`obfuscation.txt`文件中,而其他混淆规则不会。
849```
850// 混淆选项
851-enable-property-obfuscation
852-enable-string-property-obfuscation
853-enable-toplevel-obfuscation
854-compact
855-remove-log
856
857// 保留选项
858-keep-property-name
859-keep-global-name
860```
861
862**HAR中混淆注意事项**
863
8641. 如果`consumerFiles`指定的混淆配置文件中包含上述混淆选项,当其他模块依赖该模块的时候,这些混淆选项会与主模块的混淆规则合并,从而影响主模块。因此不建议开发者在`consumer-rules.txt`文件中配置混淆选项,建议仅配置保留选项。
865
8662. 如果在`consumerFiles`指定的混淆配置文件中添加`-keep-dts`选项,会被转换成`-keep-global-name`和`-keep-property-name`。
867
868## 混淆各功能上线SDK版本
869
870| 混淆选项 | 功能描述  | 最低版本号 |
871| ------- | --------- | ------ |
872| -disable-obfuscation         | 关闭混淆 | 4.0.9.2 |
873| -enable-property-obfuscation | 属性混淆 | 4.0.9.2 |
874| -enable-string-property-obfuscation | 字符串字面量属性名混淆 | 4.0.9.2 |
875| -enable-toplevel-obfuscation | 顶层作用域名称混淆 | 4.0.9.2 |
876| -enable-filename-obfuscation | HAR包文件/文件夹名称混淆 <br> HAP/HSP文件/文件夹名称混淆 | 4.1.5.3 <br> 5.0.0.19 |
877| -enable-export-obfuscation   | 向外导入或导出的名称混淆 | 4.1.5.3 |
878| -compact                     | 去除不必要的空格符和所有的换行符 | 4.0.9.2 |
879| -remove-log                  | 删除特定场景中的console.* | 4.0.9.2 |
880| -print-namecache             | 将名称缓存保存到指定的文件路径 | 4.0.9.2 |
881| -apply-namecache             | 复用指定的名称缓存文件 | 4.0.9.2 |
882| -remove-comments             | 删除文件中所有注释 | 4.1.5.3 |
883| -keep-property-name          | 保留属性名 | 4.0.9.2 |
884| -keep-global-name            | 保留顶层作用域的名称 | 4.0.9.2 |
885| -keep-file-name              | 保留HAR包的文件/文件夹的名称 <br> 保留HAP/HSP包的文件/文件夹的名称 | 4.1.5.3 <br> 5.0.0.19 |
886| -keep-dts                    | 保留指定路径的.d.ts文件中的名称 | 4.0.9.2 |
887| -keep-comments               | 保留编译生成的声明文件中class、function、namespace、enum、struct、interface、module、type及属性上方的JsDoc注释 | 4.1.5.3 |
888| -keep                        | 保留指定路径中的所有名称 | 5.0.0.18 |
889| 通配符                       | 名称类和路径类的保留选项支持通配符 | 5.0.0.24 |
890