arkanalyzer 1.0.91 → 1.0.93
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/Scene.d.ts +15 -0
- package/lib/Scene.d.ts.map +1 -1
- package/lib/Scene.js +39 -0
- package/lib/callgraph/algorithm/AbstractAnalysis.d.ts +6 -0
- package/lib/callgraph/algorithm/AbstractAnalysis.d.ts.map +1 -1
- package/lib/callgraph/algorithm/AbstractAnalysis.js +17 -8
- package/lib/callgraph/algorithm/ClassHierarchyAnalysis.d.ts +1 -0
- package/lib/callgraph/algorithm/ClassHierarchyAnalysis.d.ts.map +1 -1
- package/lib/callgraph/algorithm/ClassHierarchyAnalysis.js +9 -3
- package/lib/callgraph/algorithm/RapidTypeAnalysis.d.ts +147 -0
- package/lib/callgraph/algorithm/RapidTypeAnalysis.d.ts.map +1 -1
- package/lib/callgraph/algorithm/RapidTypeAnalysis.js +803 -76
- package/lib/callgraph/model/CallGraph.d.ts +1 -0
- package/lib/callgraph/model/CallGraph.d.ts.map +1 -1
- package/lib/callgraph/model/CallGraph.js +4 -0
- package/lib/callgraph/model/CallSite.d.ts +88 -1
- package/lib/callgraph/model/CallSite.d.ts.map +1 -1
- package/lib/callgraph/model/CallSite.js +58 -4
- package/lib/core/common/ArkIRTransformer.d.ts.map +1 -1
- package/lib/core/common/ArkIRTransformer.js +1 -1
- package/lib/core/common/IRInference.js +2 -2
- package/lib/core/common/SdkUtils.js +1 -1
- package/lib/core/common/TypeInference.d.ts.map +1 -1
- package/lib/core/common/TypeInference.js +3 -0
- package/lib/core/graph/builder/SwitchBuilder.d.ts.map +1 -1
- package/lib/core/graph/builder/SwitchBuilder.js +11 -1
- package/lib/core/graph/builder/ViewTreeBuilder.d.ts +13 -3
- package/lib/core/graph/builder/ViewTreeBuilder.d.ts.map +1 -1
- package/lib/core/graph/builder/ViewTreeBuilder.js +271 -86
- package/lib/core/model/ArkModule.d.ts +9 -12
- package/lib/core/model/ArkModule.d.ts.map +1 -1
- package/lib/core/model/ArkModule.js +7 -10
- package/lib/core/model/builder/ArkClassBuilder.js +10 -4
- package/lib/core/model/builder/ArkMethodBuilder.d.ts +2 -1
- package/lib/core/model/builder/ArkMethodBuilder.d.ts.map +1 -1
- package/lib/core/model/builder/ArkMethodBuilder.js +5 -1
- package/lib/frontend/FrontendBuilder.d.ts +8 -10
- package/lib/frontend/FrontendBuilder.d.ts.map +1 -1
- package/lib/frontend/FrontendBuilder.js +9 -17
- package/lib/frontend/common/ModuleAnalysisConfig.d.ts +8 -1
- package/lib/frontend/common/ModuleAnalysisConfig.d.ts.map +1 -1
- package/lib/frontend/common/ModuleAnalysisConfig.js +10 -1
- package/lib/frontend/common/ModuleBuilder.d.ts +44 -33
- package/lib/frontend/common/ModuleBuilder.d.ts.map +1 -1
- package/lib/frontend/common/ModuleBuilder.js +195 -66
- package/lib/frontend/common/ModuleCache.d.ts +1 -0
- package/lib/frontend/common/ModuleCache.d.ts.map +1 -1
- package/lib/frontend/common/ModuleCache.js +1 -0
- package/lib/frontend/common/ModuleDepth.d.ts +19 -14
- package/lib/frontend/common/ModuleDepth.d.ts.map +1 -1
- package/lib/frontend/common/ModuleDepth.js +25 -15
- package/lib/frontend/cppFrontend/graph/builder/SwitchBuilder.d.ts.map +1 -1
- package/lib/frontend/cppFrontend/graph/builder/SwitchBuilder.js +11 -1
- package/package.json +1 -1
|
@@ -40,25 +40,58 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
40
40
|
exports.RapidTypeAnalysis = void 0;
|
|
41
41
|
const Expr_1 = require("../../core/base/Expr");
|
|
42
42
|
const Stmt_1 = require("../../core/base/Stmt");
|
|
43
|
+
const ArkClass_1 = require("../../core/model/ArkClass");
|
|
44
|
+
const CallGraph_1 = require("../model/CallGraph");
|
|
43
45
|
const AbstractAnalysis_1 = require("./AbstractAnalysis");
|
|
44
46
|
const logger_1 = __importStar(require("../../utils/logger"));
|
|
47
|
+
const Type_1 = require("../../core/base/Type");
|
|
45
48
|
const TSConst_1 = require("../../core/common/TSConst");
|
|
46
49
|
const Local_1 = require("../../core/base/Local");
|
|
47
50
|
const Const_1 = require("../../core/common/Const");
|
|
51
|
+
const SdkUtils_1 = require("../../core/common/SdkUtils");
|
|
52
|
+
const Ref_1 = require("../../core/base/Ref");
|
|
48
53
|
const logger = logger_1.default.getLogger(logger_1.LOG_MODULE_TYPE.ARKANALYZER, 'RTA');
|
|
54
|
+
const BUILTIN_ARRAY_CLASS_NAME = 'Array';
|
|
55
|
+
/**
|
|
56
|
+
* Built-in container APIs that may take FunctionType args but only store them
|
|
57
|
+
* (do not invoke). These must not get virtual callback CallSites.
|
|
58
|
+
* All other no-CFG built-ins default to synthesizing callback edges (forEach/map/then/...).
|
|
59
|
+
*/
|
|
60
|
+
const BUILTIN_CONTAINER_NON_INVOKING_METHODS = new Map([
|
|
61
|
+
['Array', new Set(['push', 'unshift', 'splice', 'indexOf', 'lastIndexOf', 'includes'])],
|
|
62
|
+
['Map', new Set(['set', 'get', 'has', 'delete'])],
|
|
63
|
+
['Set', new Set(['add', 'has', 'delete'])],
|
|
64
|
+
['WeakMap', new Set(['set', 'get', 'has', 'delete'])],
|
|
65
|
+
['WeakSet', new Set(['add', 'has', 'delete'])],
|
|
66
|
+
]);
|
|
67
|
+
const NON_INVOKING_METHOD_NAMES = new Set(Array.from(BUILTIN_CONTAINER_NON_INVOKING_METHODS.values()).flatMap(s => Array.from(s)));
|
|
68
|
+
const CALLBACK_BINDING_ARG_BITS = 8;
|
|
69
|
+
const CALLBACK_BINDING_ARG_STRIDE = 1 << CALLBACK_BINDING_ARG_BITS;
|
|
49
70
|
class RapidTypeAnalysis extends AbstractAnalysis_1.AbstractAnalysis {
|
|
50
71
|
constructor(scene, cg, cb, enableThisPrune = false) {
|
|
51
72
|
super(scene, cg);
|
|
52
73
|
// TODO: signature duplicated check
|
|
53
74
|
this.instancedClasses = new Set();
|
|
54
|
-
// TODO: Set duplicated check
|
|
55
75
|
this.ignoredCalls = new Map();
|
|
56
76
|
this.virtualDispatchIndex = new Map();
|
|
77
|
+
// ownerFuncID → logical argument index → lazily-created binding state
|
|
78
|
+
this.callbackBindings = new Map();
|
|
79
|
+
// FieldSignature → callback targets/subscribers. This is intentionally type-based,
|
|
80
|
+
// not allocation-site-sensitive, to keep field callback propagation lightweight.
|
|
81
|
+
this.callbackFieldBindings = new Map();
|
|
82
|
+
// Reused scratch storage for closure-chain cycle detection.
|
|
83
|
+
this.closureTraceVisited = new Set();
|
|
84
|
+
// Reused scratch for AliasType peeling in unwrapFunctionType.
|
|
85
|
+
this.aliasTypeUnwrapVisited = new Set();
|
|
86
|
+
// Reused scratch list of executable callee FuncIDs for one resolveCall.
|
|
87
|
+
this.executableCalleeScratch = [];
|
|
88
|
+
// Whether getBuiltinArrayClassSignature has completed its one-time lookup (hit or miss).
|
|
89
|
+
this.builtinArrayClassResolved = false;
|
|
57
90
|
this.cgBuilder = cb;
|
|
58
91
|
this.enableThisPrune = enableThisPrune;
|
|
59
92
|
}
|
|
60
93
|
init() {
|
|
61
|
-
this.scene.getClasses().filter(c => c.getName() === Const_1.DEFAULT_ARK_CLASS_NAME).forEach(c => {
|
|
94
|
+
this.scene.getClasses().filter((c) => c.getName() === Const_1.DEFAULT_ARK_CLASS_NAME).forEach((c) => {
|
|
62
95
|
var _a;
|
|
63
96
|
const dfltMethod = (_a = c.getMethodWithName(Const_1.DEFAULT_ARK_METHOD_NAME)) === null || _a === void 0 ? void 0 : _a.getSignature();
|
|
64
97
|
if (dfltMethod) {
|
|
@@ -69,63 +102,718 @@ class RapidTypeAnalysis extends AbstractAnalysis_1.AbstractAnalysis {
|
|
|
69
102
|
super.init();
|
|
70
103
|
}
|
|
71
104
|
resolveCall(callerMethod, invokeStmt) {
|
|
72
|
-
|
|
73
|
-
let resolveResult = [];
|
|
105
|
+
const invokeExpr = invokeStmt.getInvokeExpr();
|
|
74
106
|
if (!invokeExpr) {
|
|
75
107
|
return [];
|
|
76
108
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
109
|
+
if (invokeExpr instanceof Expr_1.ArkPtrInvokeExpr) {
|
|
110
|
+
return this.resolvePtrInvoke(callerMethod, invokeStmt, invokeExpr);
|
|
111
|
+
}
|
|
112
|
+
const resolveResult = [];
|
|
113
|
+
const declared = this.resolveInvokeExpr(invokeExpr);
|
|
114
|
+
this.resolveDirectOrVirtual(callerMethod, invokeStmt, invokeExpr, declared, resolveResult);
|
|
115
|
+
this.processCallbackArguments(callerMethod, invokeStmt, invokeExpr, declared, resolveResult);
|
|
116
|
+
return resolveResult;
|
|
117
|
+
}
|
|
118
|
+
resolveDirectOrVirtual(callerMethod, invokeStmt, invokeExpr, calleeMethod, resolveResult) {
|
|
82
119
|
if (!calleeMethod) {
|
|
83
|
-
return
|
|
120
|
+
return;
|
|
84
121
|
}
|
|
85
122
|
if (invokeExpr instanceof Expr_1.ArkStaticInvokeExpr) {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
123
|
+
resolveResult.push(this.newRTACallSite(invokeStmt, callerMethod, this.cg.getCallGraphNodeByMethod(calleeMethod.getSignature()).getID()));
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
const declareClass = calleeMethod.getDeclaringArkClass();
|
|
127
|
+
const methodName = calleeMethod.getName();
|
|
128
|
+
// Aggressive heuristic: when enabled and the call is `this.foo()`,
|
|
129
|
+
// only keep the implementation of `foo` in the current declaring class,
|
|
130
|
+
// or the first superclass that defines it (e.g. B.m() calling this.foo() → A.foo() when B extends A).
|
|
131
|
+
const prunedCallSite = this.tryResolveAggressiveThisPruneCall(invokeStmt, methodName, callerMethod);
|
|
132
|
+
if (prunedCallSite) {
|
|
133
|
+
resolveResult.push(prunedCallSite);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
const directCallSite = this.resolveConstructorClassFromNew(methodName, invokeStmt, callerMethod);
|
|
137
|
+
if (directCallSite) {
|
|
138
|
+
resolveResult.push(directCallSite);
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
// Private methods cannot be overridden; only the declaring class has an implementation.
|
|
142
|
+
if (calleeMethod.isPrivate()) {
|
|
143
|
+
const calleeNode = this.cg.getCallGraphNodeByMethod(calleeMethod.getSignature());
|
|
144
|
+
if (this.instancedClasses.has(declareClass.getSignature())) {
|
|
145
|
+
resolveResult.push(this.newRTACallSite(invokeStmt, callerMethod, calleeNode.getID()));
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
this.addIgnoredCalls(declareClass.getSignature(), callerMethod, calleeNode.getID(), invokeStmt);
|
|
149
|
+
}
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
const virtualDispatchRows = this.getOrBuildVirtualDispatchRows(declareClass, methodName);
|
|
153
|
+
for (let i = 0; i < virtualDispatchRows.classSignatures.length; i++) {
|
|
154
|
+
const classSignature = virtualDispatchRows.classSignatures[i];
|
|
155
|
+
if (virtualDispatchRows.isUnconditionallyReachable[i] || this.instancedClasses.has(classSignature)) {
|
|
156
|
+
resolveResult.push(this.newRTACallSite(invokeStmt, callerMethod, virtualDispatchRows.calleeNodeIDs[i]));
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
this.addIgnoredCalls(classSignature, callerMethod, virtualDispatchRows.calleeNodeIDs[i], invokeStmt);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Single args scan:
|
|
165
|
+
* - if any executable real callee exists (or declared callee has CFG), record callback bindings
|
|
166
|
+
* (concrete FunctionType) or forward parameter bindings into callee slots;
|
|
167
|
+
* - otherwise, for no-CFG SDK / built-in / unresolved (`%unk`) callees, append virtual
|
|
168
|
+
* callback CallSites for concrete FunctionType args, parameter bindings, and callback
|
|
169
|
+
* fields.
|
|
170
|
+
*/
|
|
171
|
+
processCallbackArguments(callerMethod, invokeStmt, invokeExpr, declared, resolveResult) {
|
|
172
|
+
const hasVirtualCallbackCallee = this.scanExecutableCallees(resolveResult);
|
|
173
|
+
const hasExecutableTarget = this.executableCalleeScratch.length > 0;
|
|
174
|
+
const declaredHasCfg = declared !== undefined && declared.getCfg() !== undefined;
|
|
175
|
+
if (!hasExecutableTarget && !declaredHasCfg) {
|
|
176
|
+
this.handleNoExecutableCalleePath(callerMethod, invokeStmt, declared, hasVirtualCallbackCallee, resolveResult);
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
let declaredOwnerFuncID;
|
|
180
|
+
if (declared && !hasExecutableTarget) {
|
|
181
|
+
declaredOwnerFuncID = this.cg.getCallGraphNodeByMethod(declared.getSignature()).getID();
|
|
182
|
+
}
|
|
183
|
+
const args = invokeExpr.getArgs();
|
|
184
|
+
const callerArkMethod = this.cg.getArkMethodByFuncID(callerMethod);
|
|
185
|
+
for (let i = 0; i < args.length; i++) {
|
|
186
|
+
const arg = args[i];
|
|
187
|
+
const functionType = this.unwrapFunctionType(arg.getType());
|
|
188
|
+
if (!functionType && !this.isCallbackType(arg.getType())) {
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
if (functionType) {
|
|
192
|
+
this.bindFunctionTypeCallback(functionType, i, hasExecutableTarget, declaredOwnerFuncID);
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
this.bindClassTypeCallback(arg, i, callerArkMethod, hasExecutableTarget, declaredOwnerFuncID);
|
|
196
|
+
}
|
|
197
|
+
// Parameter forwarding: start(cb) { this.schedule(cb); }
|
|
198
|
+
// Propagate caller's parameter binding into each callee's argument slot i.
|
|
199
|
+
if (!(arg instanceof Local_1.Local) || !callerArkMethod) {
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
const sourceKey = this.resolveFuncPtrToBindingKey(arg, callerArkMethod);
|
|
203
|
+
if (sourceKey === undefined) {
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
if (hasExecutableTarget) {
|
|
207
|
+
for (let j = 0; j < this.executableCalleeScratch.length; j++) {
|
|
208
|
+
this.linkCallbackBinding(sourceKey, this.encodeCallbackBindingKey(this.executableCalleeScratch[j], i));
|
|
113
209
|
}
|
|
114
|
-
return resolveResult;
|
|
115
210
|
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
211
|
+
else if (declaredOwnerFuncID !== undefined) {
|
|
212
|
+
this.linkCallbackBinding(sourceKey, this.encodeCallbackBindingKey(declaredOwnerFuncID, i));
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Bind a FunctionType callback argument to the callee's parameter binding slot.
|
|
218
|
+
* Extracted from processCallbackArguments for readability.
|
|
219
|
+
*/
|
|
220
|
+
bindFunctionTypeCallback(functionType, argIndex, hasExecutableTarget, declaredOwnerFuncID) {
|
|
221
|
+
const callbackMethod = this.scene.getMethod(functionType.getMethodSignature());
|
|
222
|
+
if (callbackMethod && callbackMethod.getCfg()) {
|
|
223
|
+
const targetFuncID = this.cg.getCallGraphNodeByMethod(callbackMethod.getSignature()).getID();
|
|
224
|
+
if (hasExecutableTarget) {
|
|
225
|
+
for (let j = 0; j < this.executableCalleeScratch.length; j++) {
|
|
226
|
+
this.addCallbackTarget(this.executableCalleeScratch[j], argIndex, targetFuncID);
|
|
121
227
|
}
|
|
122
|
-
|
|
123
|
-
|
|
228
|
+
}
|
|
229
|
+
else if (declaredOwnerFuncID !== undefined) {
|
|
230
|
+
this.addCallbackTarget(declaredOwnerFuncID, argIndex, targetFuncID);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Bind a ClassType callback (Callback<T>, AsyncCallback<T>, ...) argument to the
|
|
236
|
+
* callee's parameter binding slot via field binding state.
|
|
237
|
+
* Extracted from processCallbackArguments for readability.
|
|
238
|
+
*/
|
|
239
|
+
bindClassTypeCallback(arg, argIndex, callerArkMethod, hasExecutableTarget, declaredOwnerFuncID) {
|
|
240
|
+
let fieldRef;
|
|
241
|
+
if (arg instanceof Ref_1.ArkInstanceFieldRef) {
|
|
242
|
+
fieldRef = arg;
|
|
243
|
+
}
|
|
244
|
+
else if (arg instanceof Local_1.Local && callerArkMethod) {
|
|
245
|
+
fieldRef = this.resolveFuncPtrToFieldRef(arg, callerArkMethod);
|
|
246
|
+
}
|
|
247
|
+
if (fieldRef) {
|
|
248
|
+
const fieldKey = this.getCallbackFieldKey(fieldRef);
|
|
249
|
+
if (hasExecutableTarget) {
|
|
250
|
+
for (let j = 0; j < this.executableCalleeScratch.length; j++) {
|
|
251
|
+
const bindingKey = this.encodeCallbackBindingKey(this.executableCalleeScratch[j], argIndex);
|
|
252
|
+
this.linkFieldToBinding(fieldKey, bindingKey);
|
|
124
253
|
}
|
|
125
254
|
}
|
|
255
|
+
else if (declaredOwnerFuncID !== undefined) {
|
|
256
|
+
const bindingKey = this.encodeCallbackBindingKey(declaredOwnerFuncID, argIndex);
|
|
257
|
+
this.linkFieldToBinding(fieldKey, bindingKey);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
scanExecutableCallees(resolveResult) {
|
|
262
|
+
this.executableCalleeScratch.length = 0;
|
|
263
|
+
let hasVirtualCallbackCallee = false;
|
|
264
|
+
for (let i = 0; i < resolveResult.length; i++) {
|
|
265
|
+
const calleeId = resolveResult[i].getCalleeFuncID();
|
|
266
|
+
if (calleeId === undefined) {
|
|
267
|
+
continue;
|
|
268
|
+
}
|
|
269
|
+
const method = this.cg.getArkMethodByFuncID(calleeId);
|
|
270
|
+
if (method && method.getCfg()) {
|
|
271
|
+
this.executableCalleeScratch.push(calleeId);
|
|
272
|
+
}
|
|
273
|
+
else if (this.shouldSynthesizeVirtualCallback(calleeId)) {
|
|
274
|
+
hasVirtualCallbackCallee = true;
|
|
275
|
+
}
|
|
126
276
|
}
|
|
277
|
+
return hasVirtualCallbackCallee;
|
|
278
|
+
}
|
|
279
|
+
handleNoExecutableCalleePath(callerMethod, invokeStmt, declared, hasVirtualCallbackCallee, resolveResult) {
|
|
280
|
+
const invokeExpr = invokeStmt.getInvokeExpr();
|
|
281
|
+
if (!invokeExpr) {
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
const args = invokeExpr.getArgs();
|
|
285
|
+
let shouldAppendVirtual = hasVirtualCallbackCallee;
|
|
286
|
+
if (!shouldAppendVirtual && declared) {
|
|
287
|
+
const declaredOwnerFuncID = this.cg.getCallGraphNodeByMethod(declared.getSignature()).getID();
|
|
288
|
+
shouldAppendVirtual = this.shouldSynthesizeVirtualCallback(declaredOwnerFuncID);
|
|
289
|
+
}
|
|
290
|
+
// Declaring signature may be `@%unk/%unk:...` with no Scene method / no CFG node that
|
|
291
|
+
// passes the SDK/built-in gates; still synthesize callback edges from the invoke itself.
|
|
292
|
+
if (!shouldAppendVirtual) {
|
|
293
|
+
shouldAppendVirtual = this.shouldSynthesizeVirtualCallbackFromInvoke(invokeExpr);
|
|
294
|
+
}
|
|
295
|
+
if (shouldAppendVirtual) {
|
|
296
|
+
this.appendVirtualCallbackCallSites(callerMethod, invokeStmt, args, resolveResult);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Unresolved declaring file (`@%unk/%unk:...`) — common when type inference fails for
|
|
301
|
+
* built-ins such as Promise.constructor in large apps.
|
|
302
|
+
*/
|
|
303
|
+
isUnresolvedProjectName(projectName) {
|
|
304
|
+
return projectName === '%unk';
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Built-in container APIs that accept FunctionType values without invoking them
|
|
308
|
+
* (writes such as push/set, and lookups such as indexOf/has). Caller must already
|
|
309
|
+
* know the method is a `@built-in` SDK method.
|
|
310
|
+
*/
|
|
311
|
+
isBuiltInContainerNonInvokingMethod(method) {
|
|
312
|
+
if (!method) {
|
|
313
|
+
return false;
|
|
314
|
+
}
|
|
315
|
+
const nonInvokingNames = BUILTIN_CONTAINER_NON_INVOKING_METHODS.get(method.getDeclaringArkClass().getName());
|
|
316
|
+
if (!nonInvokingNames) {
|
|
317
|
+
return false;
|
|
318
|
+
}
|
|
319
|
+
return nonInvokingNames.has(method.getName());
|
|
320
|
+
}
|
|
321
|
+
isBuiltInContainerNonInvokingMethodByMethodName(methodName) {
|
|
322
|
+
return NON_INVOKING_METHOD_NAMES.has(methodName);
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* Whether a no-CFG callee should synthesize virtual edges into callback args.
|
|
326
|
+
* OH SDK and unresolved (`%unk`) APIs always qualify. Built-ins qualify by default
|
|
327
|
+
* except container non-invoking APIs (Array.push / Array.indexOf / Map.set / ...).
|
|
328
|
+
* Resolves the CG node and declaring projectName once.
|
|
329
|
+
*/
|
|
330
|
+
shouldSynthesizeVirtualCallback(id) {
|
|
331
|
+
const node = this.cg.getNode(id);
|
|
332
|
+
if (!node) {
|
|
333
|
+
return false;
|
|
334
|
+
}
|
|
335
|
+
const projectName = node.getMethod().getDeclaringClassSignature().getDeclaringFileSignature().getProjectName();
|
|
336
|
+
// Same order as previous isOHSdkMethod → isUnresolved → isBuiltIn path.
|
|
337
|
+
if (node.isSdkMethod() && projectName !== SdkUtils_1.SdkUtils.BUILT_IN_NAME) {
|
|
338
|
+
return true;
|
|
339
|
+
}
|
|
340
|
+
if (this.isUnresolvedProjectName(projectName)) {
|
|
341
|
+
return !this.isBuiltInContainerNonInvokingMethodByMethodName(node.getMethod().getMethodSubSignature().getMethodName());
|
|
342
|
+
}
|
|
343
|
+
if (node.isSdkMethod() && projectName === SdkUtils_1.SdkUtils.BUILT_IN_NAME) {
|
|
344
|
+
return !this.isBuiltInContainerNonInvokingMethod(this.cg.getArkMethodByFuncID(id));
|
|
345
|
+
}
|
|
346
|
+
return false;
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* Invoke-site gate when {@link resolveInvokeExpr} cannot produce a usable declared method
|
|
350
|
+
* (e.g. `@%unk/%unk: Promise.constructor` with no Scene body).
|
|
351
|
+
*/
|
|
352
|
+
shouldSynthesizeVirtualCallbackFromInvoke(invokeExpr) {
|
|
353
|
+
const projectName = invokeExpr.getMethodSignature().getDeclaringClassSignature().getDeclaringFileSignature().getProjectName();
|
|
354
|
+
if (!this.isUnresolvedProjectName(projectName)) {
|
|
355
|
+
return false;
|
|
356
|
+
}
|
|
357
|
+
return !this.isBuiltInContainerNonInvokingMethodByMethodName(invokeExpr.getMethodSignature().getMethodSubSignature().getMethodName());
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* No-CFG callee that invokes callbacks: emit virtual edges into every callback-shaped argument.
|
|
361
|
+
* Caller must have already gated on {@link shouldSynthesizeVirtualCallback}. Built-in container
|
|
362
|
+
* non-invoking APIs (Array.push / Array.indexOf / Map.set / ...) remain excluded by that gate.
|
|
363
|
+
*/
|
|
364
|
+
appendVirtualCallbackCallSites(callerMethod, invokeStmt, args, resolveResult) {
|
|
365
|
+
for (let i = 0; i < args.length; i++) {
|
|
366
|
+
this.appendCallbackValueCallSites(args[i], invokeStmt, callerMethod, resolveResult, i);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
resolvePtrInvoke(callerMethod, invokeStmt, invokeExpr) {
|
|
370
|
+
const resolveResult = [];
|
|
371
|
+
const ptr = invokeExpr.getFuncPtrLocal();
|
|
372
|
+
this.appendCallbackValueCallSites(ptr, invokeStmt, callerMethod, resolveResult, -1);
|
|
127
373
|
return resolveResult;
|
|
128
374
|
}
|
|
375
|
+
/**
|
|
376
|
+
* Resolve a callback value through all supported representations.
|
|
377
|
+
* A value may have both a concrete static target and a field/binding origin;
|
|
378
|
+
* both paths are retained so later field targets cannot be hidden by the
|
|
379
|
+
* concrete FunctionType fast path.
|
|
380
|
+
*/
|
|
381
|
+
appendCallbackValueCallSites(value, invokeStmt, callerMethod, resolveResult, argIndex) {
|
|
382
|
+
var _a;
|
|
383
|
+
let functionType = this.unwrapFunctionType(value.getType());
|
|
384
|
+
if (!functionType) {
|
|
385
|
+
if (value instanceof Local_1.Local && this.isCallbackType(value.getType())) {
|
|
386
|
+
const defStmt = value.getDeclaringStmt();
|
|
387
|
+
if (defStmt instanceof Stmt_1.ArkAssignStmt) {
|
|
388
|
+
functionType = this.unwrapFunctionType(defStmt.getRightOp().getType());
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
if (functionType) {
|
|
393
|
+
const concreteMethod = this.scene.getMethod(functionType.getMethodSignature());
|
|
394
|
+
if (concreteMethod && concreteMethod.getCfg()) {
|
|
395
|
+
const targetFuncID = this.cg.getCallGraphNodeByMethod(concreteMethod.getSignature()).getID();
|
|
396
|
+
this.appendCallbackCallSite(invokeStmt, callerMethod, targetFuncID, resolveResult, argIndex);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
if (value instanceof Ref_1.ArkInstanceFieldRef) {
|
|
400
|
+
this.subscribeToCallbackField(value, invokeStmt, callerMethod, resolveResult, argIndex);
|
|
401
|
+
}
|
|
402
|
+
const ownerMethod = (_a = invokeStmt.getCfg()) === null || _a === void 0 ? void 0 : _a.getDeclaringMethod();
|
|
403
|
+
if (!(value instanceof Local_1.Local) || !ownerMethod) {
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
const bindingState = this.resolveFuncPtrToBindingState(value, ownerMethod);
|
|
407
|
+
if (bindingState) {
|
|
408
|
+
this.subscribeAndCollectBoundTargets(bindingState, invokeStmt, callerMethod, resolveResult, argIndex);
|
|
409
|
+
}
|
|
410
|
+
const fieldRef = this.resolveFuncPtrToFieldRef(value, ownerMethod);
|
|
411
|
+
if (fieldRef && this.isCallbackType(fieldRef.getType())) {
|
|
412
|
+
this.subscribeToCallbackField(fieldRef, invokeStmt, callerMethod, resolveResult, argIndex);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
appendCallbackCallSite(invokeStmt, callerMethod, targetFuncID, resolveResult, argIndex) {
|
|
416
|
+
const hasTarget = resolveResult.some((callSite) => {
|
|
417
|
+
return callSite.getCalleeFuncID() === targetFuncID;
|
|
418
|
+
});
|
|
419
|
+
if (hasTarget) {
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
resolveResult.push(this.newRTACallSite(invokeStmt, callerMethod, targetFuncID, argIndex));
|
|
423
|
+
}
|
|
424
|
+
subscribeToCallbackField(fieldRef, invokeStmt, callerMethod, resolveResult, argIndex) {
|
|
425
|
+
if (!this.isCallbackType(fieldRef.getType())) {
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
const fieldKey = this.getCallbackFieldKey(fieldRef);
|
|
429
|
+
this.subscribeAndCollectFieldTargets(fieldKey, invokeStmt, callerMethod, resolveResult, argIndex);
|
|
430
|
+
}
|
|
431
|
+
/**
|
|
432
|
+
* Subscribe invokeStmt to bindingState and collect already-known targets as CallSites.
|
|
433
|
+
* Late-arriving targets are delivered via addCallbackTarget → emitCallbackEdge.
|
|
434
|
+
*/
|
|
435
|
+
subscribeAndCollectBoundTargets(bindingState, invokeStmt, callerMethod, resolveResult, argIndex) {
|
|
436
|
+
if (bindingState.subscribers.has(invokeStmt)) {
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
bindingState.subscribers.set(invokeStmt, callerMethod);
|
|
440
|
+
for (const targetFuncID of bindingState.targets) {
|
|
441
|
+
this.appendCallbackCallSite(invokeStmt, callerMethod, targetFuncID, resolveResult, argIndex);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
getTypeFromAliasType(aliasType) {
|
|
445
|
+
const visited = this.aliasTypeUnwrapVisited;
|
|
446
|
+
visited.clear();
|
|
447
|
+
let current = aliasType;
|
|
448
|
+
while (current instanceof Type_1.AliasType) {
|
|
449
|
+
if (visited.has(current)) {
|
|
450
|
+
return null;
|
|
451
|
+
}
|
|
452
|
+
visited.add(current);
|
|
453
|
+
current = current.getOriginalType();
|
|
454
|
+
}
|
|
455
|
+
return current;
|
|
456
|
+
}
|
|
457
|
+
/**
|
|
458
|
+
* Whether a type can serve as a callback — FunctionType (direct or aliased)
|
|
459
|
+
* or a ClassType callback wrapper (Callback<T>, AsyncCallback<T>, ...).
|
|
460
|
+
*
|
|
461
|
+
* Unlike {@link unwrapFunctionType}, this accepts ClassType callbacks whose real
|
|
462
|
+
* FunctionType is only discoverable at the assignment site (right-hand side),
|
|
463
|
+
* not from the ClassType itself. This lets the field-subscription and field-store
|
|
464
|
+
* pipelines accept `private cb: Callback<X> = (...) => {...}` fields.
|
|
465
|
+
*/
|
|
466
|
+
isCallbackType(type) {
|
|
467
|
+
let targetType = type;
|
|
468
|
+
if (type instanceof Type_1.AliasType) {
|
|
469
|
+
targetType = this.getTypeFromAliasType(type);
|
|
470
|
+
}
|
|
471
|
+
if (targetType instanceof Type_1.FunctionType) {
|
|
472
|
+
return true;
|
|
473
|
+
}
|
|
474
|
+
if (targetType instanceof Type_1.ClassType) {
|
|
475
|
+
const targetSig = targetType.getClassSignature().toString();
|
|
476
|
+
return RapidTypeAnalysis.CALLBACK_CLASS_SIG_KEY.some(sig => targetSig.includes(sig));
|
|
477
|
+
}
|
|
478
|
+
return false;
|
|
479
|
+
}
|
|
480
|
+
/**
|
|
481
|
+
* Peel AliasType layers until a FunctionType is found.
|
|
482
|
+
* Fast-path: FunctionType returns immediately; non-alias types return undefined
|
|
483
|
+
* without touching the visited scratch set.
|
|
484
|
+
*/
|
|
485
|
+
unwrapFunctionType(type) {
|
|
486
|
+
let targetType = type;
|
|
487
|
+
if (type instanceof Type_1.AliasType) {
|
|
488
|
+
targetType = this.getTypeFromAliasType(type);
|
|
489
|
+
}
|
|
490
|
+
if (targetType instanceof Type_1.FunctionType) {
|
|
491
|
+
return targetType;
|
|
492
|
+
}
|
|
493
|
+
return null;
|
|
494
|
+
}
|
|
495
|
+
resolveFuncPtrToBindingState(ptr, ownerMethod) {
|
|
496
|
+
const key = this.resolveFuncPtrToBindingKey(ptr, ownerMethod);
|
|
497
|
+
if (key === undefined) {
|
|
498
|
+
return undefined;
|
|
499
|
+
}
|
|
500
|
+
return this.getOrCreateBindingState(this.decodeOwnerFuncID(key), this.decodeLogicalArgIndex(key));
|
|
501
|
+
}
|
|
502
|
+
resolveFuncPtrToBindingKey(ptr, ownerMethod) {
|
|
503
|
+
this.closureTraceVisited.clear();
|
|
504
|
+
const startLocal = this.recoverLocalWithDeclaringStmt(ptr, ownerMethod);
|
|
505
|
+
if (!startLocal) {
|
|
506
|
+
return undefined;
|
|
507
|
+
}
|
|
508
|
+
return this.resolveFuncPtrToBindingKeyRec(startLocal);
|
|
509
|
+
}
|
|
510
|
+
/**
|
|
511
|
+
* Resolve a function-pointer local to an instance callback field.
|
|
512
|
+
* The short-term field model follows one local alias at most; it does not
|
|
513
|
+
* perform receiver points-to analysis.
|
|
514
|
+
*/
|
|
515
|
+
resolveFuncPtrToFieldRef(ptr, ownerMethod) {
|
|
516
|
+
this.closureTraceVisited.clear();
|
|
517
|
+
let current = this.recoverLocalWithDeclaringStmt(ptr, ownerMethod);
|
|
518
|
+
while (current) {
|
|
519
|
+
if (this.closureTraceVisited.has(current)) {
|
|
520
|
+
return undefined;
|
|
521
|
+
}
|
|
522
|
+
this.closureTraceVisited.add(current);
|
|
523
|
+
const declaringStmt = current.getDeclaringStmt();
|
|
524
|
+
if (!(declaringStmt instanceof Stmt_1.ArkAssignStmt)) {
|
|
525
|
+
return undefined;
|
|
526
|
+
}
|
|
527
|
+
const rightOp = declaringStmt.getRightOp();
|
|
528
|
+
if (rightOp instanceof Ref_1.ArkInstanceFieldRef) {
|
|
529
|
+
return rightOp;
|
|
530
|
+
}
|
|
531
|
+
if (!(rightOp instanceof Local_1.Local)) {
|
|
532
|
+
return undefined;
|
|
533
|
+
}
|
|
534
|
+
current = this.recoverLocalWithDeclaringStmt(rightOp, ownerMethod);
|
|
535
|
+
}
|
|
536
|
+
return undefined;
|
|
537
|
+
}
|
|
538
|
+
resolveFuncPtrValueToBindingKey(value, ownerMethod) {
|
|
539
|
+
if (value instanceof Local_1.Local) {
|
|
540
|
+
return this.resolveFuncPtrToBindingKey(value, ownerMethod);
|
|
541
|
+
}
|
|
542
|
+
if (!(value instanceof Ref_1.ArkParameterRef)) {
|
|
543
|
+
return undefined;
|
|
544
|
+
}
|
|
545
|
+
const logicalArgIndex = this.toLogicalArgIndex(ownerMethod, value.getIndex());
|
|
546
|
+
if (logicalArgIndex < 0) {
|
|
547
|
+
return undefined;
|
|
548
|
+
}
|
|
549
|
+
const ownerFuncID = this.cg.getCallGraphNodeByMethod(ownerMethod.getSignature()).getID();
|
|
550
|
+
return this.encodeCallbackBindingKey(ownerFuncID, logicalArgIndex);
|
|
551
|
+
}
|
|
552
|
+
/**
|
|
553
|
+
* AliasType-typed ptr locals on invoke exprs may lack declaringStmt even when the
|
|
554
|
+
* method body has a same-named local that carries the real definition.
|
|
555
|
+
*/
|
|
556
|
+
recoverLocalWithDeclaringStmt(local, ownerMethod) {
|
|
557
|
+
if (local.getDeclaringStmt()) {
|
|
558
|
+
return local;
|
|
559
|
+
}
|
|
560
|
+
const body = ownerMethod.getBody();
|
|
561
|
+
if (!body) {
|
|
562
|
+
return undefined;
|
|
563
|
+
}
|
|
564
|
+
const candidate = body.getLocals().get(local.getName());
|
|
565
|
+
if (candidate && candidate.getDeclaringStmt()) {
|
|
566
|
+
return candidate;
|
|
567
|
+
}
|
|
568
|
+
return undefined;
|
|
569
|
+
}
|
|
570
|
+
/**
|
|
571
|
+
* Recursively trace a Local back to a parameter binding key.
|
|
572
|
+
* Follows local aliases and unique closure captures until an ArkParameterRef is
|
|
573
|
+
* found; returns the packed (ownerFuncID, logicalArgIndex) key, or undefined if
|
|
574
|
+
* the value does not originate from a parameter slot.
|
|
575
|
+
*/
|
|
576
|
+
resolveFuncPtrToBindingKeyRec(local) {
|
|
577
|
+
var _a;
|
|
578
|
+
if (this.closureTraceVisited.has(local)) {
|
|
579
|
+
return undefined;
|
|
580
|
+
}
|
|
581
|
+
this.closureTraceVisited.add(local);
|
|
582
|
+
const declaringStmt = local.getDeclaringStmt();
|
|
583
|
+
if (!(declaringStmt instanceof Stmt_1.ArkAssignStmt)) {
|
|
584
|
+
return undefined;
|
|
585
|
+
}
|
|
586
|
+
const rightOp = declaringStmt.getRightOp();
|
|
587
|
+
if (rightOp instanceof Ref_1.ArkParameterRef) {
|
|
588
|
+
const ownerMethod = (_a = declaringStmt.getCfg()) === null || _a === void 0 ? void 0 : _a.getDeclaringMethod();
|
|
589
|
+
if (!ownerMethod) {
|
|
590
|
+
return undefined;
|
|
591
|
+
}
|
|
592
|
+
const logicalArgIndex = this.toLogicalArgIndex(ownerMethod, rightOp.getIndex());
|
|
593
|
+
if (logicalArgIndex < 0) {
|
|
594
|
+
return undefined;
|
|
595
|
+
}
|
|
596
|
+
const ownerFuncID = this.cg.getCallGraphNodeByMethod(ownerMethod.getSignature()).getID();
|
|
597
|
+
return this.encodeCallbackBindingKey(ownerFuncID, logicalArgIndex);
|
|
598
|
+
}
|
|
599
|
+
// Local alias: const invoke = cb;
|
|
600
|
+
if (rightOp instanceof Local_1.Local) {
|
|
601
|
+
return this.resolveFuncPtrToBindingKeyRec(rightOp);
|
|
602
|
+
}
|
|
603
|
+
if (rightOp instanceof Ref_1.ClosureFieldRef) {
|
|
604
|
+
const baseType = rightOp.getBase().getType();
|
|
605
|
+
if (!(baseType instanceof Type_1.LexicalEnvType)) {
|
|
606
|
+
return undefined;
|
|
607
|
+
}
|
|
608
|
+
const closures = baseType.getClosures();
|
|
609
|
+
const fieldName = rightOp.getFieldName();
|
|
610
|
+
let match;
|
|
611
|
+
let matchCount = 0;
|
|
612
|
+
for (let i = 0; i < closures.length; i++) {
|
|
613
|
+
if (closures[i].getName() === fieldName) {
|
|
614
|
+
match = closures[i];
|
|
615
|
+
matchCount++;
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
if (matchCount !== 1 || !match) {
|
|
619
|
+
return undefined;
|
|
620
|
+
}
|
|
621
|
+
return this.resolveFuncPtrToBindingKeyRec(match);
|
|
622
|
+
}
|
|
623
|
+
return undefined;
|
|
624
|
+
}
|
|
625
|
+
toLogicalArgIndex(method, rawParamIndex) {
|
|
626
|
+
const params = method.getParameters();
|
|
627
|
+
let offset = 0;
|
|
628
|
+
if (params && params.length > 0 && params[0].getType() instanceof Type_1.LexicalEnvType) {
|
|
629
|
+
offset = 1;
|
|
630
|
+
}
|
|
631
|
+
const logicalIndex = rawParamIndex - offset;
|
|
632
|
+
if (logicalIndex < 0) {
|
|
633
|
+
return -1;
|
|
634
|
+
}
|
|
635
|
+
return logicalIndex;
|
|
636
|
+
}
|
|
637
|
+
encodeCallbackBindingKey(ownerFuncID, logicalArgIndex) {
|
|
638
|
+
return ownerFuncID * CALLBACK_BINDING_ARG_STRIDE + logicalArgIndex;
|
|
639
|
+
}
|
|
640
|
+
decodeOwnerFuncID(key) {
|
|
641
|
+
return Math.floor(key / CALLBACK_BINDING_ARG_STRIDE);
|
|
642
|
+
}
|
|
643
|
+
decodeLogicalArgIndex(key) {
|
|
644
|
+
return key % CALLBACK_BINDING_ARG_STRIDE;
|
|
645
|
+
}
|
|
646
|
+
getOrCreateBindingState(ownerFuncID, logicalArgIndex) {
|
|
647
|
+
let states = this.callbackBindings.get(ownerFuncID);
|
|
648
|
+
if (!states) {
|
|
649
|
+
states = [];
|
|
650
|
+
this.callbackBindings.set(ownerFuncID, states);
|
|
651
|
+
}
|
|
652
|
+
while (states.length <= logicalArgIndex) {
|
|
653
|
+
states.push(undefined);
|
|
654
|
+
}
|
|
655
|
+
let state = states[logicalArgIndex];
|
|
656
|
+
if (!state) {
|
|
657
|
+
state = {
|
|
658
|
+
targets: new Set(),
|
|
659
|
+
subscribers: new Map(),
|
|
660
|
+
forwards: new Set(),
|
|
661
|
+
fieldForwards: new Set(),
|
|
662
|
+
};
|
|
663
|
+
states[logicalArgIndex] = state;
|
|
664
|
+
}
|
|
665
|
+
return state;
|
|
666
|
+
}
|
|
667
|
+
getCallbackFieldKey(fieldRef) {
|
|
668
|
+
return fieldRef.getFieldSignature().toString();
|
|
669
|
+
}
|
|
670
|
+
getOrCreateCallbackFieldState(fieldKey) {
|
|
671
|
+
let state = this.callbackFieldBindings.get(fieldKey);
|
|
672
|
+
if (!state) {
|
|
673
|
+
state = {
|
|
674
|
+
targets: new Set(),
|
|
675
|
+
subscribers: new Map(),
|
|
676
|
+
bindingForwards: new Set(),
|
|
677
|
+
};
|
|
678
|
+
this.callbackFieldBindings.set(fieldKey, state);
|
|
679
|
+
}
|
|
680
|
+
return state;
|
|
681
|
+
}
|
|
682
|
+
linkCallbackField(source, fieldKey) {
|
|
683
|
+
const sourceState = this.getOrCreateBindingState(this.decodeOwnerFuncID(source), this.decodeLogicalArgIndex(source));
|
|
684
|
+
if (sourceState.fieldForwards.has(fieldKey)) {
|
|
685
|
+
return;
|
|
686
|
+
}
|
|
687
|
+
sourceState.fieldForwards.add(fieldKey);
|
|
688
|
+
for (const targetFuncID of sourceState.targets) {
|
|
689
|
+
this.addCallbackFieldTarget(fieldKey, targetFuncID);
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
addCallbackFieldTarget(fieldKey, targetFuncID) {
|
|
693
|
+
const state = this.getOrCreateCallbackFieldState(fieldKey);
|
|
694
|
+
if (state.targets.has(targetFuncID)) {
|
|
695
|
+
return;
|
|
696
|
+
}
|
|
697
|
+
state.targets.add(targetFuncID);
|
|
698
|
+
for (const [stmt, subscriberList] of state.subscribers) {
|
|
699
|
+
for (const sub of subscriberList) {
|
|
700
|
+
this.emitCallbackEdge(sub.callerFuncID, targetFuncID, stmt, sub.argIndex);
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
for (const bindingKey of state.bindingForwards) {
|
|
704
|
+
this.addCallbackTarget(this.decodeOwnerFuncID(bindingKey), this.decodeLogicalArgIndex(bindingKey), targetFuncID);
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
/**
|
|
708
|
+
* Link a callback field to a callee parameter binding slot. When the field's
|
|
709
|
+
* targets are populated later (e.g. field initializer processed after the
|
|
710
|
+
* call site due to RTA workList ordering), targets propagate to the binding
|
|
711
|
+
* slot via {@link addCallbackFieldTarget} → bindingForwards → addCallbackTarget.
|
|
712
|
+
*/
|
|
713
|
+
linkFieldToBinding(fieldKey, bindingKey) {
|
|
714
|
+
const fieldState = this.getOrCreateCallbackFieldState(fieldKey);
|
|
715
|
+
if (fieldState.bindingForwards.has(bindingKey)) {
|
|
716
|
+
return;
|
|
717
|
+
}
|
|
718
|
+
fieldState.bindingForwards.add(bindingKey);
|
|
719
|
+
for (const targetFuncID of fieldState.targets) {
|
|
720
|
+
this.addCallbackTarget(this.decodeOwnerFuncID(bindingKey), this.decodeLogicalArgIndex(bindingKey), targetFuncID);
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
subscribeAndCollectFieldTargets(fieldKey, invokeStmt, callerMethod, resolveResult, argIndex) {
|
|
724
|
+
const state = this.getOrCreateCallbackFieldState(fieldKey);
|
|
725
|
+
if (!state.subscribers.has(invokeStmt)) {
|
|
726
|
+
state.subscribers.set(invokeStmt, []);
|
|
727
|
+
}
|
|
728
|
+
const list = state.subscribers.get(invokeStmt);
|
|
729
|
+
if (list.some(s => s.callerFuncID === callerMethod && s.argIndex === argIndex)) {
|
|
730
|
+
return;
|
|
731
|
+
}
|
|
732
|
+
list.push({ callerFuncID: callerMethod, argIndex });
|
|
733
|
+
for (const targetFuncID of state.targets) {
|
|
734
|
+
this.appendCallbackCallSite(invokeStmt, callerMethod, targetFuncID, resolveResult, argIndex);
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
recordCallbackFieldStore(stmt, ownerMethod) {
|
|
738
|
+
const leftOp = stmt.getLeftOp();
|
|
739
|
+
if (!(leftOp instanceof Ref_1.ArkInstanceFieldRef)) {
|
|
740
|
+
return;
|
|
741
|
+
}
|
|
742
|
+
if (!this.isCallbackType(leftOp.getType())) {
|
|
743
|
+
return;
|
|
744
|
+
}
|
|
745
|
+
const fieldKey = this.getCallbackFieldKey(leftOp);
|
|
746
|
+
const rightOp = stmt.getRightOp();
|
|
747
|
+
const functionType = this.unwrapFunctionType(rightOp.getType());
|
|
748
|
+
if (functionType) {
|
|
749
|
+
const callbackMethod = this.scene.getMethod(functionType.getMethodSignature());
|
|
750
|
+
if (callbackMethod && callbackMethod.getCfg()) {
|
|
751
|
+
const targetFuncID = this.cg.getCallGraphNodeByMethod(callbackMethod.getSignature()).getID();
|
|
752
|
+
this.addCallbackFieldTarget(fieldKey, targetFuncID);
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
const sourceKey = this.resolveFuncPtrValueToBindingKey(rightOp, ownerMethod);
|
|
756
|
+
if (sourceKey !== undefined) {
|
|
757
|
+
this.linkCallbackField(sourceKey, fieldKey);
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
/**
|
|
761
|
+
* Link source parameter binding to a callee parameter slot and copy existing targets.
|
|
762
|
+
* Future addCallbackTarget calls on the source also propagate through forwards.
|
|
763
|
+
*/
|
|
764
|
+
linkCallbackBinding(source, target) {
|
|
765
|
+
if (source === target) {
|
|
766
|
+
return;
|
|
767
|
+
}
|
|
768
|
+
const sourceState = this.getOrCreateBindingState(this.decodeOwnerFuncID(source), this.decodeLogicalArgIndex(source));
|
|
769
|
+
sourceState.forwards.add(target);
|
|
770
|
+
const targetOwnerFuncID = this.decodeOwnerFuncID(target);
|
|
771
|
+
const targetLogicalArgIndex = this.decodeLogicalArgIndex(target);
|
|
772
|
+
for (const targetFuncID of sourceState.targets) {
|
|
773
|
+
this.addCallbackTarget(targetOwnerFuncID, targetLogicalArgIndex, targetFuncID);
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
addCallbackTarget(ownerFuncID, logicalArgIndex, targetFuncID) {
|
|
777
|
+
const state = this.getOrCreateBindingState(ownerFuncID, logicalArgIndex);
|
|
778
|
+
if (state.targets.has(targetFuncID)) {
|
|
779
|
+
return;
|
|
780
|
+
}
|
|
781
|
+
state.targets.add(targetFuncID);
|
|
782
|
+
for (const [stmt, callerFuncID] of state.subscribers) {
|
|
783
|
+
this.emitCallbackEdge(callerFuncID, targetFuncID, stmt, logicalArgIndex);
|
|
784
|
+
}
|
|
785
|
+
for (const forward of state.forwards) {
|
|
786
|
+
this.addCallbackTarget(this.decodeOwnerFuncID(forward), this.decodeLogicalArgIndex(forward), targetFuncID);
|
|
787
|
+
}
|
|
788
|
+
for (const fieldKey of state.fieldForwards) {
|
|
789
|
+
this.addCallbackFieldTarget(fieldKey, targetFuncID);
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
/**
|
|
793
|
+
* Create a CallbackCallSite, register it via CallSiteManager (for ID allocation
|
|
794
|
+
* and getCallSiteById lookup) and via CallGraph maps (for getCallSiteByStmt and
|
|
795
|
+
* getCallSitesByMethod lookup). The argIndex is set for callback virtual edges
|
|
796
|
+
* and left undefined for regular (direct/virtual/constructor) call sites.
|
|
797
|
+
*/
|
|
798
|
+
newRTACallSite(invokeStmt, callerFuncID, calleeFuncID, argIndex) {
|
|
799
|
+
const normalized = argIndex !== undefined && argIndex >= 0 ? argIndex : undefined;
|
|
800
|
+
const base = this.cg.getCallSiteManager().newCallSite(invokeStmt, undefined, calleeFuncID, callerFuncID);
|
|
801
|
+
const cs = new CallGraph_1.CallSite(base.id, base.callStmt, base.args, base.calleeFuncID, base.callerFuncID, normalized);
|
|
802
|
+
this.cg.recordCallSite(invokeStmt, cs);
|
|
803
|
+
return cs;
|
|
804
|
+
}
|
|
805
|
+
emitCallbackEdge(callerFuncID, calleeFuncID, callStmt, argIndex) {
|
|
806
|
+
this.cg.addDynamicCallEdge(callerFuncID, calleeFuncID, callStmt);
|
|
807
|
+
this.newRTACallSite(callStmt, callerFuncID, calleeFuncID, argIndex);
|
|
808
|
+
const calleeNode = this.cg.getNode(calleeFuncID);
|
|
809
|
+
if (!calleeNode || calleeNode.isSdkMethod()) {
|
|
810
|
+
return;
|
|
811
|
+
}
|
|
812
|
+
if (this.processedMethod.contains(calleeFuncID)) {
|
|
813
|
+
return;
|
|
814
|
+
}
|
|
815
|
+
this.workList.push(calleeFuncID);
|
|
816
|
+
}
|
|
129
817
|
getOrBuildVirtualDispatchRows(declareClass, methodName) {
|
|
130
818
|
const declareSig = declareClass.getSignature();
|
|
131
819
|
let methodIndex = this.virtualDispatchIndex.get(declareSig);
|
|
@@ -140,7 +828,7 @@ class RapidTypeAnalysis extends AbstractAnalysis_1.AbstractAnalysis {
|
|
|
140
828
|
const rows = {
|
|
141
829
|
classSignatures: [],
|
|
142
830
|
calleeNodeIDs: [],
|
|
143
|
-
|
|
831
|
+
isUnconditionallyReachable: [],
|
|
144
832
|
};
|
|
145
833
|
for (const arkClass of this.getClassHierarchy(declareClass)) {
|
|
146
834
|
const possibleCalleeMethod = arkClass.getMethodWithName(methodName);
|
|
@@ -148,16 +836,19 @@ class RapidTypeAnalysis extends AbstractAnalysis_1.AbstractAnalysis {
|
|
|
148
836
|
continue;
|
|
149
837
|
}
|
|
150
838
|
const classSignature = arkClass.getSignature();
|
|
151
|
-
if (possibleCalleeMethod.isGenerated() &&
|
|
152
|
-
classSignature !== declareSig) {
|
|
839
|
+
if (possibleCalleeMethod.isGenerated() && classSignature !== declareSig) {
|
|
153
840
|
continue;
|
|
154
841
|
}
|
|
155
842
|
if (possibleCalleeMethod.isAbstract()) {
|
|
156
843
|
continue;
|
|
157
844
|
}
|
|
845
|
+
const calleeNodeID = this.cg.getCallGraphNodeByMethod(possibleCalleeMethod.getSignature()).getID();
|
|
158
846
|
rows.classSignatures.push(classSignature);
|
|
159
|
-
rows.calleeNodeIDs.push(
|
|
160
|
-
|
|
847
|
+
rows.calleeNodeIDs.push(calleeNodeID);
|
|
848
|
+
// OH SDK declare targets, and invoking built-ins (Promise.then / Array.forEach /
|
|
849
|
+
// ...), need no local `new` to be callable. Container non-invoking APIs still require
|
|
850
|
+
// instancedClasses (e.g. newarray → Array) so push/indexOf/set are not always-on.
|
|
851
|
+
rows.isUnconditionallyReachable.push(classSignature === declareSig && this.shouldSynthesizeVirtualCallback(calleeNodeID));
|
|
161
852
|
}
|
|
162
853
|
methodIndex.set(methodName, rows);
|
|
163
854
|
return rows;
|
|
@@ -177,7 +868,7 @@ class RapidTypeAnalysis extends AbstractAnalysis_1.AbstractAnalysis {
|
|
|
177
868
|
while (curClass) {
|
|
178
869
|
const methodInClass = curClass.getMethodWithName(methodName);
|
|
179
870
|
if (methodInClass && !methodInClass.isAbstract()) {
|
|
180
|
-
return this.
|
|
871
|
+
return this.newRTACallSite(invokeStmt, callerMethod, this.cg.getCallGraphNodeByMethod(methodInClass.getSignature()).getID());
|
|
181
872
|
}
|
|
182
873
|
curClass = curClass.getSuperClass();
|
|
183
874
|
}
|
|
@@ -208,25 +899,20 @@ class RapidTypeAnalysis extends AbstractAnalysis_1.AbstractAnalysis {
|
|
|
208
899
|
if (!constructorMethod) {
|
|
209
900
|
return null;
|
|
210
901
|
}
|
|
211
|
-
return this.
|
|
902
|
+
return this.newRTACallSite(invokeStmt, callerMethod, this.cg.getCallGraphNodeByMethod(constructorMethod.getSignature()).getID());
|
|
212
903
|
}
|
|
213
904
|
preProcessMethod(funcID) {
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
newlyInstancedClasses.add(sig);
|
|
905
|
+
const newCallSites = [];
|
|
906
|
+
const instancedInMethod = this.collectInstancedClassesInMethod(funcID);
|
|
907
|
+
instancedInMethod.forEach((sig) => {
|
|
908
|
+
if (this.instancedClasses.has(sig)) {
|
|
909
|
+
return;
|
|
220
910
|
}
|
|
221
|
-
|
|
222
|
-
newlyInstancedClasses.forEach(sig => {
|
|
223
|
-
let ignoredCalls = this.ignoredCalls.get(sig);
|
|
911
|
+
const ignoredCalls = this.ignoredCalls.get(sig);
|
|
224
912
|
if (ignoredCalls) {
|
|
225
|
-
ignoredCalls.forEach(call => {
|
|
913
|
+
ignoredCalls.forEach((call) => {
|
|
226
914
|
this.cg.addDynamicCallEdge(call.caller, call.callee, call.callStmt);
|
|
227
|
-
const newCallSite = this.
|
|
228
|
-
this.cg.addStmtToCallSiteMap(call.callStmt, newCallSite);
|
|
229
|
-
this.cg.addMethodToCallSiteMap(call.callee, newCallSite);
|
|
915
|
+
const newCallSite = this.newRTACallSite(call.callStmt, call.caller, call.callee);
|
|
230
916
|
newCallSites.push(newCallSite);
|
|
231
917
|
});
|
|
232
918
|
}
|
|
@@ -247,26 +933,67 @@ class RapidTypeAnalysis extends AbstractAnalysis_1.AbstractAnalysis {
|
|
|
247
933
|
return instancedClasses;
|
|
248
934
|
}
|
|
249
935
|
for (let stmt of cfg.getStmts()) {
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
stmtExpr = stmt.getRightOp();
|
|
936
|
+
if (!(stmt instanceof Stmt_1.ArkAssignStmt)) {
|
|
937
|
+
continue;
|
|
253
938
|
}
|
|
254
|
-
|
|
939
|
+
const rightOp = stmt.getRightOp();
|
|
940
|
+
this.recordCallbackFieldStore(stmt, arkMethod);
|
|
941
|
+
if (rightOp instanceof Expr_1.ArkNewExpr) {
|
|
942
|
+
const classSig = rightOp.getType().getClassSignature();
|
|
943
|
+
if (classSig !== null && classSig !== undefined) {
|
|
944
|
+
// TODO: need to check if different stmt has single sig
|
|
945
|
+
instancedClasses.add(classSig);
|
|
946
|
+
}
|
|
255
947
|
continue;
|
|
256
948
|
}
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
949
|
+
// `handlers = []` lowers to newarray, not `new Array`. Treat it as Array instantiation
|
|
950
|
+
// so built-in Array methods (e.g. push) can pass the RTA instancedClasses gate.
|
|
951
|
+
if (rightOp instanceof Expr_1.ArkNewArrayExpr) {
|
|
952
|
+
const arraySig = this.getBuiltinArrayClassSignature();
|
|
953
|
+
if (arraySig) {
|
|
954
|
+
instancedClasses.add(arraySig);
|
|
955
|
+
}
|
|
261
956
|
}
|
|
262
957
|
}
|
|
263
958
|
return instancedClasses;
|
|
264
959
|
}
|
|
960
|
+
getBuiltinArrayClassSignature() {
|
|
961
|
+
if (this.builtinArrayClassResolved) {
|
|
962
|
+
return this.builtinArrayClassSignature;
|
|
963
|
+
}
|
|
964
|
+
this.builtinArrayClassResolved = true;
|
|
965
|
+
// Built-in Array is not in scene.getClasses(); use the SDK global export
|
|
966
|
+
// (lib.es5 Array) so the signature matches virtual-dispatch declareClass.
|
|
967
|
+
const globalArray = this.scene.getSdkGlobal(BUILTIN_ARRAY_CLASS_NAME);
|
|
968
|
+
if (globalArray instanceof ArkClass_1.ArkClass) {
|
|
969
|
+
const classSig = globalArray.getSignature();
|
|
970
|
+
if (classSig.getDeclaringFileSignature().getProjectName() === SdkUtils_1.SdkUtils.BUILT_IN_NAME) {
|
|
971
|
+
this.builtinArrayClassSignature = classSig;
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
return this.builtinArrayClassSignature;
|
|
975
|
+
}
|
|
265
976
|
addIgnoredCalls(arkClass, callerID, calleeID, invokeStmt) {
|
|
266
|
-
|
|
267
|
-
|
|
977
|
+
let classMap = this.ignoredCalls.get(arkClass);
|
|
978
|
+
if (!classMap) {
|
|
979
|
+
classMap = new Set();
|
|
980
|
+
this.ignoredCalls.set(arkClass, classMap);
|
|
981
|
+
}
|
|
982
|
+
for (const existing of classMap) {
|
|
983
|
+
if (existing.caller === callerID &&
|
|
984
|
+
existing.callee === calleeID &&
|
|
985
|
+
existing.callStmt === invokeStmt) {
|
|
986
|
+
return;
|
|
987
|
+
}
|
|
988
|
+
}
|
|
268
989
|
classMap.add({ caller: callerID, callee: calleeID, callStmt: invokeStmt });
|
|
269
|
-
this.ignoredCalls.set(arkClass, classMap);
|
|
270
990
|
}
|
|
271
991
|
}
|
|
272
992
|
exports.RapidTypeAnalysis = RapidTypeAnalysis;
|
|
993
|
+
RapidTypeAnalysis.CALLBACK_CLASS_SIG_KEY = [
|
|
994
|
+
'@ohos.base.d.ts: Callback', '@ohos.window.d.ts: Callback', 'component/common.d.ts: Callback',
|
|
995
|
+
'@ohos.base.d.ts: ErrorCallback',
|
|
996
|
+
'@ohos.base.d.ts: AsyncCallback',
|
|
997
|
+
'@ohos.base.d.ts: BusinessError',
|
|
998
|
+
'lib.es5.d.ts: Function',
|
|
999
|
+
];
|