• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 *     http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16import {
17  forEachChild,
18  getLeadingCommentRangesOfNode,
19  isCallExpression,
20  isExpressionStatement,
21  isIdentifier,
22  isStructDeclaration,
23  SyntaxKind,
24  visitEachChild
25} from 'typescript';
26
27import type {
28  CommentRange,
29  Identifier,
30  Node,
31  SourceFile,
32  StructDeclaration,
33  TransformationContext
34} from 'typescript';
35import type { IOptions } from '../configs/IOptions';
36import { LocalVariableCollections, PropCollections, UnobfuscationCollections } from './CommonCollections';
37import { historyUnobfuscatedNamesMap } from '../transformers/rename/RenameIdentifierTransformer';
38
39export interface ReservedNameInfo {
40  universalReservedArray: RegExp[]; // items contain wildcards
41  specificReservedArray: string[]; // items do not contain wildcards
42}
43
44/**
45 * collect exist identifier names in current source file
46 * @param sourceFile
47 */
48export function collectExistNames(sourceFile: SourceFile): Set<string> {
49  const identifiers: Set<string> = new Set<string>();
50
51  let visit = (node: Node): void => {
52    if (isIdentifier(node)) {
53      identifiers.add(node.text);
54    }
55
56    forEachChild(node, visit);
57  };
58
59  forEachChild(sourceFile, visit);
60  return identifiers;
61}
62
63type IdentifiersAndStructs = {shadowIdentifiers: Identifier[], shadowStructs: StructDeclaration[]};
64
65/**
66 * separate wildcards from specific items.
67 */
68export function separateUniversalReservedItem(originalArray: string[] | undefined,
69  shouldPrintKeptName: boolean): ReservedNameInfo {
70  if (!originalArray) {
71    throw new Error('Unable to handle the empty array.');
72  }
73  const reservedInfo: ReservedNameInfo = {
74    universalReservedArray: [],
75    specificReservedArray: []
76  };
77
78  originalArray.forEach(reservedItem => {
79    if (containWildcards(reservedItem)) {
80      const regexPattern = wildcardTransformer(reservedItem);
81      const regexOperator = new RegExp(`^${regexPattern}$`);
82      reservedInfo.universalReservedArray.push(regexOperator);
83      recordWildcardMapping(reservedItem, regexOperator, shouldPrintKeptName);
84    } else {
85      reservedInfo.specificReservedArray.push(reservedItem);
86    }
87  });
88  return reservedInfo;
89}
90
91function recordWildcardMapping(originString: string, regExpression: RegExp,
92  shouldPrintKeptName: boolean): void {
93  if (shouldPrintKeptName) {
94    UnobfuscationCollections.reservedWildcardMap.set(regExpression, originString);
95  }
96}
97
98/**
99 * check if the item contains '*', '?'.
100 */
101export function containWildcards(item: string): boolean {
102  return /[\*\?]/.test(item);
103}
104
105/**
106 * Convert specific characters into regular expressions.
107 */
108export function wildcardTransformer(wildcard: string, isPath?: boolean): string {
109  // Add an escape character in front of special characters
110  // special characters: '\', '^', '$', '.', '+', '|', '[', ']', '{', '}', '(', ')'
111  let escapedItem = wildcard.replace(/[\\+^${}()|\[\]\.]/g, '\\$&');
112
113  // isPath: containing '**', and '*', '?' can not be matched with '/'.
114  if (isPath) {
115    // before: ../**/a/b/c*/?.ets
116    // after: ../.*/a/b/c[^/]*/[^/].ets
117    return escapedItem.replace(/\*\*/g, '.*').replace(/(?<!\.)\*/g, '[^/]*').replace(/\?/g, '[^/]');
118  }
119  // before: *a?
120  // after: .*a.
121  return escapedItem.replace(/\*/g, '.*').replace(/\?/g, '.');
122}
123
124/**
125 * Determine whether the original name needs to be preserved.
126 */
127export function needToBeReserved(reservedSet: Set<string>, universalArray: RegExp[], originalName: string): boolean {
128  return reservedSet.has(originalName) || isMatchWildcard(universalArray, originalName);
129}
130
131/**
132 * Determine whether it can match the wildcard character in the array.
133 */
134export function isMatchWildcard(wildcardArray: RegExp[], item: string): boolean {
135  for (const wildcard of wildcardArray) {
136    if (wildcard.test(item)) {
137      return true;
138    }
139  }
140  return false;
141}
142
143/**
144 * Separate parts of an array that contain wildcard characters.
145 */
146export function handleReservedConfig(config: IOptions, optionName: string, reservedListName: string,
147  universalLisName: string, enableRemove?: string): void {
148  const reservedConfig = config?.[optionName];
149  let needSeparate: boolean = !!(reservedConfig?.[reservedListName]);
150  if (enableRemove) {
151    needSeparate &&= reservedConfig[enableRemove];
152  }
153  if (needSeparate) {
154    // separate items which contain wildcards from others
155    const reservedInfo: ReservedNameInfo =
156      separateUniversalReservedItem(reservedConfig[reservedListName], !!(config.mUnobfuscationOption?.mPrintKeptNames));
157    reservedConfig[reservedListName] = reservedInfo.specificReservedArray;
158    reservedConfig[universalLisName] = reservedInfo.universalReservedArray;
159  }
160}
161
162export function isReservedLocalVariable(mangledName: string): boolean {
163  return LocalVariableCollections.reservedLangForLocal.has(mangledName) ||
164    LocalVariableCollections.reservedConfig?.has(mangledName) ||
165    UnobfuscationCollections.reservedSdkApiForLocal?.has(mangledName) ||
166    UnobfuscationCollections.reservedExportName?.has(mangledName);
167}
168
169export function isReservedTopLevel(originalName: string, enablePropertyObf: boolean): boolean {
170  if (enablePropertyObf) {
171    return isReservedProperty(originalName);
172  }
173
174  // The 'mReservedToplevelNames' has already been added to 'PropCollections.reservedProperties'.
175  return UnobfuscationCollections.reservedLangForTopLevel.has(originalName) ||
176    UnobfuscationCollections.reservedSdkApiForGlobal?.has(originalName) ||
177    UnobfuscationCollections.reservedExportName?.has(originalName) ||
178    PropCollections.reservedProperties?.has(originalName) ||
179    isMatchWildcard(PropCollections.universalReservedProperties, originalName);
180}
181
182export function isReservedProperty(originalName: string): boolean {
183  return UnobfuscationCollections.reservedSdkApiForProp?.has(originalName) ||
184    UnobfuscationCollections.reservedLangForProperty?.has(originalName) ||
185    UnobfuscationCollections.reservedStruct?.has(originalName) ||
186    UnobfuscationCollections.reservedExportNameAndProp?.has(originalName) ||
187    UnobfuscationCollections.reservedStrProp?.has(originalName) ||
188    UnobfuscationCollections.reservedEnum?.has(originalName) ||
189    PropCollections.reservedProperties?.has(originalName) ||
190    isMatchWildcard(PropCollections.universalReservedProperties, originalName);
191}
192
193  /**
194   * Reasons for not being obfuscated.
195   */
196export enum WhitelistType {
197  SDK = 'sdk',
198  LANG = 'lang',
199  CONF = 'conf',
200  STRUCT = 'struct',
201  EXPORT = 'exported',
202  STRPROP = 'strProp',
203  ENUM = 'enum'
204}
205
206function needToRecordTopLevel(originalName: string, recordMap: Map<string, Set<string>>,
207  nameWithScope: string, enablePropertyObf: boolean): boolean {
208  if (enablePropertyObf) {
209    return needToRecordProperty(originalName, recordMap, nameWithScope);
210  }
211
212  let reservedFlag = false;
213  if (UnobfuscationCollections.reservedLangForTopLevel.has(originalName)) {
214    recordReservedName(nameWithScope, WhitelistType.LANG, recordMap);
215    reservedFlag = true;
216  }
217
218  if (UnobfuscationCollections.reservedSdkApiForGlobal?.has(originalName)) {
219    recordReservedName(nameWithScope, WhitelistType.SDK, recordMap);
220    reservedFlag = true;
221  }
222
223  if (UnobfuscationCollections.reservedExportName?.has(originalName)) {
224    recordReservedName(nameWithScope, WhitelistType.EXPORT, recordMap);
225    reservedFlag = true;
226  }
227
228  // The 'mReservedToplevelNames' has already been added to 'PropCollections.reservedProperties'.
229  if (PropCollections.reservedProperties?.has(originalName) ||
230    isMatchWildcard(PropCollections.universalReservedProperties, originalName)) {
231    recordReservedName(nameWithScope, WhitelistType.CONF, recordMap);
232    reservedFlag = true;
233  }
234
235  return reservedFlag;
236}
237
238function needToReservedLocal(originalName: string, recordMap: Map<string, Set<string>>, nameWithScope: string): boolean {
239  let reservedFlag = false;
240
241  if (LocalVariableCollections.reservedLangForLocal.has(originalName)) {
242    recordReservedName(nameWithScope, WhitelistType.LANG, recordMap);
243    reservedFlag = true;
244  }
245
246  if (UnobfuscationCollections.reservedSdkApiForLocal?.has(originalName)) {
247    recordReservedName(nameWithScope, WhitelistType.SDK, recordMap);
248    reservedFlag = true;
249  }
250
251  if (UnobfuscationCollections.reservedExportName?.has(originalName)) {
252    recordReservedName(nameWithScope, WhitelistType.EXPORT, recordMap);
253    reservedFlag = true;
254  }
255
256  if (LocalVariableCollections.reservedConfig?.has(originalName)) {
257    recordReservedName(nameWithScope, WhitelistType.CONF, recordMap);
258    reservedFlag = true;
259  }
260
261  return reservedFlag;
262}
263
264/**
265 * If the property name is in the whitelist, record the reason for not being obfuscated.
266 * @param nameWithScope: If both property obfuscation and top-level obfuscation or export obfuscation are enabled,
267 * this interface is also used to record the reasons why the top-level names or export names were not obfuscated,
268 * and the top-level names or export names include the scope.
269 */
270export function needToRecordProperty(originalName: string, recordMap?: Map<string, Set<string>>, nameWithScope?: string): boolean {
271  let reservedFlag = false;
272  let recordName = nameWithScope ? nameWithScope : originalName;
273  if (UnobfuscationCollections.reservedSdkApiForProp?.has(originalName)) {
274    recordReservedName(recordName, WhitelistType.SDK, recordMap);
275    reservedFlag = true;
276  }
277
278  if (UnobfuscationCollections.reservedLangForProperty?.has(originalName)) {
279    recordReservedName(recordName, WhitelistType.LANG, recordMap);
280    reservedFlag = true;
281  }
282
283  if (UnobfuscationCollections.reservedStruct?.has(originalName)) {
284    recordReservedName(recordName, WhitelistType.STRUCT, recordMap);
285    reservedFlag = true;
286  }
287
288  if (UnobfuscationCollections.reservedExportNameAndProp?.has(originalName)) {
289    recordReservedName(recordName, WhitelistType.EXPORT, recordMap);
290    reservedFlag = true;
291  }
292
293  if (UnobfuscationCollections.reservedStrProp?.has(originalName)) {
294    recordReservedName(recordName, WhitelistType.STRPROP, recordMap);
295    reservedFlag = true;
296  }
297
298  if (UnobfuscationCollections.reservedEnum?.has(originalName)) {
299    recordReservedName(recordName, WhitelistType.ENUM, recordMap);
300    reservedFlag = true;
301  }
302
303  if (PropCollections.reservedProperties?.has(originalName) ||
304    isMatchWildcard(PropCollections.universalReservedProperties, originalName)) {
305    recordReservedName(recordName, WhitelistType.CONF, recordMap);
306    reservedFlag = true;
307  }
308
309  return reservedFlag;
310}
311
312export function isInTopLevelWhitelist(originalName: string, recordMap: Map<string, Set<string>>,
313  nameWithScope: string, enablePropertyObf: boolean, shouldPrintKeptNames: boolean): boolean {
314  if (shouldPrintKeptNames) {
315    return needToRecordTopLevel(originalName, recordMap, nameWithScope, enablePropertyObf);
316  }
317
318  return isReservedTopLevel(originalName, enablePropertyObf);
319}
320
321export function isInPropertyWhitelist(originalName: string, recordMap: Map<string, Set<string>>,
322  shouldPrintKeptNames: boolean): boolean {
323  if (shouldPrintKeptNames) {
324    return needToRecordProperty(originalName, recordMap);
325  }
326
327  return isReservedProperty(originalName);
328}
329
330export function isInLocalWhitelist(originalName: string, recordMap: Map<string, Set<string>>,
331  nameWithScope: string, shouldPrintKeptNames: boolean): boolean {
332  if (shouldPrintKeptNames) {
333    return needToReservedLocal(originalName, recordMap, nameWithScope);
334  }
335
336  return isReservedLocalVariable(originalName);
337}
338
339function recordReservedName(originalName: string, type: string, recordObj: Map<string, Set<string>>): void {
340  if (!recordObj.has(originalName)) {
341    recordObj.set(originalName, new Set());
342  }
343  recordObj.get(originalName).add(type);
344}
345
346export function recordHistoryUnobfuscatedNames(nameWithScope: string): void {
347  if (historyUnobfuscatedNamesMap?.has(nameWithScope)) {
348    UnobfuscationCollections.unobfuscatedNamesMap.set(nameWithScope,
349      new Set(historyUnobfuscatedNamesMap.get(nameWithScope)));
350  }
351}