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}