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 * collect exist identifiers in current source file 67 * @param sourceFile 68 * @param context 69 */ 70export function collectIdentifiersAndStructs(sourceFile: SourceFile, context: TransformationContext): IdentifiersAndStructs { 71 const identifiers: Identifier[] = []; 72 const structs: StructDeclaration[] = []; 73 74 let visit = (node: Node): Node => { 75 if (isStructDeclaration(node)) { 76 structs.push(node); 77 } 78 // @ts-ignore 79 if (getOriginalNode(node).virtual) { 80 return node; 81 } 82 if (!isIdentifier(node) || !node.parent) { 83 return visitEachChild(node, visit, context); 84 } 85 86 identifiers.push(node); 87 return node; 88 }; 89 90 visit(sourceFile); 91 return {shadowIdentifiers: identifiers, shadowStructs: structs}; 92} 93 94export function isCommentedNode(node: Node, sourceFile: SourceFile): boolean { 95 const ranges: CommentRange[] = getLeadingCommentRangesOfNode(node, sourceFile); 96 return ranges !== undefined; 97} 98 99export function isSuperCallStatement(node: Node): boolean { 100 return isExpressionStatement(node) && 101 isCallExpression(node.expression) && 102 node.expression.expression.kind === SyntaxKind.SuperKeyword; 103} 104 105/** 106 * separate wildcards from specific items. 107 */ 108export function separateUniversalReservedItem(originalArray: string[]): ReservedNameInfo { 109 if (!originalArray) { 110 throw new Error('Unable to handle the empty array.'); 111 } 112 const reservedInfo: ReservedNameInfo = { 113 universalReservedArray: [], 114 specificReservedArray: [] 115 }; 116 117 originalArray.forEach(reservedItem => { 118 if (containWildcards(reservedItem)) { 119 const regexPattern = wildcardTransformer(reservedItem); 120 const regexOperator = new RegExp(`^${regexPattern}$`); 121 reservedInfo.universalReservedArray.push(regexOperator); 122 recordWildcardMapping(reservedItem, regexOperator); 123 } else { 124 reservedInfo.specificReservedArray.push(reservedItem); 125 } 126 }); 127 return reservedInfo; 128} 129 130function recordWildcardMapping(originString: string, regExpression: RegExp): void { 131 if (UnobfuscationCollections.printKeptName) { 132 UnobfuscationCollections.reservedWildcardMap.set(regExpression, originString); 133 } 134} 135 136/** 137 * check if the item contains '*', '?'. 138 */ 139export function containWildcards(item: string): boolean { 140 return /[\*\?]/.test(item); 141} 142 143/** 144 * Convert specific characters into regular expressions. 145 */ 146export function wildcardTransformer(wildcard: string, isPath?: boolean): string { 147 // Add an escape character in front of special characters 148 // special characters: '\', '^', '$', '.', '+', '|', '[', ']', '{', '}', '(', ')' 149 let escapedItem = wildcard.replace(/[\\+^${}()|\[\]\.]/g, '\\$&'); 150 151 // isPath: containing '**', and '*', '?' can not be matched with '/'. 152 if (isPath) { 153 // before: ../**/a/b/c*/?.ets 154 // after: ../.*/a/b/c[^/]*/[^/].ets 155 return escapedItem.replace(/\*\*/g, '.*').replace(/(?<!\.)\*/g, '[^/]*').replace(/\?/g, '[^/]'); 156 } 157 // before: *a? 158 // after: .*a. 159 return escapedItem.replace(/\*/g, '.*').replace(/\?/g, '.'); 160} 161 162/** 163 * Determine whether the original name needs to be preserved. 164 */ 165export function needToBeReserved(reservedSet: Set<string>, universalArray: RegExp[], originalName: string): boolean { 166 return reservedSet.has(originalName) || isMatchWildcard(universalArray, originalName); 167} 168 169/** 170 * Determine whether it can match the wildcard character in the array. 171 */ 172export function isMatchWildcard(wildcardArray: RegExp[], item: string): boolean { 173 for (const wildcard of wildcardArray) { 174 if (wildcard.test(item)) { 175 return true; 176 } 177 } 178 return false; 179} 180 181/** 182 * Separate parts of an array that contain wildcard characters. 183 */ 184export function handleReservedConfig(config: IOptions, optionName: string, reservedListName: string, 185 universalLisName: string, enableRemove?: string): void { 186 const reservedConfig = config?.[optionName]; 187 let needSeparate: boolean = !!(reservedConfig?.[reservedListName]); 188 if (enableRemove) { 189 needSeparate &&= reservedConfig[enableRemove]; 190 } 191 if (needSeparate) { 192 // separate items which contain wildcards from others 193 const reservedInfo: ReservedNameInfo = separateUniversalReservedItem(reservedConfig[reservedListName]); 194 reservedConfig[reservedListName] = reservedInfo.specificReservedArray; 195 reservedConfig[universalLisName] = reservedInfo.universalReservedArray; 196 } 197} 198 199export function isReservedLocalVariable(mangledName: string): boolean { 200 return LocalVariableCollections.reservedLangForLocal.has(mangledName) || 201 LocalVariableCollections.reservedConfig?.has(mangledName) || 202 LocalVariableCollections.reservedStruct?.has(mangledName) || 203 UnobfuscationCollections.reservedSdkApiForProp?.has(mangledName) || 204 UnobfuscationCollections.reservedExportName?.has(mangledName); 205} 206 207export function isReservedTopLevel(originalName: string): boolean { 208 if (PropCollections.enablePropertyObfuscation) { 209 return isReservedProperty(originalName); 210 } 211 212 // The 'mReservedToplevelNames' has already been added to 'PropCollections.reservedProperties'. 213 return UnobfuscationCollections.reservedLangForTopLevel.has(originalName) || 214 UnobfuscationCollections.reservedSdkApiForGlobal?.has(originalName) || 215 UnobfuscationCollections.reservedExportName?.has(originalName) || 216 PropCollections.reservedProperties?.has(originalName) || 217 isMatchWildcard(PropCollections.universalReservedProperties, originalName); 218} 219 220export function isReservedProperty(originalName: string): boolean { 221 return UnobfuscationCollections.reservedSdkApiForProp?.has(originalName) || 222 UnobfuscationCollections.reservedLangForProperty?.has(originalName) || 223 UnobfuscationCollections.reservedStruct?.has(originalName) || 224 UnobfuscationCollections.reservedExportNameAndProp?.has(originalName) || 225 UnobfuscationCollections.reservedStrProp?.has(originalName) || 226 UnobfuscationCollections.reservedEnum?.has(originalName) || 227 PropCollections.reservedProperties?.has(originalName) || 228 isMatchWildcard(PropCollections.universalReservedProperties, originalName); 229} 230 231 /** 232 * Reasons for not being obfuscated. 233 */ 234export enum WhitelistType { 235 SDK = 'sdk', 236 LANG = 'lang', 237 CONF = 'conf', 238 STRUCT = 'struct', 239 EXPORT = 'exported', 240 STRPROP = 'strProp', 241 ENUM = 'enum' 242} 243 244function needToRecordTopLevel(originalName: string, recordMap: Map<string, Set<string>>, nameWithScope: string): boolean { 245 if (PropCollections.enablePropertyObfuscation) { 246 return needToRecordProperty(originalName, recordMap, nameWithScope); 247 } 248 249 let reservedFlag = false; 250 if (UnobfuscationCollections.reservedLangForTopLevel.has(originalName)) { 251 recordReservedName(nameWithScope, WhitelistType.LANG, recordMap); 252 reservedFlag = true; 253 } 254 255 if (UnobfuscationCollections.reservedSdkApiForGlobal?.has(originalName)) { 256 recordReservedName(nameWithScope, WhitelistType.SDK, recordMap); 257 reservedFlag = true; 258 } 259 260 if (UnobfuscationCollections.reservedExportName?.has(originalName)) { 261 recordReservedName(nameWithScope, WhitelistType.EXPORT, recordMap); 262 reservedFlag = true; 263 } 264 265 // The 'mReservedToplevelNames' has already been added to 'PropCollections.reservedProperties'. 266 if (PropCollections.reservedProperties?.has(originalName) || 267 isMatchWildcard(PropCollections.universalReservedProperties, originalName)) { 268 recordReservedName(nameWithScope, WhitelistType.CONF, recordMap); 269 reservedFlag = true; 270 } 271 272 return reservedFlag; 273} 274 275function needToReservedLocal(originalName: string, recordMap: Map<string, Set<string>>, nameWithScope: string): boolean { 276 let reservedFlag = false; 277 278 if (LocalVariableCollections.reservedLangForLocal.has(originalName)) { 279 recordReservedName(nameWithScope, WhitelistType.LANG, recordMap); 280 reservedFlag = true; 281 } 282 283 if (UnobfuscationCollections.reservedSdkApiForLocal?.has(originalName)) { 284 recordReservedName(nameWithScope, WhitelistType.SDK, recordMap); 285 reservedFlag = true; 286 } 287 288 if (UnobfuscationCollections.reservedExportName?.has(originalName)) { 289 recordReservedName(nameWithScope, WhitelistType.EXPORT, recordMap); 290 reservedFlag = true; 291 } 292 293 if (LocalVariableCollections.reservedConfig?.has(originalName)) { 294 recordReservedName(nameWithScope, WhitelistType.CONF, recordMap); 295 reservedFlag = true; 296 } 297 298 if (LocalVariableCollections.reservedStruct?.has(originalName)) { 299 recordReservedName(nameWithScope, WhitelistType.STRUCT, recordMap); 300 reservedFlag = true; 301 } 302 303 return reservedFlag; 304} 305 306/** 307 * If the property name is in the whitelist, record the reason for not being obfuscated. 308 * @param nameWithScope: If both property obfuscation and top-level obfuscation or export obfuscation are enabled, 309 * this interface is also used to record the reasons why the top-level names or export names were not obfuscated, 310 * and the top-level names or export names include the scope. 311 */ 312export function needToRecordProperty(originalName: string, recordMap?: Map<string, Set<string>>, nameWithScope?: string): boolean { 313 let reservedFlag = false; 314 let recordName = nameWithScope ? nameWithScope : originalName; 315 if (UnobfuscationCollections.reservedSdkApiForProp?.has(originalName)) { 316 recordReservedName(recordName, WhitelistType.SDK, recordMap); 317 reservedFlag = true; 318 } 319 320 if (UnobfuscationCollections.reservedLangForProperty?.has(originalName)) { 321 recordReservedName(recordName, WhitelistType.LANG, recordMap); 322 reservedFlag = true; 323 } 324 325 if (UnobfuscationCollections.reservedStruct?.has(originalName)) { 326 recordReservedName(recordName, WhitelistType.STRUCT, recordMap); 327 reservedFlag = true; 328 } 329 330 if (UnobfuscationCollections.reservedExportNameAndProp?.has(originalName)) { 331 recordReservedName(recordName, WhitelistType.EXPORT, recordMap); 332 reservedFlag = true; 333 } 334 335 if (UnobfuscationCollections.reservedStrProp?.has(originalName)) { 336 recordReservedName(recordName, WhitelistType.STRPROP, recordMap); 337 reservedFlag = true; 338 } 339 340 if (UnobfuscationCollections.reservedEnum?.has(originalName)) { 341 recordReservedName(recordName, WhitelistType.ENUM, recordMap); 342 reservedFlag = true; 343 } 344 345 if (PropCollections.reservedProperties?.has(originalName) || 346 isMatchWildcard(PropCollections.universalReservedProperties, originalName)) { 347 recordReservedName(recordName, WhitelistType.CONF, recordMap); 348 reservedFlag = true; 349 } 350 351 return reservedFlag; 352} 353 354export function isInTopLevelWhitelist(originalName: string, recordMap: Map<string, Set<string>>, nameWithScope: string): boolean { 355 if (UnobfuscationCollections.printKeptName) { 356 return needToRecordTopLevel(originalName, recordMap, nameWithScope); 357 } 358 359 return isReservedTopLevel(originalName); 360} 361 362export function isInPropertyWhitelist(originalName: string, recordMap: Map<string, Set<string>>): boolean { 363 if (UnobfuscationCollections.printKeptName) { 364 return needToRecordProperty(originalName, recordMap); 365 } 366 367 return isReservedProperty(originalName); 368} 369 370export function isInLocalWhitelist(originalName: string, recordMap: Map<string, Set<string>>, nameWithScope: string): boolean { 371 if (UnobfuscationCollections.printKeptName) { 372 return needToReservedLocal(originalName, recordMap, nameWithScope); 373 } 374 375 return isReservedLocalVariable(originalName); 376} 377 378export function recordReservedName(originalName: string, type: string, recordObj?: Map<string, Set<string>>): void { 379 if (!UnobfuscationCollections.printKeptName || !recordObj) { 380 return; 381 } 382 if (!recordObj.has(originalName)) { 383 recordObj.set(originalName, new Set()); 384 } 385 recordObj.get(originalName).add(type); 386} 387 388export function recordHistoryUnobfuscatedNames(nameWithScope: string): void { 389 if (historyUnobfuscatedNamesMap?.has(nameWithScope)) { 390 UnobfuscationCollections.unobfuscatedNamesMap.set(nameWithScope, 391 new Set(historyUnobfuscatedNamesMap.get(nameWithScope))); 392 } 393}