• 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相比之下,ArkGuard混淆工具主要针对JS、TS和ArkTS语言。JS支持运行时动态修改对象、函数,而混淆是在编译阶段进行的静态处理,这种差异可能导致混淆后的名称在运行时无法被正确解析,进而引发运行时异常。TS和ArkTS虽然引入了静态类型系统,但采用了结构性类型机制,即具有相同结构的不同命名类型会被视为等价类型。因此,在TS和ArkTS中仍然无法追溯类型的确切来源。
34基于这些特性,使用ArkGuard时需要对更多的语法场景进行白名单配置,同时,ArkGuard采用全局生效的属性保留机制,根据白名单统一保留所有同名属性,而无法支持针对特定类型的精确保留配置。
35
36具体而言,可以参考以下示例:
37
38假设ArkGuard支持配置指定类型的白名单,配置类A1作为白名单,类A1的属性prop1在白名单中,而A2中的prop1属性不在白名单中。a2作为参数被传入test函数中,并在test函数内访问其属性。混淆前,可以正常访问prop1属性,混淆后,由于A1的属性prop1没有被混淆,但A2的prop1属性被混淆,会导致在test函数中访问prop1属性时功能异常。
39因此,ArkGuard无法支持针对特定类型精确保留配置。
40
41```typescript
42// 混淆前
43class A1 {
44  prop1: string = '';
45}
46
47class A2 {
48  prop1: string = '';
49}
50
51function test(input: A1) {
52  console.log(input.prop1);
53}
54
55let a2 = new A2();
56a2.prop1 = 'prop a2';
57test(a2);
58```
59
60```typescript
61// 混淆后
62class A1 {
63  prop1: string = '';
64}
65
66class A2 {
67  a: string = '';
68}
69
70function test(input: A1) {
71  console.log(input.prop1);
72}
73
74let a2 = new A2();
75a2.a = 'prop a2';
76test(a2);
77```
78
79综上所述,开发者应了解语言差异对混淆效果的影响,并尽量使用不重复的名称,以优化各种场景下的混淆效果。
80
81**2.安全保证的有限性**
82
83与其他代码混淆工具一样,混淆只能在一定程度上增加逆向过程的难度,并不能真正阻止逆向工程。
84
85并且,由于ArkGuard混淆工具仅支持基础混淆功能,开发者不应只依赖ArkGuard来保证应用的安全性,对于源码安全有高要求的开发者,应考虑使用[应用加密](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/code-protect)、第三方安全加固等安全措施来保护代码。
86
87## 混淆机制及流程
88
89下图为应用编译的简要流程图:
90
91![compilation-process](figures/compilation-process.png)
92
93开发者可以在模块的build-profile.json5配置文件中开启混淆功能,详细参考[ArkGuard混淆开启指南](source-obfuscation-guide.md),从而在编译打包的过程中自动对源码进行混淆处理。
94
95混淆过程中,首先读取混淆开关。在开关开启的情况下,解析混淆配置文件,并依据[混淆规则合并策略](#混淆规则合并策略)合并混淆规则。然后按照混淆规则对经过语法转换的中间文件进行混淆,最后将混淆后的中间文件落盘至build目录。开发者可以通过build目录中混淆后的产物,确认混淆效果。
96
97在使用混淆功能前,建议开发者先通过文档了解[混淆选项的能力](source-obfuscation.md#混淆选项)与[混淆选项所需要保留白名单的场景](source-obfuscation.md#保留选项),再根据开发需求选择对应的混淆功能。
98
99
100## 混淆选项
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| 输出未混淆名单 | [`-print-kept-names`](#-print-kept-names) |
118| 缩减语言预置白名单 | [`-extra-options strip-language-default`](#-extra-options-strip-language-default) |
119| 缩减系统预置白名单 | [`-extra-options strip-system-api-args`](#-extra-options-strip-system-api-args) |
120| 保留声明文件参数 | [`-keep-parameter-names`](#-keep-parameter-names) |
121| 合并依赖模块选项 | [`-enable-lib-obfuscation-options`](#-enable-lib-obfuscation-options) |
122| 通过注释在源码中标记白名单 | [`-use-keep-in-source`](#-use-keep-in-source) |
123
124### -disable-obfuscation
125
126关闭所有混淆。
127
128若配置该选项,那么默认混淆(局部变量及参数名)以及所有已配置的混淆、保留选项的功能将全部失效。
129
130### -enable-property-obfuscation
131
132开启属性名称混淆,效果如下:
133
134  ```
135  // 混淆前:
136  class TestA {
137    static prop1: number = 0;
138  }
139  TestA.prop1;
140  ```
141
142  ```
143  // 混淆后:
144  class TestA {
145    static i: number = 0;
146  }
147  TestA.i;
148  ```
149
150若配置该选项,所有属性名将被混淆,以下场景除外:
151
152* 在未开启`-enable-export-obfuscation`选项的情况下,被`import/export`直接导入或导出的类、对象的属性名不会被混淆。例如下面例子中的属性名`data`不会被混淆。
153
154    ```
155    export class MyClass {
156       data: string;
157    }
158    ```
159
160* ArkUI组件中的属性名不会被混淆。例如下面例子中的`message`和`data`不会被混淆。
161
162    ```
163    @Component struct MyExample {
164        @State message: string = "hello";
165        data: number[] = [];
166        // ...
167    }
168    ```
169
170* 被[保留选项](#-keep-property-name)指定的属性名不会被混淆。
171* SDK API列表中的属性名不会被混淆。SDK API列表是构建时从SDK中自动提取出来的一个名称列表,其缓存文件为systemApiCache.json,路径为工程目录下build/default/cache/{...}/release/obfuscation中。
172* 字符串字面量属性名不会被混淆。例如下面例子中的`"exampleName"`和`"exampleAge"`或者其他不在白名单的内容不会被混淆。
173
174    ```
175    let person = {"exampleName": "abc"};
176    person["exampleAge"] = 22;
177    ```
178
179### -enable-string-property-obfuscation
180
181开启字符串属性混淆,仅在已开启属性混淆的基础上生效。
182
183若想混淆字符串字面量属性名,需要在已配置`-enable-property-obfuscation`的基础上使用。例如:
184
185  ```
186  -enable-property-obfuscation
187  -enable-string-property-obfuscation
188  ```
189
190根据上述配置,下面例子中的`"exampleName"`和`"exampleAge"`或者其他不在白名单的内容混淆效果如下:
191
192  ```
193  // 混淆前:
194  let person = {"exampleName": "abc"};
195  person["exampleAge"] = 22;
196  ```
197
198  ```
199  // 混淆后:
200  let person = {"a": "abc"};
201  person["b"] = 22;
202  ```
203
204
205
206**使用该选项时,需要注意以下事项:**
207
208**1.** 如果代码里面有字符串属性名包含特殊字符(除了`a-z、A-Z、0-9、_`之外的字符),例如`let obj = {"\n": 123, "": 4, " ": 5}`,建议不要开启`-enable-string-property-obfuscation`选项,因为可能无法通过[保留选项](#-keep-property-name)来指定保留这些名字。
209
210**2.** SDK API的属性白名单中不包含声明文件中使用的字符串常量值,例如示例中的字符串'ohos.want.action.home'未包含在属性白名单中:
211```
212// SDK API文件@ohos.app.ability.wantConstant片段:
213export enum Params {
214  ACTION_HOME = 'ohos.want.action.home'
215}
216// 开发者源码示例:
217let params = obj['ohos.want.action.home'];
218```
219
220因此在开启了`-enable-string-property-obfuscation`选项时,如果想保留代码中使用的SDK API字符串常量的属性不被混淆,例如obj['ohos.want.action.home'],那么需要使用[-keep-property-name选项](#-keep-property-name)保留。
221
222### -enable-toplevel-obfuscation
223
224开启顶层作用域名称混淆,效果如下:
225
226  ```
227  // 混淆前:
228  let count = 0;
229  ```
230
231  ```
232  // 混淆后:
233  let s = 0;
234  ```
235
236若配置该选项,所有顶层作用域的名称都会被混淆,以下场景除外:
237
238* 在未开启`-enable-export-obfuscation`选项的情况下,被`import/export`直接导入或导出的名称不会被混淆。
239* 当前文件找不到声明的名称不会被混淆。
240* 被[保留选项](#-keep-global-name)指定的顶层作用域名称不会被混淆。
241* SDK API列表中的顶层作用域名称不会被混淆。
242
243### -enable-export-obfuscation
244
245开启直接导入或导出的名称混淆,效果如下:
246
247  ```
248  // 混淆前:
249  namespace ns {
250    export type customT = string;
251  }
252  ```
253
254  ```
255  // 混淆后:
256  namespace ns {
257    export type h = string;
258  }
259  ```
260
261若配置该选项,那么非顶层作用域中导入或导出的名称会被混淆。**若想混淆顶层作用域中导入或导出的名称,需要在已配置`-enable-toplevel-obfuscation`的基础上使用;若想混淆导入或导出的属性名,需要在已配置`-enable-property-obfuscation`的基础上使用。** 开启此选项时,以下特殊场景不会被混淆:
262
263* 远程HAR(真实路径在oh_modules中的包)中导出的名称和属性名不会被混淆。
264* 被[保留选项](#保留选项)指定的名称与属性名不会被混淆。
265* SDK API列表中的名称不会被混淆。
266
267### -enable-filename-obfuscation
268
269开启文件/文件夹名称混淆,效果如下:
270
271  ```
272  // 混淆前:
273  import * as m from '../test1/test2';
274  import { foo } from '../test1/test2';
275  const module = import('../test1/test2');
276  ```
277
278
279  ```
280  // 混淆后:
281  import * as m from '../a/b';
282  import { foo } from '../a/b';
283  const module = import('../a/b');
284  ```
285
286若配置该选项,所有文件和文件夹名称都将被混淆,以下场景除外:
287
288* oh-package.json5文件中'main'、'types'字段配置的文件/文件夹名称不会被混淆。
289* 模块内module.json5文件中'srcEntry'字段配置的文件/文件夹名称不会被混淆。
290* 被[-keep-file-name](#-keep-file-name)指定的文件/文件夹名称不会被混淆。
291* 非ECMAScript模块引用方式(例如:`const module = require('./module')`)。
292* 非路径引用方式,例如`import module from 'json5'`中的`json5`不会被混淆。
293
294>**注意**:
295>
296>由于系统会在应用运行时加载某些指定的文件,针对这类文件,开发者需要手动在[-keep-file-name](#-keep-file-name)选项中配置相应的白名单,防止指定文件被混淆,导致运行失败。
297>
298>编译入口、Ability组件、Worker多线程,这三种不能混淆的文件名在DevEco Studio 5.0.3.500及以上版本已被自动收集进白名单中,无需再手动配置,其它不能混淆文件名的场景仍需开发者手动配置。
299
300### -compact
301
302删除不必要的空格符和所有的换行符。
303
304若配置该选项,所有代码会被压缩到一行。效果如下:
305
306  ```
307  // 混淆前:
308  class TestA {
309    static prop1: number = 0;
310  }
311  TestA.prop1;
312  ```
313
314  ```
315  // 混淆后:
316  class TestA { static prop1: number = 0; } TestA.prop1;
317  ```
318
319>**注意**:
320>
321>release模式构建的应用栈信息仅包含代码行号,不包含列号,因此-compact功能开启后无法依据报错栈中的行号定位到源码具体位置。
322
323### -remove-comments
324
325删除编译生成的声明文件中的JsDoc注释,效果如下:
326
327混淆前:
328  ```
329  /**
330   * @todo
331   */
332  declare let count: number;
333  ```
334
335混淆后:
336  ```
337  declare let count: number;
338  ```
339
340可以通过[`-keep-comments`](#-keep-comments)配置来保留编译生成的声明文件中的JsDoc注释。
341
342>**注意**:
343>
344>编译生成的源码文件中的注释默认会被全部删除,不支持配置保留。
345
346### -remove-log
347
348删除对console.*语句的调用,要求console.*语句返回值未被使用,效果如下:
349
350  ```
351  // 混淆前:
352  if (flag) {
353    console.log("hello");
354  }
355  ```
356
357  ```
358  // 混淆后:
359  if (flag) {
360  }
361  ```
362
363若配置该选项,以下场景中的console.*语句会被删除:
364
3651. 文件顶层的调用。
3662. 代码块中的调用。
367   例如:
368   ```
369   function foo() {
370    console.log('in block');
371   }
372   ```
3733. module或namespace中的调用。
374   例如:
375   ```
376   namespace ns {
377    console.log('in ns');
378   }
379   ```
3804. switch语句中的调用。
381
382### -print-namecache
383
384将名称缓存保存到指定的文件路径*filepath*中,名称缓存包含名称混淆前后的映射。其中,*filepath*为必选参数,支持相对路径和绝对路径,相对路径的起始位置为混淆配置文件的当前目录。*filepath*参数中的文件名请以`.json`为后缀。
385
386例如:
387```
388-print-namecache
389./customCache/nameCache.json
390```
391
392>**注意**:
393>
394>每次全量构建工程都会生成新的nameCache.json文件,因此发布新版本时需保存该文件的副本。
395
396### -apply-namecache
397
398复用指定的名称缓存文件*filepath*。其中,*filepath*为必选参数,支持相对路径和绝对路径,相对路径的起始位置为混淆配置文件的当前目录。*filepath*参数中的文件名请以`.json`为后缀。
399该选项适用于增量编译场景。开启该选项后,名称将会被混淆成缓存映射对应的名称,若找不到对应的缓存,则会被混淆成新的随机名称。
400
401例如:
402```
403-apply-namecache
404./customCache/nameCache.json
405```
406
407默认情况下,DevEco Studio会在临时的缓存目录中保存缓存文件,并且在增量编译场景中自动应用该缓存文件。
408缓存目录:build/default/cache/{...}/release/obfuscation409
410### -print-kept-names
411
412该选项支持输出未混淆名单和全量白名单,且支持配置*filepath*。其中,*filepath*为可选参数,仅支持相对路径,相对路径的起始位置为混淆配置文件的当前目录。*filepath*参数中的文件名请以`.json`为后缀。
413
414当*filepath*参数缺省时,未混淆名单(keptNames.json)和全量白名单(whitelist.json)默认输出到缓存路径`build/default/cache/{...}/release/obfuscation`中。
415
416当*filepath*配置参数时,未混淆名单还会输出到该参数指定的路径中。
417
418一次全量编译流程中收集到的全部白名单,分为以下七种:
419
420(1)'sdk':表示系统api。
421
422(2)'lang':表示语言中的关键字。
423
424(3)'conf':表示用户配置的保留选项中的白名单。
425
426(4)'struct':表示ArkUI的struct中的属性。
427
428(5)'exported':表示被导出的名称及其属性。
429
430(6)'strProp':表示字符串属性。
431
432(7)'enum':表示enum中的成员。
433
434其中,'sdk'类白名单单独输出到缓存路径`build/default/cache/{...}/release/obfuscation/`下的`systemApiCache.json`文件中,其他类型白名单则都输出到`whitelist.json`文件中。
435
436未混淆名单(keptNames.json)中包含未混淆的名称及未混淆的原因。其中,未混淆原因有以下七种:与sdk白名单重名、与语言白名单重名、与用户配置白名单重名、与struct白名单重名、与导出白名单重名、与字符串属性白名单重名(未开启字符串属性混淆的情况下)以及与enum白名单重名。
437
438**使用该选项时,需要注意以下事项:**
439
440**1.** 在编译HAR模块且开启属性混淆的情况下,'enum'白名单将收集enum中的成员名称。
441例如:
442```
443enum Test {
444  member1,
445  member2
446}
447```
448enum白名单内容为['member1', 'member2']。这是由于历史版本的har模块的编译中间产物为js文件,在js文件中enum类型会转换为一个立即执行函数,而enum成员会被转化为一个字符串属性和一个字符串常量。因此,为了保证开启属性混淆的情况下功能正常,需要将enum成员名称收集为白名单。在编译新版字节码har模块时,此特性仍然被保留。
449
450**2.** 在编译HAP/HSP/字节码HAR模块且开启属性混淆的情况下,当enum的成员被初始化时,'enum'白名单收集初始化表达式中包含的变量名称。
451例如:
452```
453let outdoor = 1;
454enum Test {
455  member1,
456  member2 = outdoor + member1 + 2
457}
458```
459其中,编译HAP/HSP模块的情况下,enum白名单内容为['outdoor', 'member1'];编译字节码HAR模块的情况下,enum白名单内容为['outdoor', 'member1', 'member2']。
460
461### -extra-options strip-language-default
462
463混淆的预置语言白名单中**默认包含了typescript的系统接口中关于dom、webworker、scriphost等API的名称以及Web API的名称**。如果开发者源码中的属性与这部分名称重名,混淆工具会对这些属性进行保留。
464
465如果开发者需要混淆这部分代码,需要配置`-extra-options strip-language-default`选项。
466
467开发者可通过以下方式确定混淆工具默认保留的API的具体减少范围:
468
469开启`-print-kept-names`选项,对比开启和关闭`-extra-options strip-language-default`选项时全量白名单(whitelist.json)中`lang`字段的内容差异,该差异即为预置语言白名单的具体减少范围。
470
471### -extra-options strip-system-api-args
472
473当前混淆的系统API白名单中**默认包含了系统API中的局部变量名称**,且系统API白名单默认对开发者源码中的局部变量生效。如果开发者源码中的属性与系统API中的局部变量重名或源码中的局部变量与系统API白名单重名,混淆工具会对这部分属性和局部变量名称进行保留。
474
475如果开发者需要混淆这部分代码,需要配置`-extra-options strip-system-api-args`选项。
476
477系统API白名单文件(systemApiCache.json)的ReservedLocalNames、ReservedPropertyNames和ReservedGlobalNames字段可以查看系统API白名单的具体内容。系统API白名单文件位于模块目录下build/default/cache/{...}/release/obfuscation路径中,记录了SDK中的接口与属性名称,与其重名的源码不会被混淆。
478
479开发者可通过以下方式确定系统白名单减少的具体范围:
480
481通过对比开启和关闭`-extra-options strip-system-api-args`选项时系统API白名单文件(systemApiCache.json)中ReservedLocalNames和ReservedPropertyNames字段的内容差异,该差异即为系统白名单的具体减少范围,ReservedGlobalNames字段的内容不会产生变化。
482
483**使用-extra-options选项的方法如下**:
484
485在混淆配置文件中添加`-extra-options`前缀和选项,且前缀与选项之间不能包含其他内容。支持开启单个选项或同时开启两个选项。例如:
486
487单个选项:
488
489```
490-extra-options
491strip-language-default
492
493-extra-options strip-language-default
494```
495
496同时开启两个选项:
497
498```
499-extra-options strip-language-default, strip-system-api-args
500
501-extra-options strip-language-default strip-system-api-args
502
503-extra-options strip-language-default
504-extra-options strip-system-api-args
505```
506
507### -keep-parameter-names
508保留声明文件中对外接口的参数名称。开启此选项后,有如下效果:
509- 对于函数与类中成员方法,如果函数或方法名称没有被混淆,则保留其参数名称。
510- 对于类的构造器,如果类名没有被混淆,则保留构造器中的参数名称。
511
512**使用该选项时,需要注意以下事项:**
513
514**1.** 对于非上述场景(如匿名函数)中的参数名称,无法通过此选项保留。
515
516**2.** 源码文件中的参数名称仍然会被混淆,无法通过此选项保留。
517
518### -enable-lib-obfuscation-options
519配置此开关后,依赖模块的混淆选项将被合并到当前编译模块的混淆配置中。
520
521混淆配置分为[混淆选项](#混淆选项)和[保留选项](#保留选项):
522- **默认情况下**,生效的混淆配置为当前编译模块的混淆配置与依赖模块的保留选项的合并结果。
523- **启用该开关后**,生效的混淆配置为当前编译模块的混淆配置与依赖模块的混淆配置的合并结果。
524
525混淆规则合并逻辑参考[混淆规则合并策略](#混淆规则合并策略)。
526
527### -use-keep-in-source
528
529支持在`.ts`/`.ets`源码中通过以下两种注释标记白名单(不支持声明文件):
530
531`// @KeepSymbol`:用来标记需要保留的名称,通常写在代码上一行,表示该名称在编译时不会被混淆。
532
533`// @KeepAsConsumer`:用来标记需要保留的名称,通常写在代码上一行,表示该名称在编译时不会被混淆。在HAR/HSP模块中,被@KeepAsConsumer标记的名称还会生成在obfuscation.txt中;在HAP模块中,@KeepAsConsumer和@KeepSymbol的效果相同。
534
535**当前支持这两种标记的语法如下:**
536
537注:以下均以`// @KeepSymbol`为例,`// @KeepAsConsumer`支持的场景和`// @KeepSymbol`相同。
538
539#### 类
540
541当前支持对类中的以下语法进行标记:
542
543- 类声明
544- 构造函数
545- 字段和方法
546
547**示例**
548
549```typescript
550// 保留类名和所有成员名。
551// @KeepSymbol
552class MyClass01 {
553  prop01: string = "prop"; // MyClass01和prop01不会被混淆。
554}
555
556// 通过构造函数保留类名。
557class MyClass02 {
558  prop02: string = "prop";
559  // @KeepSymbol
560  constructor() {}; // MyClass02不会被混淆。
561}
562
563// 保留类名和指定的字段名和方法,类中MyClass03,prop03_1,method03_2不会被混淆。
564class MyClass03 {
565  // @KeepSymbol
566  prop03_1: string = "prop";
567  prop03_2: number = 1;
568  constructor() {};
569
570  method03_1(): void {};
571  // @KeepSymbol
572  method03_2(): void {};
573}
574```
575
576#### 接口
577
578当前支持对接口中的以下语法进行标记:
579
580- 接口声明
581- 字段和方法
582
583**示例**
584
585```typescript
586// 保留接口名和所有成员名,MyInterface01,name01,foo01不会被混淆。
587// @KeepSymbol
588interface MyInterface01 {
589  name01: string;
590  foo01(): void;
591}
592
593// 保留接口名和指定的字段和方法名,MyInterface02,name02不会被混淆。
594interface MyInterface02 {
595  // @KeepSymbol
596  name02: string;
597  foo02(): void;
598}
599```
600
601#### 枚举
602
603当前支持对枚举中的以下语法进行标记:
604
605- 枚举声明
606- 枚举成员
607
608**示例**
609
610```typescript
611// 保留枚举名和所有成员名,Color01,RED01,BLUE01不会被混淆。
612// @KeepSymbol
613enum Color01 {
614  RED01,
615  BLUE01
616}
617
618// 保留枚举名指定的枚举成员名。
619enum Color02 {
620  RED02,
621  // @KeepSymbol
622  BLUE02 // Color02,BLUE02不会被混淆。
623}
624```
625
626#### 函数
627
628当前支持对函数名进行标记。
629
630**示例**
631
632```typescript
633// 保留函数名,MyAdd不会被混淆。
634// @KeepSymbol
635function MyAdd(a: number, b:number): number {
636  return a + b;
637}
638```
639
640#### 命名空间
641
642当前支持对命名空间名称进行标记。
643
644**示例**
645
646```typescript
647// 保留命名空间名以及内部直接导出的成员名称,MyNameSpace以及foo不会被混淆。
648// @KeepSymbol
649namespace MyNameSpace {
650  export function foo(){};
651  function bar(){};
652}
653```
654
655#### 全局变量
656
657当前仅支持全局变量的标记,不支持局部变量。
658
659**示例**
660
661```typescript
662// 保留被标记的变量名,myVal不会被混淆。
663// @KeepSymbol
664const myVal = 1;
665```
666
667#### 白名单添加规则
668
669被标记的名称,根据以下规则添加到混淆白名单,被KeepAsConsumer保留的名称,还会额外生成到`obfuscation.txt`文件中:
670
671* 如果该名称在top-level或者被直接export,则会被添加到-keep-global-name中。
672
673* 如果该名称被直接export,还会被添加到-keep-property-name中。
674
675* 如果该名称是属性,则会被添加到-keep-property-name中。
676
677* 局部变量名不会被添加到白名单(不会被保留)。
678
679**示例**
680
681```typescript
682// @KeepAsConsumer
683export class MyClass {
684  prop01: string = "prop";
685}
686```
687此时`MyClass`会被添加到-keep-global-name以及-keep-property-name中,`prop01`则会被添加到-keep-property-name中,同时,该规则还会被写入`obfuscation.txt`文件中。
688
689#### -use-keep-in-source不支持的场景
690
691暂不支持字符串属性、数字属性以及计算属性。
692
693**示例**
694
695```typescript
696const myMethodName = "myMethod";
697
698// 11,aa,myMethod不会被收集到白名单中
699class MyClass01 {
700  // @KeepSymbol
701  11:11;
702  // @KeepSymbol
703  'aa':'aa';
704  // @KeepSymbol
705  [myMethodName](){}
706}
707
708// RED不会被收集到白名单中
709enum MyEnum {
710  // @KeepSymbol
711  'RED',
712  BLUE
713}
714```
715
716## 保留选项
717
718### 已有保留选项汇总
719
720| 功能 | 选项 |
721| --- | --- |
722| 指定保留属性名称 | [`-keep-property-name`](#-keep-property-name) |
723| 指定保留顶层作用域或导入导出元素名称 | [`-keep-global-name`](#-keep-global-name) |
724| 指定保留文件/文件夹名称 | [`-keep-file-name`](#-keep-file-name) |
725| 指定保留注释 | [`-keep-comments`](#-keep-comments) |
726| 指定保留声明文件中的所有名称 | [`-keep-dts`](#-keep-dts) |
727| 指定保留源码文件中的所有名称 | [`-keep`](#-keep) |
728
729### -keep-property-name
730
731指定想保留的属性名,支持使用[名称类通配符](#名称类通配符)。按如下方式进行配置,表示保留名称为`age`、`firstName`和`lastName`的属性:
732
733```
734-keep-property-name
735age
736firstName
737lastName
738```
739
740**使用该选项时,需要注意以下事项:**
741
742**1.** 该选项在开启`-enable-property-obfuscation`时生效。
743
744**2.** 属性白名单作用于全局。即代码中出现多个重名属性,只要与`-keep-property-name`配置白名单名称相同,均不会被混淆。
745
746**哪些属性名应该被保留?**
747
7481.如果代码中通过字符串拼接、变量访问或使用`defineProperty`方法来定义对象属性,则这些属性名应被保留。例如:
749
750```
751var obj = {x0: 0, x1: 0, x2: 0};
752for (var i = 0; i <= 2; i++) {
753    console.info(obj['x' + i]);  // x0, x1, x2应该被保留
754}
755
756Object.defineProperty(obj, 'y', {});  // y应该被保留
757Object.getOwnPropertyDescriptor(obj, 'y');  // y应该被保留
758console.info(obj.y);
759
760obj.s = 0;
761let key = 's';
762console.info(obj[key]);        // key对应的变量值s应该被保留
763
764obj.t1 = 0;
765console.info(obj['t' + '1']);        // t1应该被保留
766```
767
768对于如下的字符串常量形式的属性调用,可以选择性保留:
769
770```
771// 混淆配置:
772// -enable-property-obfuscation
773// -enable-string-property-obfuscation
774
775obj.t = 0;
776console.info(obj['t']); // 此时,'t'会被正确混淆,t可以选择性保留
777
778obj.['v'] = 0;
779console.info(obj['v']); // 此时,'v'会被正确混淆,v可以选择性保留
780```
781
7822.对于间接导出的场景,例如`export MyClass`和`let a = MyClass; export {a};`,如果不想混淆属性名,需要使用[保留选项](#保留选项)来保留这些属性名。此外,对于直接导出的类或对象的属性的属性名,例如下面例子中的`name`和`age`,如果不想混淆它们,也需要使用[保留选项](#保留选项)来保留这些属性名。
783
784```
785export class MyClass {
786    person = {name: "123", age: 100};
787}
788```
789
7903.在ArkTS/TS/JS文件中使用so库的API(如示例中的foo)时,需手动保留API名称。
791
792```
793import testNapi from 'library.so'
794testNapi.foo() // foo需要保留,示例如:-keep-property-name foo
795```
796
7974.JSON数据解析及对象序列化时,需要保留使用到的字段,例如:
798
799```
800// 示例JSON文件结构(test.json):
801/*
802{
803  "jsonProperty": "value",
804  "otherProperty": "value2"
805}
806*/
807
808const jsonData = fs.readFileSync('./test.json', 'utf8');
809let jsonObj = JSON.parse(jsonData);
810let jsonProp = jsonObj.jsonProperty; // jsonProperty应该被保留
811
812class jsonTest {
813  prop1: string = '';
814  prop2: number = 0
815}
816
817let obj = new jsonTest();
818const jsonStr = JSON.stringify(obj); // prop1、prop2会被混淆,应该被保留
819```
820
8215.使用到的数据库相关的字段,需要手动保留。例如,数据库键值对类型(ValuesBucket)中的属性:
822
823```
824const valueBucket: ValuesBucket = {
825  'ID1': ID1, // ID1应该被保留
826  'NAME1': name, // NAME1应该被保留
827  'AGE1': age, // AGE1应该被保留
828  'SALARY1': salary // SALARY1应该被保留
829}
830```
831
8326.源码中自定义装饰器修饰了成员变量、成员方法、参数,同时其源码编译的中间产物为js文件时(如编译release源码HAR或者源码包含@ts-ignore、@ts-nocheck),这些装饰器所在的成员变量/成员方法名称需要被保留。这是由于ts高级语法特性转换为js标准语法时,将上述装饰器所在的成员变量/成员方法名称硬编码为字符串常量。
833
834示例:
835
836```
837class A {
838  // 1.成员变量装饰器
839  @CustomDecoarter
840  propertyName: string = ""   // propertyName 需要被保留
841  // 2.成员方法装饰器
842  @MethodDecoarter
843  methodName1(){} // methodName1 需要被保留
844  // 3.方法参数装饰器
845  methodName2(@ParamDecorator param: string): void { // methodName2 需要被保留
846  }
847}
848```
849
850### -keep-global-name
851
852指定要保留的顶层作用域或导入和导出元素的名称,支持使用[名称类通配符](#名称类通配符)。可按如下方式进行配置:
853
854```
855-keep-global-name
856Person
857printPersonName
858```
859
860`namespace`中导出的名称也可以通过`-keep-global-name`选项保留,示例如下:
861
862```
863export namespace Ns {
864  export const age = 18; // -keep-global-name age 保留变量age
865  export function myFunc () {}; // -keep-global-name myFunc 保留函数myFunc
866}
867```
868
869> **注意**
870>
871> `-keep-global-name`指定的白名单作用于全局。即代码中出现多个顶层作用域名称或者导出名称,只要与`-keep-global-name`配置的白名单名称相同,均不会被混淆。
872
873**哪些顶层作用域的名称应该被保留?**
874
8751.在JavaScript中,全局变量是`globalThis`的属性。使用`globalThis`访问全局变量时,应保留该变量名。
876
877示例:
878
879```
880var a = 0;
881console.info(globalThis.a);  // a 应该被保留
882
883function foo(){}
884globalThis.foo();           // foo 应该被保留
885
886var c = 0;
887console.info(c);             // c 可以被正确地混淆
888
889function bar(){}
890bar();                      // bar 可以被正确地混淆
891
892class MyClass {}
893let d = new MyClass();      // MyClass 可以被正确地混淆
894```
895
8962.当以命名导入的方式导入 so 库的 API 时,若同时开启`-enable-toplevel-obfuscation`和`-enable-export-obfuscation`选项,需要手动保留 API 的名称。
897
898```
899import { testNapi, testNapi1 as myNapi } from 'library.so' // testNapi 和 testNapi1 应该被保留
900```
901
902### -keep-file-name
903
904指定要保留的文件/文件夹的名称(不需要写文件后缀),支持使用[名称类通配符](#名称类通配符)。例如:
905
906```
907-keep-file-name
908index
909entry
910```
911
912**哪些文件名应该被保留?**
913
9141.在使用`require`引入文件路径时,由于`ArkTS`不支持[CommonJS](../arkts-utils/module-principle.md#commonjs模块)语法,因此这种情况下路径应该被保留。
915
916```
917const module1 = require('./file1')   // file1 应该被保留
918```
919
9202.对于动态导入的路径名,由于无法识别`import`函数中的参数是否为路径,因此这种情况下路径应该被保留。
921
922```
923const moduleName = './file2'         // moduleName对应的路径名file2应该被保留
924const module2 = import(moduleName)
925```
926
9273.在使用[动态路由](../ui/arkts-navigation-navigation.md#跨包动态路由)进行路由跳转时,传递给动态路由的路径应该被保留。动态路由提供系统路由表和自定义路由表两种方式。若采用自定义路由表进行跳转,配置白名单的方式与上述第二种动态引用场景一致。而若采用系统路由表进行跳转,则需要将模块下`resources/base/profile/route_map.json`文件中`pageSourceFile`字段对应的路径添加到白名单中。
928
929```
930  {
931    "routerMap": [
932      {
933        "name": "PageOne",
934        "pageSourceFile": "src/main/ets/pages/directory/PageOne.ets",  // 路径都应该被保留
935        "buildFunction": "PageOneBuilder",
936        "data": {
937          "description" : "this is PageOne"
938        }
939      }
940    ]
941  }
942```
943
944### -keep-comments
945
946保留编译生成的声明文件中class、function、namespace、enum、struct、interface、module、type及属性上方的JsDoc注释,支持使用[名称类通配符](#名称类通配符)。例如想保留声明文件中Human类上方的JsDoc注释,可进行以下配置:
947
948```
949-keep-comments
950Human
951```
952
953**使用该选项时,需要注意以下事项:**
954
955**1.** 该选项在开启`-remove-comments`时生效。
956
957**2.** 当编译生成的声明文件中class、function、namespace、enum、 struct、interface、module、type及属性的名称被混淆时,该元素上方的JsDoc注释无法通过`-keep-comments`保留。例如当在`-keep-comments`中配置了exportClass时,如果exportClass类名被混淆,其JsDoc注释无法被保留:
958
959```
960/*
961 * @class exportClass
962 */
963export class exportClass {}
964```
965
966### -keep-dts
967
968指定路径*filepath*的`.d.ts`文件中的名称(例如变量名、类名、属性名等)会被添加至`-keep-global-name`和`-keep-property-name`白名单中。请注意,*filepath*仅支持绝对路径,并且可以指定为一个目录。在这种情况下,该目录中所有`.d.ts`文件中的名称都将被保留。
969
970### -keep
971
972保留指定相对路径*filepath*中的所有名称(例如变量名、类名、属性名等)不被混淆。*filepath*可以是文件或文件夹,若是文件夹,则文件夹下的文件及子文件夹中文件都不混淆。
973*filepath*仅支持相对路径,`./`与`../`为相对于混淆配置文件所在目录,支持使用[路径类通配符](#路径类通配符)。
974
975```
976-keep
977./src/main/ets/fileName.ts   // fileName.ts中的名称不混淆
978../folder                    // folder目录下文件及子文件夹中的名称都不混淆
979../oh_modules/json5          // 引用的三方库json5里所有文件中的名称都不混淆
980```
981
982**如何在模块中保留远程HAR包**
983
984**方式一**:指定远程`HAR`包在模块级`oh_modules`中的具体路径(该路径为软链接路径,真实路径为工程级`oh_modules`中的文件路径)。因为在配置模块级`oh_modules`中的路径作为白名单时,需要具体到包名或之后的目录才能正确地软链接到真实的目录路径,所以不能仅配置`HAR`包的上级目录名称。
985
986```
987// 正例
988-keep
989./oh_modules/harName1         // harName1目录下所有文件及子文件夹中的名称都不混淆
990./oh_modules/harName1/src     // src目录下所有文件及子文件夹中的名称都不混淆
991./oh_modules/folder/harName2  // harName2目录下所有文件及子文件夹中的名称都不混淆
992
993// 反例
994-keep
995./oh_modules                  // 保留模块级oh_modules里HAR包时,不支持配置HAR包的上级目录名称
996```
997
998**方式二**:指定远程`HAR`包在工程级`oh_modules`中的具体路径。工程级`oh_modules`中的文件路径均为真实路径,可直接配置。
999
1000```
1001-keep
1002../oh_modules                  // 工程级oh_modules目录下所有文件及子文件夹中的名称都不混淆
1003../oh_modules/harName3          // harName3目录下所有文件及子文件夹中的名称都不混淆
1004```
1005
1006模块级`oh_modules`和工程级`oh_modules`在`DevEco Studio`中的目录结构如下图所示:
1007
1008![oh_modules](./figures/oh_modules.png)
1009
1010**使用该选项时,需要注意以下事项:**
1011
1012**1.** 被`-keep filepath`所保留的文件,其依赖链路上的文件中导出名称及其属性都会被保留。
1013
1014**2.** 该功能不影响文件名混淆`-enable-filename-obfuscation`的功能。
1015
1016### 保留选项支持的通配符
1017
1018#### 名称类通配符
1019
1020名称类通配符使用方式如下:
1021
1022| 通配符 | 含义                   | 示例                                       |
1023| ------ | ---------------------- | ------------------------------------------ |
1024| ?      | 匹配任意单个字符       | "AB?"能匹配"ABC"等,但不能匹配"AB"。        |
1025| \*     | 匹配任意数量的任意字符 | "\*AB\*"能匹配"AB"、"aABb"、"cAB"、"ABc"等。 |
1026
1027**使用示例**:
1028
1029保留所有以a开头的属性名称:
1030
1031```
1032-keep-property-name
1033a*
1034```
1035
1036保留所有单个字符的属性名称:
1037
1038```
1039-keep-property-name
1040?
1041```
1042
1043保留所有属性名称:
1044
1045```
1046-keep-property-name
1047*
1048```
1049
1050#### 路径类通配符
1051
1052路径类通配符使用方式如下:
1053
1054| 通配符 | 含义                                                                     | 示例                                              |
1055| ------ | ------------------------------------------------------------------------ | ------------------------------------------------- |
1056| ?     | 匹配任意单个字符,除了路径分隔符`/`。                                      | "../a?"能匹配"../ab"等,但不能匹配"../a/"。         |
1057| \*      | 匹配任意数量的任意字符,除了路径分隔符`/`。                                | "../a*/c"能匹配"../ab/c",但不能匹配"../ab/d/s/c"。 |
1058| \*\*   | 匹配任意数量的任意字符。                                                   | "../a**/c"能匹配"../ab/c",也能匹配"../ab/d/s/c"。  |
1059| !      | 表示非,只能写在某个路径最前端,用来排除用户配置的白名单中已有的某种情况。 | "!../a/b/c.ets"表示除"../a/b/c.ets"以外。           |
1060
1061**使用示例**:
1062
1063表示路径../a/b/中所有文件夹(不包含子文件夹)中的c.ets文件不会被混淆:
1064
1065```
1066-keep
1067../a/b/*/c.ets
1068```
1069
1070表示路径../a/b/中所有文件夹(包含子文件夹)中的c.ets文件不会被混淆:
1071
1072```
1073-keep
1074../a/b/**/c.ets
1075```
1076
1077表示路径../a/b/中,除了c.ets文件以外的其它文件都不会被混淆。其中,`!`不可单独使用,只能用来排除白名单中已有的情况:
1078
1079```
1080-keep
1081../a/b/
1082!../a/b/c.ets
1083```
1084
1085表示路径../a/中的所有文件(不包含子文件夹)不会被混淆:
1086
1087```
1088-keep
1089../a/*
1090```
1091
1092表示路径../a/下的所有文件夹(包含子文件夹)中的所有文件不会被混淆:
1093
1094```
1095-keep
1096../a/**
1097```
1098
1099表示模块内的所有文件不会被混淆:
1100
1101```
1102-keep
1103./**
1104```
1105
1106**使用通配符时,需要注意以下事项:**
1107
1108**1.** 以上选项不支持将通配符 `*`、`?`、`!` 用作其他含义。
1109例如:
1110
1111```
1112class A {
1113  '*'= 1
1114}
1115
1116-keep-property-name
1117*
1118```
1119
1120此时`*`表示匹配任意数量的任意字符,配置效果为所有属性名称都不混淆,而不是只有`*`属性不被混淆。
1121
1122**2.** -keep选项中只允许使用`/`路径格式,不支持`\`或`\\`。
1123
1124## 混淆规则合并策略
1125
1126在编译一个模块时,生效的混淆规则是**当前编译模块混淆规则**和**依赖模块混淆规则**的合并结果,具体规则如下:
1127
1128**当前编译模块混淆规则**
1129指当前模块配置文件`build-profile.json5`中`arkOptions.obfuscation.ruleOptions.files`字段指定的混淆配置文件内容。
1130
1131**依赖模块混淆规则**
1132根据依赖模块的类型,混淆规则分为以下两个来源:
1133
1134- **本地HAR/HSP模块**
1135  指该模块配置文件`build-profile.json5`中`arkOptions.obfuscation.consumerFiles`字段指定的混淆配置文件内容。
1136
1137- **远程HAR/HSP包**
1138  指该远程HAR/HSP包中`obfuscation.txt`文件内容。
1139
1140当构建HAP、HSP和HAR的时候,最终的混淆规则是下列文件的合并:
1141* 当前构建模块的ruleOptions.files属性。
1142* 依赖的本地HSP的consumerFiles属性。
1143* 依赖的本地HAR的consumerFiles属性。
1144* 依赖的远程HAR和远程HSP中的obfuscation.txt文件。
1145
1146如果构建的是HAR,生成的远程HAR中的obfuscation.txt是下列文件的合并:
1147* 自身的consumerFiles属性。
1148* 依赖的本地HSP的consumerFiles属性。
1149* 依赖的本地HAR的consumerFiles属性。
1150* 依赖的远程HAR和远程HSP中的obfuscation.txt文件。
1151
1152如果构建的是HSP,生成的远程HSP中的obfuscation.txt仅包含自身的consumerFiles属性。
1153如果构建的是HAP,则不会生成obfuscation.txt文件。
1154
1155#### 混淆规则合并逻辑
1156
1157混淆选项:使用或运算进行合并,即开关选项只要在参与合并的任意一个规则文件中存在,最终的合并结果中就会包含该开关选项。
1158保留选项:合并时,对于白名单选项,其内容取并集。
1159
1160- **如果当前编译模块混淆配置未包含`-enable-lib-obfuscation-options`选项**:合并对象为当前模块的所有混淆规则与依赖模块混淆规则中的[保留选项](#保留选项)。
1161
1162- **如果当前编译模块混淆配置包含`-enable-lib-obfuscation-options`选项**:合并对象为当前模块的所有混淆规则与依赖模块的所有混淆规则。
1163
1164当`consumerFiles`指定的混淆配置文件中包含以下混淆规则时,这些混淆规则会被合并到远程HAR和远程HSP的`obfuscation.txt`文件中,而其他混淆规则不会。
1165```
1166// 混淆选项
1167-enable-property-obfuscation
1168-enable-string-property-obfuscation
1169-enable-toplevel-obfuscation
1170-compact
1171-remove-log
1172
1173// 保留选项
1174-keep-property-name
1175-keep-global-name
1176```
1177
1178**HSP和HAR中混淆注意事项**
1179
11801. 如果`consumerFiles`指定的混淆配置文件中包含上述混淆选项,当其他模块依赖该模块时,这些混淆选项会与主模块的混淆规则合并,从而影响主模块。因此不建议开发者在`consumer-rules.txt`文件中配置混淆选项,建议仅配置保留选项。
1181
11822. 如果在`consumerFiles`指定的混淆配置文件中添加`-keep-dts`选项,该选项会被转换成`-keep-global-name`和`-keep-property-name`。
1183
1184## 混淆各功能上线SDK版本
1185
1186| 混淆选项 | 功能描述  | 最低版本号 |
1187| ------- | --------- | ------ |
1188| -disable-obfuscation         | 关闭混淆 | 4.0.9.2 |
1189| -enable-property-obfuscation | 属性混淆 | 4.0.9.2 |
1190| -enable-string-property-obfuscation | 字符串字面量属性名混淆 | 4.0.9.2 |
1191| -enable-toplevel-obfuscation | 顶层作用域名称混淆 | 4.0.9.2 |
1192| -enable-filename-obfuscation | HAR包文件/文件夹名称混淆 <br> HAP/HSP文件/文件夹名称混淆 | 4.1.5.3 <br> 5.0.0.19 |
1193| -enable-export-obfuscation   | 向外导入或导出的名称混淆 | 4.1.5.3 |
1194| -compact                     | 去除不必要的空格符和所有的换行符 | 4.0.9.2 |
1195| -remove-log                  | 删除特定场景中的console.* | 4.0.9.2 |
1196| -print-namecache             | 将名称缓存保存到指定的文件路径 | 4.0.9.2 |
1197| -apply-namecache             | 复用指定的名称缓存文件 | 4.0.9.2 |
1198| -remove-comments             | 删除文件中所有注释 | 4.1.5.3 |
1199| -keep-property-name          | 保留属性名 | 4.0.9.2 |
1200| -keep-global-name            | 保留顶层作用域的名称 | 4.0.9.2 |
1201| -keep-file-name              | 保留HAR包的文件/文件夹的名称 <br> 保留HAP/HSP包的文件/文件夹的名称 | 4.1.5.3 <br> 5.0.0.19 |
1202| -keep-dts                    | 保留指定路径的.d.ts文件中的名称 | 4.0.9.2 |
1203| -keep-comments               | 保留编译生成的声明文件中class、function、namespace、enum、struct、interface、module、type及属性上方的JsDoc注释 | 4.1.5.3 |
1204| -keep                        | 保留指定路径中的所有名称 | 5.0.0.18 |
1205| 通配符                       | 名称类和路径类的保留选项支持通配符 | 5.0.0.24 |
1206| -use-keep-in-source          | 通过注释在源码中标记白名单 | 5.1.0.57 |
1207