1/* 2 * Copyright (c) 2024-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 { Scene } from '../../Scene'; 17import { Value } from '../../core/base/Value'; 18import { NodeID } from '../../core/graph/BaseExplicitGraph'; 19import path from 'path'; 20import * as fs from 'fs'; 21import { CallGraph, CallGraphNode, CallSite, DynCallSite, FuncID } from '../model/CallGraph'; 22import { AbstractAnalysis } from '../algorithm/AbstractAnalysis'; 23import { ClassType, Type, UnknownType } from '../../core/base/Type'; 24import { CallGraphBuilder } from '../model/builder/CallGraphBuilder'; 25import { Stmt } from '../../core/base/Stmt'; 26import Logger, { LOG_MODULE_TYPE } from '../../utils/logger'; 27import { DummyMainCreater } from '../../core/common/DummyMainCreater'; 28import { PTAStat } from '../common/Statistics'; 29import { Pag, PagNode, PagEdgeKind, PagEdge, PagLocalNode, PagGlobalThisNode, PagArrayNode } from './Pag'; 30import { PagBuilder } from './PagBuilder'; 31import { PointerAnalysisConfig, PtaAnalysisScale } from './PointerAnalysisConfig'; 32import { DiffPTData, IPtsCollection } from './PtsDS'; 33import { Local } from '../../core/base/Local'; 34import { ArkMethod } from '../../core/model/ArkMethod'; 35 36const logger = Logger.getLogger(LOG_MODULE_TYPE.ARKANALYZER, 'PTA'); 37 38export class PointerAnalysis extends AbstractAnalysis { 39 private pag: Pag; 40 private pagBuilder: PagBuilder; 41 private ptd: DiffPTData<NodeID, NodeID, IPtsCollection<NodeID>>; 42 private entries!: FuncID[]; 43 private worklist!: NodeID[]; 44 // record all updated nodes 45 private ptaStat: PTAStat; 46 private typeDiffMap!: Map<Value, Set<Type>>; 47 private config: PointerAnalysisConfig; 48 49 constructor(p: Pag, cg: CallGraph, s: Scene, config: PointerAnalysisConfig) { 50 super(s, cg); 51 this.pag = p; 52 this.ptd = new DiffPTData<NodeID, NodeID, IPtsCollection<NodeID>>(config.ptsCollectionCtor); 53 this.pagBuilder = new PagBuilder(this.pag, this.cg, s, config.kLimit, config.analysisScale); 54 this.cgBuilder = new CallGraphBuilder(this.cg, s); 55 this.ptaStat = new PTAStat(this); 56 this.config = config; 57 } 58 59 static pointerAnalysisForWholeProject(projectScene: Scene, config?: PointerAnalysisConfig): PointerAnalysis { 60 let cg = new CallGraph(projectScene); 61 let cgBuilder = new CallGraphBuilder(cg, projectScene); 62 cgBuilder.buildDirectCallGraphForScene(); 63 let pag = new Pag(); 64 if (!config) { 65 config = PointerAnalysisConfig.create(1, 'out/', false, false); 66 } 67 68 const dummyMainCreator = new DummyMainCreater(projectScene); 69 dummyMainCreator.createDummyMain(); 70 const dummyMainMethod = dummyMainCreator.getDummyMain(); 71 cgBuilder.buildDirectCallGraph([dummyMainMethod]); 72 73 let dummyMainMethodID = cg.getCallGraphNodeByMethod(dummyMainMethod.getSignature()).getID(); 74 cg.setDummyMainFuncID(dummyMainMethodID); 75 76 let pta = new PointerAnalysis(pag, cg, projectScene, config); 77 pta.setEntries([dummyMainMethodID]); 78 pta.start(); 79 return pta; 80 } 81 82 static pointerAnalysisForMethod(s: Scene, method: ArkMethod, config?: PointerAnalysisConfig): PointerAnalysis { 83 let cg = new CallGraph(s); 84 let cgBuilder = new CallGraphBuilder(cg, s); 85 cgBuilder.buildDirectCallGraphForScene(); 86 let pag = new Pag(); 87 if (!config) { 88 config = PointerAnalysisConfig.create(1, 'out/', false, false); 89 } 90 91 let entryMethodID = cg.getCallGraphNodeByMethod(method.getSignature()).getID(); 92 let pta = new PointerAnalysis(pag, cg, s, config); 93 pta.setEntries([entryMethodID]); 94 pta.start(); 95 return pta; 96 } 97 98 protected init(): void { 99 logger.warn(`========== Init Pointer Analysis ==========`); 100 // start statistics 101 this.ptaStat.startStat(); 102 // build funcPag with entries 103 this.pagBuilder.buildForEntries(this.entries); 104 if (this.config.dotDump) { 105 this.pag.dump(path.join(this.config.outputDirectory, 'ptaInit_pag.dot')); 106 this.cg.dump(path.join(this.config.outputDirectory, 'cg_init.dot')); 107 } 108 } 109 110 public start(): void { 111 this.init(); 112 this.solveConstraint(); 113 this.postProcess(); 114 } 115 116 private postProcess(): void { 117 this.ptaStat.endStat(); 118 this.pagBuilder.doStat(); 119 this.cg.printStat(); 120 this.pagBuilder.printStat(); 121 this.ptaStat.printStat(); 122 if (this.config.dotDump) { 123 this.pag.dump(path.join(this.config.outputDirectory, 'ptaEnd_pag.dot')); 124 this.cg.dump(path.join(this.config.outputDirectory, 'cgEnd.dot')); 125 } 126 127 if (this.config.unhandledFuncDump) { 128 this.dumpUnhandledFunctions(); 129 } 130 } 131 132 public getPTD(): DiffPTData<NodeID, NodeID, IPtsCollection<NodeID>> { 133 return this.ptd; 134 } 135 136 public getStat(): string { 137 let ret: string = this.cg.getStat(); 138 ret += '\n' + this.pagBuilder.getStat(); 139 ret += '\n' + this.ptaStat.getStat(); 140 141 return ret; 142 } 143 144 protected preProcessMethod(funcID: FuncID): CallSite[] { 145 // do nothing 146 return []; 147 } 148 149 public setEntries(fIds: FuncID[]): void { 150 this.entries = fIds; 151 } 152 153 private solveConstraint(): void { 154 this.worklist = []; 155 logger.warn(`========== Pointer Analysis Start ==========`); 156 this.initWorklist(); 157 let reanalyzer: boolean = true; 158 159 while (reanalyzer) { 160 this.ptaStat.iterTimes++; 161 logger.warn(`========== Pointer Analysis Round ${this.ptaStat.iterTimes} ==========`); 162 163 // do pointer transfer 164 this.solveWorklist(); 165 // process dynamic call 166 if (this.config.analysisScale === PtaAnalysisScale.WholeProgram || this.ptaStat.iterTimes === 1) { 167 reanalyzer = this.onTheFlyDynamicCallSolve(); 168 } else { 169 reanalyzer = false; 170 } 171 if (this.config.dotDump) { 172 this.pag.dump(path.join(this.config.outputDirectory, `pta_pag_itor#${this.ptaStat.iterTimes}.dot`)); 173 } 174 } 175 } 176 177 /** 178 * get newly added Address Edge, and add them to initial WorkList 179 */ 180 private initWorklist(): boolean { 181 let changed: boolean = false; 182 this.addToReanalyze(this.pagBuilder.getRetriggerNodes()); 183 for (let e of this.pag.getAddrEdges()) { 184 this.ptaStat.numProcessedAddr++; 185 186 let { src, dst } = e.getEndPoints(); 187 this.ptd.addPts(dst, src); 188 if (this.pag.getNode(src) instanceof PagGlobalThisNode) { 189 // readd globalThis heapObj into workList 190 this.ptd.addPts(src, src); 191 this.worklist.push(src); 192 } 193 194 this.worklist.push(dst); 195 changed = true; 196 } 197 this.pag.resetAddrEdges(); 198 return changed; 199 } 200 201 private solveWorklist(): boolean { 202 while (this.worklist.length > 0) { 203 let node = this.worklist.shift() as NodeID; 204 this.processNode(node); 205 } 206 207 return true; 208 } 209 210 private processNode(nodeId: NodeID): boolean { 211 this.handleThis(nodeId); 212 this.handleLoadWrite(nodeId); 213 this.handleCopy(nodeId); 214 this.handlePt(nodeId); 215 216 this.detectTypeDiff(nodeId); 217 return true; 218 } 219 220 private handleCopy(nodeID: NodeID): boolean { 221 let node = this.pag.getNode(nodeID) as PagNode; 222 node.getOutgoingCopyEdges()?.forEach(copyEdge => { 223 this.propagate(copyEdge); 224 this.ptaStat.numProcessedCopy++; 225 }); 226 227 return true; 228 } 229 230 private handleLoadWrite(nodeID: NodeID): boolean { 231 let node = this.pag.getNode(nodeID) as PagNode; 232 let nodeValue = node.getValue(); 233 let diffPts = this.ptd.getDiffPts(nodeID); 234 if (!diffPts || diffPts.count() === 0) { 235 return false; 236 } 237 238 // get related field node with current node's value 239 let instanceFieldNodeMap = this.pag.getNodesByBaseValue(nodeValue) ?? new Map(); 240 // get intra procedural field node by exportMap 241 let intraProceduralFieldNodeMap = new Map(); 242 243 if (nodeValue instanceof Local) { 244 this.pagBuilder.getExportVariableMap(nodeValue).forEach(dst => { 245 let temp = this.pag.getNodesByBaseValue(dst) ?? new Map(); 246 intraProceduralFieldNodeMap = this.mergeInstanceFieldMap(instanceFieldNodeMap, temp); 247 }); 248 } 249 250 instanceFieldNodeMap!.forEach((nodeIDs, cid) => { 251 // TODO: check cid 252 // cid === -1 will escape the check, mainly for globalThis 253 let baseCid = node.getCid(); 254 if (baseCid !== -1 && cid !== baseCid) { 255 return; 256 } 257 nodeIDs.forEach((nodeID: number) => { 258 // get abstract field node 259 let fieldNode = this.pag.getNode(nodeID) as PagNode; 260 261 this.handleFieldInEdges(fieldNode, diffPts!); 262 this.handleFieldOutEdges(fieldNode, diffPts!); 263 }); 264 }); 265 266 // without cid check, because closure and export is under different cid 267 intraProceduralFieldNodeMap!.forEach(nodeIDs => { 268 nodeIDs.forEach((nodeID: number) => { 269 // get abstract field node 270 let fieldNode = this.pag.getNode(nodeID) as PagNode; 271 272 this.handleFieldInEdges(fieldNode, diffPts!); 273 this.handleFieldOutEdges(fieldNode, diffPts!); 274 }); 275 }); 276 277 return true; 278 } 279 280 private handleFieldInEdges(fieldNode: PagNode, diffPts: IPtsCollection<number>): void { 281 fieldNode.getIncomingEdge().forEach(edge => { 282 if (edge.getKind() !== PagEdgeKind.Write) { 283 return; 284 } 285 let srcNode = edge.getSrcNode() as PagNode; 286 this.ptaStat.numProcessedWrite++; 287 for (let pt of diffPts!) { 288 // filter pt 289 // clone the real field node with abstract field node 290 let dstNode; 291 if (fieldNode instanceof PagArrayNode) { 292 dstNode = this.pag.getOrClonePagContainerFieldNode(pt, fieldNode); 293 } else { 294 dstNode = this.pag.getOrClonePagFieldNode(fieldNode, pt); 295 } 296 if (!(dstNode && this.pag.addPagEdge(srcNode, dstNode!, PagEdgeKind.Copy))) { 297 continue; 298 } 299 300 this.ptaStat.numRealWrite++; 301 302 if (this.ptd.resetElem(srcNode.getID())) { 303 this.worklist.push(srcNode.getID()); 304 } 305 } 306 }); 307 } 308 309 private handleFieldOutEdges(fieldNode: PagNode, diffPts: IPtsCollection<number>): void { 310 fieldNode.getOutgoingEdges().forEach(edge => { 311 if (edge.getKind() !== PagEdgeKind.Load) { 312 return; 313 } 314 let dstNode = edge.getDstNode() as PagNode; 315 this.ptaStat.numProcessedLoad++; 316 for (let pt of diffPts!) { 317 let srcNode; 318 if (fieldNode instanceof PagArrayNode) { 319 srcNode = this.pag.getOrClonePagContainerFieldNode(pt, fieldNode); 320 } else { 321 srcNode = this.pag.getOrClonePagFieldNode(fieldNode, pt); 322 } 323 if (!(srcNode && this.pag.addPagEdge(srcNode!, dstNode, PagEdgeKind.Copy))) { 324 continue; 325 } 326 327 this.ptaStat.numRealLoad++; 328 329 // TODO: if field is used before initialzed, newSrc node has no diff pts 330 if (this.ptd.resetElem(srcNode.getID())) { 331 this.worklist.push(srcNode.getID()); 332 } 333 } 334 }); 335 } 336 337 /** 338 * If current node is a base of a called method, pointer in this node will be transfered into `this` Local in method 339 */ 340 private handleThis(nodeID: NodeID): boolean { 341 let node = this.pag.getNode(nodeID) as PagNode; 342 node.getOutgoingThisEdges()?.forEach(thisEdge => { 343 this.propagate(thisEdge); 344 this.ptaStat.numProcessedThis++; 345 }); 346 347 return true; 348 } 349 350 private handlePt(nodeID: NodeID): void { 351 let realDiff = this.ptd.calculateDiff(nodeID, nodeID); 352 353 if (realDiff.count() !== 0) { 354 // record the updated nodes 355 this.pagBuilder.addUpdatedNode(nodeID, realDiff); 356 } 357 this.ptd.flush(nodeID); 358 this.pagBuilder.setPtForNode(nodeID, this.ptd.getPropaPts(nodeID)); 359 } 360 361 private propagate(edge: PagEdge): boolean { 362 let changed: boolean = false; 363 let { src, dst } = edge.getEndPoints(); 364 let diffPts = this.ptd.getDiffPts(src); 365 if (!diffPts) { 366 return changed; 367 } 368 let realDiffPts = this.ptd.calculateDiff(src, dst); 369 370 for (let pt of realDiffPts) { 371 changed = this.ptd.addPts(dst, pt) || changed; 372 } 373 374 if (changed) { 375 this.worklist.push(dst); 376 } 377 378 return changed; 379 } 380 381 /** 382 * 1. 记录被更新的节点(记录cid, nodeid) 383 * 2. ( PAGLocalNode记录callsite(cid, value唯一)),通过1种的nodeID查询Node,拿到Callsite 384 * 3. 在addDynamicCall里对传入指针过滤(已处理指针和未处理指针) 385 */ 386 private onTheFlyDynamicCallSolve(): boolean { 387 let changed: boolean = false; 388 let processedCallSites: Set<DynCallSite> = new Set(); 389 this.pagBuilder.getUpdatedNodes().forEach((pts, nodeID) => { 390 let node = this.pag.getNode(nodeID) as PagNode; 391 392 if (!(node instanceof PagLocalNode)) { 393 logger.warn(`node ${nodeID} is not local node, value: ${node.getValue()}`); 394 return; 395 } 396 397 changed = this.processDynCallSite(node, pts, processedCallSites) || changed; 398 changed = this.processUnknownCallSite(node, pts) || changed; 399 }); 400 this.pagBuilder.resetUpdatedNodes(); 401 let srcNodes = this.pagBuilder.handleUnprocessedCallSites(processedCallSites); 402 changed = this.addToReanalyze(srcNodes) || changed; 403 404 changed = this.pagBuilder.handleReachable() || changed; 405 changed = this.initWorklist() || changed; 406 return changed; 407 } 408 409 private processDynCallSite(node: PagLocalNode, pts: IPtsCollection<NodeID>, processedCallSites: Set<DynCallSite>): boolean { 410 let changed: boolean = false; 411 let dynCallSites = node.getRelatedDynCallSites(); 412 413 if (!dynCallSites && !node.isSdkParam()) { 414 logger.warn(`node ${node.getID()} has no related dynamic call site`); 415 return changed; 416 } 417 418 logger.info(`[process dynamic callsite] node ${node.getID()}`); 419 dynCallSites.forEach(dynCallsite => { 420 for (let pt of pts) { 421 let srcNodes = this.pagBuilder.addDynamicCallEdge(dynCallsite, pt, node.getCid()); 422 changed = this.addToReanalyze(srcNodes) || changed; 423 } 424 processedCallSites.add(dynCallsite); 425 }); 426 427 return changed; 428 } 429 430 private processUnknownCallSite(node: PagLocalNode, pts: IPtsCollection<NodeID>): boolean { 431 let changed: boolean = false; 432 let unknownCallSites = node.getRelatedUnknownCallSites(); 433 434 if (!unknownCallSites) { 435 logger.warn(`node ${node.getID()} has no related unknown call site`); 436 return changed; 437 } 438 439 logger.info(`[process unknown callsite] node ${node.getID()}`); 440 unknownCallSites.forEach(unknownCallSite => { 441 for (let pt of pts) { 442 let srcNodes = this.pagBuilder.addDynamicCallEdge(unknownCallSite, pt, node.getCid()); 443 changed = this.addToReanalyze(srcNodes) || changed; 444 } 445 }); 446 447 return changed; 448 } 449 450 private addToReanalyze(startNodes: NodeID[]): boolean { 451 let flag = false; 452 for (let node of startNodes) { 453 if (!this.worklist.includes(node) && this.ptd.resetElem(node)) { 454 this.worklist.push(node); 455 flag = true; 456 } 457 } 458 return flag; 459 } 460 461 /** 462 * compare interface 463 */ 464 public noAlias(leftValue: Value, rightValue: Value): boolean { 465 let leftValueNodes = this.pag.getNodesByValue(leftValue)?.values()!; 466 let rightValueNodes = this.pag.getNodesByValue(rightValue)?.values()!; 467 468 let leftValuePts: Set<NodeID> = new Set(); 469 let rightValuePts: Set<NodeID> = new Set(); 470 471 for (let nodeID of leftValueNodes) { 472 let node = this.pag.getNode(nodeID) as PagNode; 473 for (let pt of node.getPointTo()) { 474 leftValuePts.add(pt); 475 } 476 } 477 478 for (let nodeID of rightValueNodes) { 479 let node = this.pag.getNode(nodeID) as PagNode; 480 for (let pt of node.getPointTo()) { 481 rightValuePts.add(pt); 482 } 483 } 484 485 if (leftValuePts.size > rightValuePts.size) { 486 [leftValuePts, rightValuePts] = [rightValuePts, leftValuePts]; 487 } 488 489 for (const elem of leftValuePts) { 490 if (rightValuePts.has(elem)) { 491 return false; 492 } 493 } 494 495 // no alias 496 return true; 497 } 498 499 public mayAlias(leftValue: Value, rightValue: Value): boolean { 500 return !this.noAlias(leftValue, rightValue); 501 } 502 503 public getRelatedNodes(value: Value): Set<Value> { 504 let valueNodes = this.pag.getNodesByValue(value); 505 let relatedAllNodes: Set<Value> = new Set(); 506 let workListNodes: NodeID[] = []; 507 let processedNodes: Set<NodeID> = new Set(); 508 509 if (valueNodes) { 510 for (const nodeID of valueNodes.values()) { 511 workListNodes.push(nodeID); 512 } 513 } 514 515 while (workListNodes.length !== 0) { 516 let valueNodeID: NodeID = workListNodes.shift()!; 517 if (processedNodes.has(valueNodeID)) { 518 continue; 519 } 520 521 this.processRelatedNode(valueNodeID, workListNodes, processedNodes); 522 } 523 524 processedNodes.forEach(nodeID => { 525 let valueNode = this.pag.getNode(nodeID) as PagNode; 526 relatedAllNodes.add(valueNode.getValue()); 527 }); 528 529 return relatedAllNodes; 530 } 531 532 private processRelatedNode(valueNodeID: NodeID, workListNodes: NodeID[], processedNodes: Set<NodeID>): void { 533 let valueNode = this.pag.getNode(valueNodeID) as PagNode; 534 535 this.addIncomingEdgesToWorkList(valueNode, workListNodes, processedNodes); 536 this.addOutgoingEdgesToWorkList(valueNode, workListNodes, processedNodes); 537 538 processedNodes.add(valueNodeID); 539 } 540 541 private addIncomingEdgesToWorkList(valueNode: PagNode, workListNodes: NodeID[], processedNodes: Set<NodeID>): void { 542 let inCopyEdges = valueNode.getIncomingCopyEdges(); 543 let inThisEdges = valueNode.getIncomingThisEdges(); 544 let combinedEdges = new Set([...(inCopyEdges ?? []), ...(inThisEdges ?? [])]); 545 if (combinedEdges) { 546 combinedEdges.forEach(edge => { 547 let srcID = edge.getSrcID(); 548 if (!processedNodes.has(srcID)) { 549 workListNodes.push(srcID); 550 } 551 }); 552 } 553 } 554 555 private addOutgoingEdgesToWorkList(valueNode: PagNode, workListNodes: NodeID[], processedNodes: Set<NodeID>): void { 556 let outCopyEdges = valueNode.getOutgoingCopyEdges(); 557 let outThisEdges = valueNode.getOutgoingThisEdges(); 558 let combinedEdges = new Set([...(outCopyEdges ?? []), ...(outThisEdges ?? [])]); 559 if (combinedEdges) { 560 combinedEdges.forEach(edge => { 561 let dstID = edge.getDstID(); 562 if (!processedNodes.has(dstID)) { 563 workListNodes.push(dstID); 564 } 565 }); 566 } 567 } 568 569 private detectTypeDiff(nodeId: NodeID): void { 570 if (this.config.detectTypeDiff === false) { 571 return; 572 } 573 574 this.typeDiffMap = this.typeDiffMap ?? new Map(); 575 let node = this.pag.getNode(nodeId) as PagNode; 576 577 let value = node.getValue(); 578 let origType = node.getValue().getType(); 579 // TODO: union type 580 if (!(origType instanceof ClassType || origType instanceof UnknownType)) { 581 return; 582 } 583 584 let findSameType = false; 585 let pts = node.getPointTo(); 586 if (pts.count() === 0) { 587 return; 588 } 589 590 for (let pt of pts) { 591 let ptNode = this.pag.getNode(pt) as PagNode; 592 let type = ptNode.getValue().getType(); 593 if (type.toString() !== origType.toString()) { 594 let diffSet = this.typeDiffMap.get(value) ?? new Set(); 595 this.typeDiffMap.set(value, diffSet); 596 if (!diffSet.has(type)) { 597 diffSet.add(type); 598 } 599 } else { 600 findSameType = true; 601 } 602 } 603 604 // If find pts to original type, 605 // need add original type back since it is a correct type 606 let diffSet = this.typeDiffMap.get(value); 607 if (diffSet && findSameType) { 608 diffSet.add(origType); 609 } 610 } 611 612 public getTypeDiffMap(): Map<Value, Set<Type>> { 613 return this.typeDiffMap ?? new Map(); 614 } 615 616 protected resolveCall(sourceMethod: NodeID, invokeStmt: Stmt): CallSite[] { 617 return []; 618 } 619 620 public getUnhandledFuncs(): FuncID[] { 621 return this.pagBuilder.getUnhandledFuncs(); 622 } 623 624 public getHandledFuncs(): FuncID[] { 625 return this.pagBuilder.getHandledFuncs(); 626 } 627 628 public getPTAConfig(): PointerAnalysisConfig { 629 return this.config; 630 } 631 632 private dumpUnhandledFunctions(): void { 633 const filePath = path.join(this.config.outputDirectory, 'PtaUnhandledFunctionList.txt'); 634 fs.access(filePath, fs.constants.F_OK, err => { 635 if (!err) { 636 fs.truncate(filePath, 0, err => { 637 err && logger.error('Error to truncate file ', err); 638 }); 639 } 640 641 let updatedContent: string = ''; 642 this.getUnhandledFuncs().forEach(funcID => { 643 let cgNode = this.cg.getNode(funcID); 644 if ((cgNode as CallGraphNode).isSdkMethod()) { 645 return; 646 } 647 648 let f = this.cg.getArkMethodByFuncID(funcID); 649 if (f) { 650 updatedContent += f.getSignature().toString() + '\n'; 651 } 652 }); 653 654 fs.writeFile(filePath, updatedContent, 'utf8', err => { 655 if (err) { 656 logger.error('Error to write file', err); 657 } 658 }); 659 }); 660 } 661 662 public mergeInstanceFieldMap(src: Map<number, number[]>, dst: Map<number, number[]>): Map<number, number[]> { 663 dst.forEach((value, key) => { 664 if (src.has(key)) { 665 src.set(key, [...src.get(key)!, ...value]); 666 } else { 667 src.set(key, value); 668 } 669 }); 670 return src; 671 } 672} 673