1/* 2 * Copyright (c) 2025 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 { ApiExtractor } from '../common/ApiExtractor'; 17import { FileUtils } from './FileUtils'; 18import { 19 AtIntentCollections, 20 AtKeepCollections, 21 BytecodeObfuscationCollections, 22 UnobfuscationCollections 23} from './CommonCollections'; 24import * as crypto from 'crypto'; 25import * as ts from 'typescript'; 26import fs from 'fs'; 27import path from 'path'; 28 29export function addToSet<T>(targetSet: Set<T>, sourceSet: Set<T>): void { 30 sourceSet.forEach((element) => targetSet.add(element)); 31} 32 33export function arrayToSet<T>(array: T[]): Set<T> { 34 return new Set(array); 35} 36 37export function setToArray<T>(set: Set<T>): T[] { 38 return Array.from(set); 39} 40 41export function areSetsEqual<T>(setA: Set<T>, setB: Set<T>): boolean { 42 if (setA.size !== setB.size) { 43 return false; 44 } 45 for (const setItem of setA) { 46 if (!setB.has(setItem)) { 47 return false; 48 } 49 } 50 return true; 51} 52 53export const SOURCE_FILE_PATHS: string = 'sourceFilePaths.cache'; 54export const TRANSFORMED_PATH: string = 'transformed'; 55export const FILE_NAMES_MAP: string = 'transformedFileNamesMap.json'; 56export const FILE_WHITE_LISTS: string = 'fileWhiteLists.json'; 57export const PROJECT_WHITE_LIST: string = 'projectWhiteList.json'; 58 59// this while list is only used for bytecode obfuscation 60export const DECORATOR_WHITE_LIST = [ 61 'Monitor', 62 'Track', 63 'Trace', 64 'AnimatableExtend' 65]; 66 67export interface KeepInfo { 68 propertyNames: Set<string>; 69 globalNames: Set<string>; 70} 71 72/** 73 * Informantion of build files 74 */ 75export interface ModuleInfo { 76 content: string; 77 /** 78 * The path in build cache dir 79 */ 80 buildFilePath: string; 81 /** 82 * The `originSourceFilePath` relative to project root dir. 83 */ 84 relativeSourceFilePath: string; 85 /** 86 * The origin source file path will be set with rollup moduleId when obfuscate intermediate js source code, 87 * whereas be set with tsc node.fileName when obfuscate intermediate ts source code. 88 */ 89 originSourceFilePath?: string; 90 rollupModuleId?: string; 91} 92 93export interface FileContent { 94 moduleInfo: ModuleInfo; 95 previousStageSourceMap?: ts.RawSourceMap; 96} 97 98/** 99 * We have these structure to store project white list and file white lists, 100 * project white list is merged by each file white list. 101 * 102 * We have keepinfo and reservedinfo in white lists: 103 * KeepInfo: names that we cannot obfuscate or obfuscate as. 104 * ReservedInfo: names that we cannot obfuscate as. 105 * 106 * ProjectWhiteList 107 * ├── projectKeepInfo: ProjectKeepInfo 108 * │ ├── propertyNames: Set<string> 109 * │ └── globalNames: Set<string> 110 * └── projectReservedInfo: ProjectReservedInfo 111 * ├── enumProperties: Set<string> 112 * └── propertyParams: Set<string> 113 * FileWhiteList 114 * ├── fileKeepInfo: FileKeepInfo 115 * │ ├── keepSymbol?: KeepInfo (optional) 116 * │ │ ├── propertyNames: Set<string> 117 * │ │ └── globalNames: Set<string> 118 * │ ├── keepAsConsumer?: KeepInfo (optional) 119 * │ │ ├── propertyNames: Set<string> 120 * │ │ └── globalNames: Set<string> 121 * │ ├── structProperties: Set<string> 122 * │ ├── exported: KeepInfo 123 * │ │ ├── propertyNames: Set<string> 124 * │ │ └── globalNames: Set<string> 125 * │ ├── enumProperties: Set<string> 126 * │ └── stringProperties: Set<string> 127 * │ └── arkUIKeepInfo: KeepInfo 128 * │ ├── propertyNames: Set<string> 129 * │ └── globalNames: Set<string> 130 * └── bytecodeObfuscateKeepInfo: BytecodeObfuscateKeepInfo 131 * └── decoratorMap?: Map<string, string[]> 132 * └── fileReservedInfo: FileReservedInfo 133 * ├── enumProperties: Set<string> 134 * └── propertyParams: Set<string> 135 */ 136export interface FileKeepInfo { 137 keepSymbol?: KeepInfo; // Names marked with "@KeepSymbol". 138 keepAsConsumer?: KeepInfo; // Names marked with "@KeepAsConsumer". 139 structProperties: Set<string>; // Struct properties collected from struct. 140 exported: KeepInfo; // Exported names and properties. 141 enumProperties: Set<string>; // Enum properties. 142 stringProperties: Set<string>; // String properties. 143 arkUIKeepInfo?: KeepInfo; // Collecting classes and members 144} 145 146export interface FileReservedInfo { 147 enumProperties: Set<string>; // Enum members, collected when has initializer. 148 propertyParams: Set<string>; // Properties parameters in constructor. 149} 150 151export interface BytecodeObfuscateKeepInfo { 152 decoratorMap?: Object; // collect DecoratorMap 153} 154 155export interface FileWhiteList { 156 fileKeepInfo: FileKeepInfo; 157 fileReservedInfo: FileReservedInfo; 158 bytecodeObfuscateKeepInfo?: BytecodeObfuscateKeepInfo 159} 160 161export interface ProjectKeepInfo { 162 propertyNames: Set<string>; 163 globalNames: Set<string>; 164} 165 166export interface ProjectReservedInfo { 167 enumProperties: Set<string>; 168 propertyParams: Set<string>; 169} 170 171export interface ProjectWhiteList { 172 projectKeepInfo: ProjectKeepInfo; 173 projectReservedInfo: ProjectReservedInfo; 174} 175 176export interface ProjectWhiteListJsonData { 177 projectKeepInfo: { 178 propertyNames: Array<string>; 179 globalNames: Array<string>; 180 }; 181 projectReservedInfo: { 182 enumProperties: Array<string>; 183 propertyParams: Array<string>; 184 }; 185} 186 187 188// The object to manage project white lists, should be initialized in arkObfuscator 189export let projectWhiteListManager: ProjectWhiteListManager | undefined; 190 191// Initialize projectWhiteListManager 192export function initProjectWhiteListManager(cachePath: string, isIncremental: boolean, enableAtKeep: boolean): void { 193 projectWhiteListManager = new ProjectWhiteListManager(cachePath, isIncremental, enableAtKeep); 194} 195 196// Clear projectWhiteListManager 197export function clearProjectWhiteListManager(): void { 198 projectWhiteListManager = undefined; 199} 200 201/** 202 * This class is used to manage project white lists. 203 * Used to collect white list of each file and merge them into project white lists. 204 */ 205export class ProjectWhiteListManager { 206 // If atKeep is enabled 207 private enableAtKeep: boolean; 208 209 // Cache path for file white lists 210 private fileWhiteListsCachePath: string; 211 212 // Cache path for project white lists 213 private projectWhiteListCachePath: string; 214 215 // If it is incremental compliation 216 private isIncremental: boolean = false; 217 218 // If we should reObfuscate all files 219 private shouldReObfuscate: boolean = false; 220 221 // White lists of each file 222 private fileWhiteListMap: Map<string, FileWhiteList>; 223 224 // White lists for one file 225 public fileWhiteListInfo: FileWhiteList | undefined; 226 227 public getEnableAtKeep(): boolean { 228 return this.enableAtKeep; 229 } 230 231 public getFileWhiteListsCachePath(): string { 232 return this.fileWhiteListsCachePath; 233 } 234 235 public getProjectWhiteListCachePath(): string { 236 return this.projectWhiteListCachePath; 237 } 238 239 public getIsIncremental(): boolean { 240 return this.isIncremental; 241 } 242 243 public getShouldReObfuscate(): boolean { 244 return this.shouldReObfuscate; 245 } 246 247 public getFileWhiteListMap(): Map<string, FileWhiteList> { 248 return this.fileWhiteListMap; 249 } 250 251 constructor(cachePath: string, isIncremental: boolean, enableAtKeep: boolean) { 252 this.fileWhiteListsCachePath = path.join(cachePath, FILE_WHITE_LISTS); 253 this.projectWhiteListCachePath = path.join(cachePath, PROJECT_WHITE_LIST); 254 this.isIncremental = isIncremental; 255 this.enableAtKeep = enableAtKeep; 256 this.fileWhiteListMap = new Map(); 257 } 258 259 private createDefaultFileKeepInfo(): FileKeepInfo { 260 return { 261 structProperties: new Set<string>(), 262 exported: { 263 propertyNames: new Set<string>(), 264 globalNames: new Set<string>(), 265 }, 266 enumProperties: new Set<string>(), 267 stringProperties: new Set<string>(), 268 arkUIKeepInfo: { 269 propertyNames: new Set<string>(), 270 globalNames: new Set<string>(), 271 }, 272 }; 273 } 274 275 private createDefaultFileReservedInfo(): FileReservedInfo { 276 return { 277 enumProperties: new Set<string>(), 278 propertyParams: new Set<string>(), 279 }; 280 } 281 282 // Create one fileWhilteList object, with keepSymbol & keepAsConsumer if atKeep is enabled 283 public createFileWhiteList(): FileWhiteList { 284 const fileKeepInfo = this.enableAtKeep 285 ? { 286 keepSymbol: { 287 propertyNames: new Set<string>(), 288 globalNames: new Set<string>(), 289 }, 290 keepAsConsumer: { 291 propertyNames: new Set<string>(), 292 globalNames: new Set<string>(), 293 }, 294 ...this.createDefaultFileKeepInfo(), 295 } 296 : this.createDefaultFileKeepInfo(); 297 return { 298 fileKeepInfo, 299 fileReservedInfo: this.createDefaultFileReservedInfo(), 300 }; 301 } 302 303 // Initialize current collector, 304 // should be called before we collect fileWhiteLists. 305 public setCurrentCollector(path: string): void { 306 const unixPath = FileUtils.toUnixPath(path); 307 let fileWhiteListInfo: FileWhiteList | undefined = this.fileWhiteListMap.get(unixPath); 308 if (!fileWhiteListInfo) { 309 fileWhiteListInfo = this.createFileWhiteList(); 310 this.fileWhiteListMap.set(unixPath, fileWhiteListInfo); 311 } 312 this.fileWhiteListInfo = fileWhiteListInfo; 313 } 314 315 private readFileWhiteLists(filePath: string): Map<string, FileWhiteList> { 316 const fileContent = fs.readFileSync(filePath, 'utf8'); 317 const parsed: Object = JSON.parse(fileContent); 318 319 const map = new Map<string, FileWhiteList>(); 320 for (const key in parsed) { 321 if (Object.prototype.hasOwnProperty.call(parsed, key)) { 322 const fileKeepInfo: FileKeepInfo = { 323 keepSymbol: parsed[key].fileKeepInfo.keepSymbol 324 ? { 325 propertyNames: arrayToSet(parsed[key].fileKeepInfo.keepSymbol.propertyNames), 326 globalNames: arrayToSet(parsed[key].fileKeepInfo.keepSymbol.globalNames), 327 } 328 : undefined, 329 keepAsConsumer: parsed[key].fileKeepInfo.keepAsConsumer 330 ? { 331 propertyNames: arrayToSet(parsed[key].fileKeepInfo.keepAsConsumer.propertyNames), 332 globalNames: arrayToSet(parsed[key].fileKeepInfo.keepAsConsumer.globalNames), 333 } 334 : undefined, 335 structProperties: arrayToSet(parsed[key].fileKeepInfo.structProperties), 336 exported: { 337 propertyNames: arrayToSet(parsed[key].fileKeepInfo.exported.propertyNames), 338 globalNames: arrayToSet(parsed[key].fileKeepInfo.exported.globalNames), 339 }, 340 enumProperties: arrayToSet(parsed[key].fileKeepInfo.enumProperties), 341 stringProperties: arrayToSet(parsed[key].fileKeepInfo.stringProperties), 342 arkUIKeepInfo: parsed[key].fileKeepInfo.arkUIKeepInfo 343 ? { 344 propertyNames: arrayToSet(parsed[key].fileKeepInfo.arkUIKeepInfo.propertyNames), 345 globalNames: arrayToSet(parsed[key].fileKeepInfo.arkUIKeepInfo.globalNames), 346 } 347 : undefined, 348 }; 349 350 const fileReservedInfo: FileReservedInfo = { 351 enumProperties: arrayToSet(parsed[key].fileReservedInfo.enumProperties), 352 propertyParams: arrayToSet(parsed[key].fileReservedInfo.propertyParams), 353 }; 354 const bytecodeObfuscateKeepInfo: BytecodeObfuscateKeepInfo = { 355 decoratorMap: parsed[key].bytecodeObfuscateKeepInfo?.decoratorMap, 356 }; 357 map.set(key, { fileKeepInfo, fileReservedInfo, bytecodeObfuscateKeepInfo }); 358 } 359 } 360 361 return map; 362 } 363 364 private writeFileWhiteLists(filePath: string, fileWhiteLists: Map<string, FileWhiteList>): void { 365 const jsonData: Object = {}; 366 for (const [key, value] of fileWhiteLists) { 367 jsonData[key] = { 368 fileKeepInfo: { 369 keepSymbol: value.fileKeepInfo.keepSymbol 370 ? { 371 propertyNames: setToArray(value.fileKeepInfo.keepSymbol.propertyNames), 372 globalNames: setToArray(value.fileKeepInfo.keepSymbol.globalNames), 373 } 374 : undefined, 375 keepAsConsumer: value.fileKeepInfo.keepAsConsumer 376 ? { 377 propertyNames: setToArray(value.fileKeepInfo.keepAsConsumer.propertyNames), 378 globalNames: setToArray(value.fileKeepInfo.keepAsConsumer.globalNames), 379 } 380 : undefined, 381 structProperties: setToArray(value.fileKeepInfo.structProperties), 382 exported: { 383 propertyNames: setToArray(value.fileKeepInfo.exported.propertyNames), 384 globalNames: setToArray(value.fileKeepInfo.exported.globalNames), 385 }, 386 enumProperties: setToArray(value.fileKeepInfo.enumProperties), 387 stringProperties: setToArray(value.fileKeepInfo.stringProperties), 388 arkUIKeepInfo: value.fileKeepInfo.arkUIKeepInfo 389 ? { 390 propertyNames: setToArray(value.fileKeepInfo.arkUIKeepInfo.propertyNames), 391 globalNames: setToArray(value.fileKeepInfo.arkUIKeepInfo.globalNames), 392 } 393 : undefined, 394 }, 395 fileReservedInfo: { 396 enumProperties: setToArray(value.fileReservedInfo.enumProperties), 397 propertyParams: setToArray(value.fileReservedInfo.propertyParams), 398 }, 399 }; 400 if (value.bytecodeObfuscateKeepInfo?.decoratorMap) { 401 jsonData[key].bytecodeObfuscateKeepInfo = { 402 decoratorMap: value.bytecodeObfuscateKeepInfo.decoratorMap, 403 }; 404 } 405 } 406 407 const jsonString = JSON.stringify(jsonData, null, 2); 408 fs.writeFileSync(filePath, jsonString, 'utf8'); 409 } 410 411 private readProjectWhiteList(filePath: string): ProjectWhiteList { 412 const fileContent = fs.readFileSync(filePath, 'utf8'); 413 const parsed: ProjectWhiteListJsonData = JSON.parse(fileContent); 414 415 const projectKeepInfo: ProjectKeepInfo = { 416 propertyNames: arrayToSet(parsed.projectKeepInfo.propertyNames), 417 globalNames: arrayToSet(parsed.projectKeepInfo.globalNames), 418 }; 419 420 const projectReservedInfo: ProjectReservedInfo = { 421 enumProperties: arrayToSet(parsed.projectReservedInfo.enumProperties), 422 propertyParams: arrayToSet(parsed.projectReservedInfo.propertyParams), 423 }; 424 425 return { 426 projectKeepInfo, 427 projectReservedInfo, 428 }; 429 } 430 431 private writeProjectWhiteList(filePath: string, projectWhiteList: ProjectWhiteList): void { 432 const jsonData: ProjectWhiteListJsonData = { 433 projectKeepInfo: { 434 propertyNames: setToArray(projectWhiteList.projectKeepInfo.propertyNames), 435 globalNames: setToArray(projectWhiteList.projectKeepInfo.globalNames), 436 }, 437 projectReservedInfo: { 438 enumProperties: setToArray(projectWhiteList.projectReservedInfo.enumProperties), 439 propertyParams: setToArray(projectWhiteList.projectReservedInfo.propertyParams), 440 }, 441 }; 442 443 const jsonString = JSON.stringify(jsonData, null, 2); 444 fs.writeFileSync(filePath, jsonString, 'utf8'); 445 } 446 447 public updateFileWhiteListMap(deletedFilePath?: Set<string>): void { 448 const lastFileWhiteLists: Map<string, FileWhiteList> = this.readFileWhiteLists(this.fileWhiteListsCachePath); 449 450 deletedFilePath?.forEach((path) => { 451 lastFileWhiteLists.delete(path); 452 }); 453 this.fileWhiteListMap.forEach((value, key) => { 454 lastFileWhiteLists.set(key, value); 455 }); 456 this.writeFileWhiteLists(this.fileWhiteListsCachePath, lastFileWhiteLists); 457 this.fileWhiteListMap = lastFileWhiteLists; 458 } 459 460 public createProjectWhiteList(fileWhiteLists: Map<string, FileWhiteList>): ProjectWhiteList { 461 const projectKeepInfo: ProjectKeepInfo = { 462 propertyNames: new Set(), 463 globalNames: new Set(), 464 }; 465 466 const projectReservedInfo: ProjectReservedInfo = { 467 enumProperties: new Set(), 468 propertyParams: new Set(), 469 }; 470 471 fileWhiteLists.forEach((fileWhiteList) => { 472 // 1. Collect fileKeepInfo 473 // Collect keepSymbol 474 fileWhiteList.fileKeepInfo.keepSymbol?.globalNames.forEach((globalName) => { 475 projectKeepInfo.globalNames.add(globalName); 476 }); 477 fileWhiteList.fileKeepInfo.keepSymbol?.propertyNames.forEach((propertyName) => { 478 projectKeepInfo.propertyNames.add(propertyName); 479 }); 480 481 // Collect keepAsConsumer 482 fileWhiteList.fileKeepInfo.keepAsConsumer?.globalNames.forEach((globalName) => { 483 projectKeepInfo.globalNames.add(globalName); 484 }); 485 fileWhiteList.fileKeepInfo.keepAsConsumer?.propertyNames.forEach((propertyName) => { 486 projectKeepInfo.propertyNames.add(propertyName); 487 }); 488 489 // Collect structProperties 490 fileWhiteList.fileKeepInfo.structProperties.forEach((propertyName) => { 491 projectKeepInfo.propertyNames.add(propertyName); 492 }); 493 494 // Collect exportedNames 495 fileWhiteList.fileKeepInfo.exported.globalNames.forEach((globalName) => { 496 projectKeepInfo.globalNames.add(globalName); 497 }); 498 fileWhiteList.fileKeepInfo.exported.propertyNames.forEach((propertyName) => { 499 projectKeepInfo.propertyNames.add(propertyName); 500 }); 501 502 // Collect enumProperties 503 fileWhiteList.fileKeepInfo.enumProperties.forEach((propertyName) => { 504 projectKeepInfo.propertyNames.add(propertyName); 505 }); 506 507 // Collect stringProperties 508 fileWhiteList.fileKeepInfo.stringProperties.forEach((propertyName) => { 509 projectKeepInfo.propertyNames.add(propertyName); 510 }); 511 512 // Collect arkUIKeepInfo 513 fileWhiteList.fileKeepInfo.arkUIKeepInfo?.globalNames.forEach((globalName) => { 514 projectKeepInfo.globalNames.add(globalName); 515 AtIntentCollections.globalNames.add(globalName); 516 }); 517 fileWhiteList.fileKeepInfo.arkUIKeepInfo?.propertyNames.forEach((propertyName) => { 518 projectKeepInfo.propertyNames.add(propertyName); 519 AtIntentCollections.propertyNames.add(propertyName); 520 }); 521 522 // 2. Collect fileReservedInfo 523 // Collect enumProperties 524 fileWhiteList.fileReservedInfo.enumProperties.forEach((enumPropertyName) => { 525 projectReservedInfo.enumProperties.add(enumPropertyName); 526 }); 527 528 // Collect propertyParams 529 fileWhiteList.fileReservedInfo.propertyParams.forEach((propertyParam) => { 530 projectReservedInfo.propertyParams.add(propertyParam); 531 }); 532 533 const decoratorMap = fileWhiteList.bytecodeObfuscateKeepInfo?.decoratorMap; 534 for (const key in decoratorMap) { 535 if (Object.prototype.hasOwnProperty.call(decoratorMap, key)) { 536 decoratorMap[key]?.forEach(item => projectKeepInfo.globalNames.add(item)); 537 } 538 } 539 }); 540 541 const projectWhiteList = { 542 projectKeepInfo: projectKeepInfo, 543 projectReservedInfo: projectReservedInfo, 544 }; 545 546 return projectWhiteList; 547 } 548 549 // Determine if the project's white list has been updated. 550 private areProjectWhiteListsEqual(whiteList1: ProjectWhiteList, whiteList2: ProjectWhiteList): boolean { 551 const projectKeepInfoEqual = 552 areSetsEqual(whiteList1.projectKeepInfo.propertyNames, whiteList2.projectKeepInfo.propertyNames) && 553 areSetsEqual(whiteList1.projectKeepInfo.globalNames, whiteList2.projectKeepInfo.globalNames); 554 555 if (!projectKeepInfoEqual) { 556 return false; 557 } 558 559 const projectReservedInfoEqual = 560 areSetsEqual(whiteList1.projectReservedInfo.enumProperties, whiteList2.projectReservedInfo.enumProperties) && 561 areSetsEqual(whiteList1.projectReservedInfo.propertyParams, whiteList2.projectReservedInfo.propertyParams); 562 563 return projectReservedInfoEqual; 564 } 565 566 // We only scan updated files or newly created files in incremental compliation, 567 // so we need to update the UnobfuscationCollections and reserved names use all file white lists. 568 // This should be called in incremental compliation after we updated file white list. 569 private updateUnobfuscationCollections(): void { 570 this.fileWhiteListMap.forEach((fileWhiteList) => { 571 if (this.enableAtKeep) { 572 addToSet(AtKeepCollections.keepSymbol.propertyNames, fileWhiteList.fileKeepInfo.keepSymbol.propertyNames); 573 addToSet(AtKeepCollections.keepSymbol.globalNames, fileWhiteList.fileKeepInfo.keepSymbol.globalNames); 574 addToSet(AtKeepCollections.keepAsConsumer.propertyNames, fileWhiteList.fileKeepInfo.keepAsConsumer.propertyNames); 575 addToSet(AtKeepCollections.keepAsConsumer.globalNames, fileWhiteList.fileKeepInfo.keepAsConsumer.globalNames); 576 } 577 addToSet(UnobfuscationCollections.reservedStruct, fileWhiteList.fileKeepInfo.structProperties); 578 addToSet(UnobfuscationCollections.reservedEnum, fileWhiteList.fileKeepInfo.enumProperties); 579 addToSet(UnobfuscationCollections.reservedExportName, fileWhiteList.fileKeepInfo.exported.globalNames); 580 addToSet(UnobfuscationCollections.reservedExportNameAndProp, fileWhiteList.fileKeepInfo.exported.propertyNames); 581 addToSet(UnobfuscationCollections.reservedStrProp, fileWhiteList.fileKeepInfo.stringProperties); 582 if (fileWhiteList.fileKeepInfo.arkUIKeepInfo) { 583 addToSet(AtIntentCollections.propertyNames, fileWhiteList.fileKeepInfo.arkUIKeepInfo.propertyNames); 584 addToSet(AtIntentCollections.globalNames, fileWhiteList.fileKeepInfo.arkUIKeepInfo.globalNames); 585 } 586 addToSet(ApiExtractor.mConstructorPropertySet, fileWhiteList.fileReservedInfo.propertyParams); 587 addToSet(ApiExtractor.mEnumMemberSet, fileWhiteList.fileReservedInfo.enumProperties); 588 const decoratorMap = fileWhiteList.bytecodeObfuscateKeepInfo?.decoratorMap; 589 for (const key in decoratorMap) { 590 if (Object.prototype.hasOwnProperty.call(decoratorMap, key)) { 591 decoratorMap[key]?.forEach(item => BytecodeObfuscationCollections.decoratorProp.add(item)); 592 } 593 } 594 }); 595 } 596 597 private updateProjectWhiteList(): ProjectWhiteList { 598 const lastProjectWhiteList: ProjectWhiteList = this.readProjectWhiteList(this.projectWhiteListCachePath); 599 const newestProjectWhiteList: ProjectWhiteList = this.createProjectWhiteList(this.fileWhiteListMap); 600 if (!this.areProjectWhiteListsEqual(lastProjectWhiteList, newestProjectWhiteList)) { 601 this.writeProjectWhiteList(this.projectWhiteListCachePath, newestProjectWhiteList); 602 this.shouldReObfuscate = true; 603 } 604 return newestProjectWhiteList; 605 } 606 607 public createOrUpdateWhiteListCaches(deletedFilePath?: Set<string>): void { 608 let projectWhiteList: ProjectWhiteList; 609 if (!this.isIncremental) { 610 this.writeFileWhiteLists(this.fileWhiteListsCachePath, this.fileWhiteListMap); 611 projectWhiteList = this.createProjectWhiteList(this.fileWhiteListMap); 612 this.writeProjectWhiteList(this.projectWhiteListCachePath, projectWhiteList); 613 } else { 614 this.updateFileWhiteListMap(deletedFilePath); 615 projectWhiteList = this.updateProjectWhiteList(); 616 this.updateUnobfuscationCollections(); 617 } 618 this.fileWhiteListMap.clear(); 619 } 620} 621 622/** 623 * This class is used to manage sourceFilePath.json file. 624 * Will be created when initialize arkObfuscator. 625 */ 626export class FilePathManager { 627 // Stores all source files paths 628 private sourceFilePaths: Set<string>; 629 630 // Files deleted in incremental build 631 private deletedSourceFilePaths: Set<string>; 632 633 // Files added in incremental build 634 private addedSourceFilePaths: Set<string>; 635 636 // Cache path of sourceFilePaths.cache file 637 private filePathsCache: string; 638 639 public getSourceFilePaths(): Set<string> { 640 return this.sourceFilePaths; 641 } 642 643 public getDeletedSourceFilePaths(): Set<string> { 644 return this.deletedSourceFilePaths; 645 } 646 647 public getAddedSourceFilePaths(): Set<string> { 648 return this.addedSourceFilePaths; 649 } 650 651 public getFilePathsCache(): string { 652 return this.filePathsCache; 653 } 654 655 constructor(cachePath: string) { 656 this.filePathsCache = path.join(cachePath, SOURCE_FILE_PATHS); 657 this.sourceFilePaths = new Set(); 658 this.deletedSourceFilePaths = new Set(); 659 this.addedSourceFilePaths = new Set(); 660 } 661 662 private setSourceFilePaths(sourceFilePaths: Set<string>): void { 663 sourceFilePaths.forEach((filePath) => { 664 if (!FileUtils.isReadableFile(filePath) || !ApiExtractor.isParsableFile(filePath)) { 665 return; 666 } 667 this.sourceFilePaths.add(filePath); 668 }); 669 } 670 671 private writeSourceFilePaths(): void { 672 const content = Array.from(this.sourceFilePaths).join('\n'); 673 FileUtils.writeFile(this.filePathsCache, content); 674 } 675 676 // Update sourceFilePaths.cache and get deleted file list and added file list 677 private updateSourceFilePaths(): void { 678 const cacheContent = FileUtils.readFile(this.filePathsCache).split('\n'); 679 const cacheSet = new Set(cacheContent); 680 681 for (const path of cacheSet) { 682 if (!this.sourceFilePaths.has(path)) { 683 this.deletedSourceFilePaths.add(path); 684 } 685 } 686 687 for (const path of this.sourceFilePaths) { 688 if (!cacheSet.has(path)) { 689 this.addedSourceFilePaths.add(path); 690 } 691 } 692 693 this.writeSourceFilePaths(); 694 } 695 696 // Create or update sourceFilePaths.cache 697 public createOrUpdateSourceFilePaths(sourceFilePaths: Set<string>): void { 698 this.setSourceFilePaths(sourceFilePaths); 699 if (this.isIncremental()) { 700 this.updateSourceFilePaths(); 701 } else { 702 this.writeSourceFilePaths(); 703 } 704 } 705 706 public isIncremental(): boolean { 707 return fs.existsSync(this.filePathsCache); 708 } 709} 710 711/** 712 * This class is used to manage transfromed file content. 713 * Will be created when initialize arkObfuscator. 714 */ 715export class FileContentManager { 716 // Path that stores all transfromed file content 717 private transformedFilesDir: string; 718 719 // Path of fileNamesMap 720 private fileNamesMapPath: string; 721 722 // Stores [originpath -> transformed cache path] 723 public fileNamesMap: Map<string, string>; 724 725 // If it is incremental compilation 726 private isIncremental: boolean; 727 728 public getTransformedFilesDir(): string { 729 return this.transformedFilesDir; 730 } 731 732 public getFileNamesMapPath(): string { 733 return this.fileNamesMapPath; 734 } 735 736 public getIsIncremental(): boolean { 737 return this.isIncremental; 738 } 739 740 constructor(cachePath: string, isIncremental: boolean) { 741 this.transformedFilesDir = path.join(cachePath, TRANSFORMED_PATH); 742 this.fileNamesMapPath = path.join(this.transformedFilesDir, FILE_NAMES_MAP); 743 this.fileNamesMap = new Map(); 744 this.isIncremental = isIncremental; 745 FileUtils.createDirectory(this.transformedFilesDir); 746 } 747 748 // Generate hash from filePah 749 private generatePathHash(filePath: string): string { 750 const hash = crypto.createHash('md5'); 751 hash.update(filePath); 752 return hash.digest('hex').slice(0, 16); 753 } 754 755 // Generate new file name for transfromed sourcefiles. 756 // Generated from origin filePath's hash & time stamp. 757 private generateFileName(filePath: string): string { 758 const hash = this.generatePathHash(filePath); 759 const timestamp = Date.now().toString(); 760 return `${hash}_${timestamp}`; 761 } 762 763 // Delete a set of files, used in incremental compliation if we have deleted some files 764 public deleteFileContent(deletedSourceFilePaths: Set<string>): void { 765 deletedSourceFilePaths.forEach((filePath) => { 766 this.deleteTransformedFile(filePath); 767 }); 768 } 769 770 // Delete transfromed file 771 private deleteTransformedFile(deletedSourceFilePath: string): void { 772 if (this.fileNamesMap.has(deletedSourceFilePath)) { 773 const transformedFilePath: string = this.fileNamesMap.get(deletedSourceFilePath); 774 this.fileNamesMap.delete(deletedSourceFilePath); 775 FileUtils.deleteFile(transformedFilePath); 776 } 777 } 778 779 public readFileNamesMap(): void { 780 const jsonObject = FileUtils.readFileAsJson(this.fileNamesMapPath); 781 this.fileNamesMap = new Map<string, string>(Object.entries(jsonObject)); 782 } 783 784 public writeFileNamesMap(): void { 785 const jsonObject = Object.fromEntries(this.fileNamesMap.entries()); 786 const jsonString = JSON.stringify(jsonObject, null, 2); 787 FileUtils.writeFile(this.fileNamesMapPath, jsonString); 788 this.fileNamesMap.clear(); 789 } 790 791 public readFileContent(transformedFilePath: string): FileContent { 792 const fileContentJson = FileUtils.readFile(transformedFilePath); 793 const fileConstent: FileContent = JSON.parse(fileContentJson); 794 return fileConstent; 795 } 796 797 private writeFileContent(filePath: string, fileContent: FileContent): void { 798 const jsonString = JSON.stringify(fileContent, null, 2); 799 FileUtils.writeFile(filePath, jsonString); 800 } 801 802 // Update file content for newly created files or updated files 803 public updateFileContent(fileContent: FileContent): void { 804 const originPath = FileUtils.toUnixPath(fileContent.moduleInfo.originSourceFilePath); 805 if (this.isIncremental) { 806 this.deleteTransformedFile(originPath); 807 } 808 const fileName = this.generateFileName(originPath); 809 const transformedFilePath = path.join(this.transformedFilesDir, fileName); 810 this.fileNamesMap.set(originPath, transformedFilePath); 811 this.writeFileContent(transformedFilePath, fileContent); 812 } 813 814 public getSortedFiles(): string[] { 815 return (this.fileNamesMap && Array.from(this.fileNamesMap.keys())).sort((a, b) => a.localeCompare(b)) || []; 816 } 817} 818