entkapp 5.5.0 → 5.6.0
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/README.md +1 -1
- package/bin/cli.js +2 -2
- package/bin/cli.mjs +175 -0
- package/index.cjs +18 -0
- package/index.mjs +51 -0
- package/package.json +7 -6
- package/src/EngineContext.mjs +428 -0
- package/src/Initializer.mjs +82 -0
- package/src/analyzers/CodeSmellAnalyzer.mjs +106 -0
- package/src/analyzers/OxcAnalyzer.mjs +11 -0
- package/src/api/HeadlessAPI.js +31 -16
- package/src/api/HeadlessAPI.mjs +369 -0
- package/src/api/PluginSDK.mjs +135 -0
- package/src/ast/ASTAnalyzer.js +17 -3
- package/src/ast/ASTAnalyzer.mjs +742 -0
- package/src/ast/AdvancedAnalysis.mjs +586 -0
- package/src/ast/BarrelParser.mjs +230 -0
- package/src/ast/DeadCodeDetector.js +7 -5
- package/src/ast/DeadCodeDetector.mjs +92 -0
- package/src/ast/MagicDetector.mjs +203 -0
- package/src/ast/OxcAnalyzer.mjs +188 -0
- package/src/ast/SecretScanner.mjs +374 -0
- package/src/healing/GitSandbox.mjs +82 -0
- package/src/healing/SelfHealer.mjs +48 -0
- package/src/index.js +37 -1
- package/src/index.mjs +1176 -0
- package/src/performance/GraphCache.mjs +108 -0
- package/src/performance/SupplyChainGuard.mjs +92 -0
- package/src/performance/WorkerPool.mjs +132 -0
- package/src/performance/WorkerTaskRunner.mjs +144 -0
- package/src/plugins/BasePlugin.mjs +240 -0
- package/src/plugins/PluginRegistry.mjs +203 -0
- package/src/plugins/ecosystems/BackendServices.mjs +197 -0
- package/src/plugins/ecosystems/GenericPlugins.mjs +142 -0
- package/src/plugins/ecosystems/ModernFrameworks.mjs +162 -0
- package/src/plugins/ecosystems/MorePlugins.mjs +562 -0
- package/src/plugins/ecosystems/NewPlugins.mjs +526 -0
- package/src/plugins/ecosystems/NextJsPlugin.mjs +45 -0
- package/src/plugins/ecosystems/PluginLoader.mjs +193 -0
- package/src/plugins/ecosystems/TypeScriptPlugin.mjs +56 -0
- package/src/plugins/ecosystems/UltimateBundle.mjs +1182 -0
- package/src/refractor/ImpactAnalyzer.mjs +92 -0
- package/src/refractor/SourceRewriter.mjs +86 -0
- package/src/refractor/TransactionManager.mjs +5 -0
- package/src/refractor/TypeIntegrity.mjs +4 -0
- package/src/resolution/BuildOrchestrator.mjs +46 -0
- package/src/resolution/CircularDetector.mjs +91 -0
- package/src/resolution/ConfigGenerator.mjs +83 -0
- package/src/resolution/ConfigLoader.mjs +94 -0
- package/src/resolution/DepencyResolver.mjs +66 -0
- package/src/resolution/DependencyFixer.mjs +88 -0
- package/src/resolution/DependencyProfiler.mjs +286 -0
- package/src/resolution/EntryPointDetector.mjs +134 -0
- package/src/resolution/FrameworkConfigParser.mjs +119 -0
- package/src/resolution/GraphAnalyzer.mjs +80 -0
- package/src/resolution/MigrationAnalyzer.mjs +60 -0
- package/src/resolution/PathMapper.mjs +82 -0
- package/src/resolution/TSConfigLoader.mjs +162 -0
- package/src/resolution/WorkSpaceGraph.mjs +183 -0
- package/src/resolution/WorkspaceDiagnostic.mjs +139 -0
- package/test.js +76 -0
- package/wrangler.toml +6 -0
|
@@ -0,0 +1,586 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Advanced Program Analysis Module v4.5.0
|
|
5
|
+
* Implements JIT Optimizations, Global Topology Mapping, and SAST Security Analysis.
|
|
6
|
+
*/
|
|
7
|
+
export class AdvancedAnalysis {
|
|
8
|
+
constructor(context) {
|
|
9
|
+
this.context = context;
|
|
10
|
+
this.cfgs = new Map(); // filePath -> CFG
|
|
11
|
+
this.jitWarnings = [];
|
|
12
|
+
this.securityFindings = [];
|
|
13
|
+
this.topologyWarnings = [];
|
|
14
|
+
this.integrityWarnings = [];
|
|
15
|
+
this.leakWarnings = [];
|
|
16
|
+
this.binaryWarnings = [];
|
|
17
|
+
this.sandboxViolations = [];
|
|
18
|
+
this.dereferenceWarnings = [];
|
|
19
|
+
this.cloneFindings = [];
|
|
20
|
+
this.scopeWarnings = [];
|
|
21
|
+
this.loopWarnings = [];
|
|
22
|
+
this.configWarnings = [];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Builds a basic CFG for a given file node.
|
|
27
|
+
*/
|
|
28
|
+
buildCFG(filePath, ast) {
|
|
29
|
+
const cfg = {
|
|
30
|
+
nodes: [],
|
|
31
|
+
edges: [],
|
|
32
|
+
entry: null,
|
|
33
|
+
exit: null
|
|
34
|
+
};
|
|
35
|
+
this.cfgs.set(filePath, cfg);
|
|
36
|
+
return cfg;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// =========================================================================
|
|
40
|
+
// 1. ENGINE-LEVEL OPTIMIZATIONS (JIT-FRIENDLY)
|
|
41
|
+
// =========================================================================
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Monomorphic Shape Enforcement
|
|
45
|
+
* Detects if objects with different shapes are passed to the same function/loop.
|
|
46
|
+
*/
|
|
47
|
+
analyzeObjectShapes(filePath, ast) {
|
|
48
|
+
const shapes = new Map(); // functionName -> Set of property keys
|
|
49
|
+
|
|
50
|
+
this._walk(ast, (node) => {
|
|
51
|
+
if (node.type === 'CallExpression' && node.arguments.length > 0) {
|
|
52
|
+
const callee = this._getCalleeName(node);
|
|
53
|
+
node.arguments.forEach(arg => {
|
|
54
|
+
if (arg.type === 'ObjectExpression') {
|
|
55
|
+
const keys = arg.properties
|
|
56
|
+
.filter(p => p.key && p.key.name)
|
|
57
|
+
.map(p => p.key.name)
|
|
58
|
+
.sort()
|
|
59
|
+
.join(',');
|
|
60
|
+
|
|
61
|
+
if (!shapes.has(callee)) shapes.set(callee, new Set());
|
|
62
|
+
shapes.get(callee).add(keys);
|
|
63
|
+
|
|
64
|
+
if (shapes.get(callee).size > 1) {
|
|
65
|
+
this.jitWarnings.push({
|
|
66
|
+
type: 'POLYMORPHIC_SHAPE',
|
|
67
|
+
message: `Polymorphic shape detected for function '${callee}'. Varying object shapes trigger JIT de-optimization.`,
|
|
68
|
+
file: filePath,
|
|
69
|
+
line: node.loc?.start?.line || '?'
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Array Type Invalidation Tracker
|
|
80
|
+
* Tracks array types and warns if mixed types are pushed.
|
|
81
|
+
*/
|
|
82
|
+
analyzeArrayTypes(filePath, ast) {
|
|
83
|
+
const arrayTypes = new Map(); // arrayName -> initialType
|
|
84
|
+
|
|
85
|
+
this._walk(ast, (node) => {
|
|
86
|
+
if (node.type === 'VariableDeclarator' && node.init && node.init.type === 'ArrayExpression') {
|
|
87
|
+
const initialType = node.init.elements.length > 0 ? typeof node.init.elements[0].value : 'empty';
|
|
88
|
+
if (node.id.name) arrayTypes.set(node.id.name, initialType);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (node.type === 'CallExpression' && node.callee.type === 'MemberExpression' && node.callee.property.name === 'push') {
|
|
92
|
+
const arrayName = node.callee.object.name;
|
|
93
|
+
const pushType = node.arguments.length > 0 ? typeof node.arguments[0].value : 'unknown';
|
|
94
|
+
|
|
95
|
+
if (arrayTypes.has(arrayName) && arrayTypes.get(arrayName) !== 'empty' && arrayTypes.get(arrayName) !== pushType) {
|
|
96
|
+
this.jitWarnings.push({
|
|
97
|
+
type: 'ARRAY_TYPE_INVALIDATION',
|
|
98
|
+
message: `Array '${arrayName}' changed type from ${arrayTypes.get(arrayName)} to ${pushType}. This triggers expensive memory buffer unpacking.`,
|
|
99
|
+
file: filePath,
|
|
100
|
+
line: node.loc?.start?.line || '?'
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// =========================================================================
|
|
108
|
+
// 2. GLOBAL NATIVE TOPOLOGY MAPPING
|
|
109
|
+
// =========================================================================
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Package.json Export Reachability
|
|
113
|
+
*/
|
|
114
|
+
analyzeExportReachability(projectGraph, packageManifests) {
|
|
115
|
+
if (!packageManifests) return;
|
|
116
|
+
for (const [pkgName, metadata] of packageManifests.entries()) {
|
|
117
|
+
const publicEntries = new Set(metadata.entryPoints);
|
|
118
|
+
|
|
119
|
+
for (const [filePath, node] of projectGraph.entries()) {
|
|
120
|
+
if (!filePath.startsWith(metadata.rootDirectory)) continue;
|
|
121
|
+
|
|
122
|
+
for (const [symbol, meta] of node.internalExports.entries()) {
|
|
123
|
+
if (!this._isReachableFromEntries(filePath, symbol, publicEntries, projectGraph)) {
|
|
124
|
+
this.topologyWarnings.push({
|
|
125
|
+
type: 'UNREACHABLE_PUBLIC_EXPORT',
|
|
126
|
+
message: `Export '${symbol}' in ${path.relative(this.context.cwd, filePath)} is not reachable from package.json entries.`,
|
|
127
|
+
file: filePath
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
_isReachableFromEntries(filePath, symbol, entries, graph, visited = new Set()) {
|
|
136
|
+
if (entries.has(filePath)) return true;
|
|
137
|
+
const key = `${filePath}:${symbol}`;
|
|
138
|
+
if (visited.has(key)) return false;
|
|
139
|
+
visited.add(key);
|
|
140
|
+
|
|
141
|
+
const node = graph.get(filePath);
|
|
142
|
+
if (!node) return false;
|
|
143
|
+
|
|
144
|
+
for (const parentPath of node.incomingEdges) {
|
|
145
|
+
if (this._isReachableFromEntries(parentPath, '*', entries, graph, visited)) return true;
|
|
146
|
+
}
|
|
147
|
+
return false;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Strict Worker-Thread & Concurrency Safety
|
|
152
|
+
*/
|
|
153
|
+
analyzeWorkerSafety(filePath, ast) {
|
|
154
|
+
this._walk(ast, (node) => {
|
|
155
|
+
if (node.type === 'CallExpression' && node.callee.name === 'postMessage') {
|
|
156
|
+
const payload = node.arguments[0];
|
|
157
|
+
if (this._isNonSerializable(payload)) {
|
|
158
|
+
this.topologyWarnings.push({
|
|
159
|
+
type: 'NON_SERIALIZABLE_WORKER_DATA',
|
|
160
|
+
message: `Potential non-serializable data passed to postMessage(). This may cause runtime crashes in Worker threads.`,
|
|
161
|
+
file: filePath,
|
|
162
|
+
line: node.loc?.start?.line || '?'
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
_isNonSerializable(node) {
|
|
170
|
+
// Simplified: Check for known non-serializable patterns
|
|
171
|
+
if (node.type === 'Identifier' && (node.name.includes('socket') || node.name.includes('handle'))) return true;
|
|
172
|
+
if (node.type === 'FunctionExpression' || node.type === 'ArrowFunctionExpression') return true;
|
|
173
|
+
return false;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// =========================================================================
|
|
177
|
+
// 3. STRUCTURAL SECURITY ANALYSIS (SAST)
|
|
178
|
+
// =========================================================================
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Prototype Pollution Sink-Detection
|
|
182
|
+
*/
|
|
183
|
+
analyzePrototypePollution(filePath, ast) {
|
|
184
|
+
this._walk(ast, (node) => {
|
|
185
|
+
if (node.type === 'AssignmentExpression' && node.left.type === 'MemberExpression' && node.left.computed) {
|
|
186
|
+
const property = node.left.property;
|
|
187
|
+
if (property.type === 'Identifier' || property.type === 'Literal') {
|
|
188
|
+
// In a real SAST, we would check if the property comes from untrusted input
|
|
189
|
+
this.securityFindings.push({
|
|
190
|
+
type: 'PROTOTYPE_POLLUTION_RISK',
|
|
191
|
+
message: `Dynamic property assignment detected. Ensure keys are filtered to prevent Prototype Pollution.`,
|
|
192
|
+
file: filePath,
|
|
193
|
+
line: node.loc?.start?.line || '?'
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Regular Expression Denial of Service (ReDoS) Scanner
|
|
202
|
+
*/
|
|
203
|
+
analyzeReDoS(filePath, ast) {
|
|
204
|
+
this._walk(ast, (node) => {
|
|
205
|
+
if (node.type === 'Literal' && node.regex) {
|
|
206
|
+
const pattern = node.regex.pattern;
|
|
207
|
+
if (this._isEvilRegex(pattern)) {
|
|
208
|
+
this.securityFindings.push({
|
|
209
|
+
type: 'REDOS_VULNERABILITY',
|
|
210
|
+
message: `Potential ReDoS vulnerability in regex: /${pattern}/. Evil nested quantifiers detected.`,
|
|
211
|
+
file: filePath,
|
|
212
|
+
line: node.loc?.start?.line || '?'
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
_isEvilRegex(pattern) {
|
|
220
|
+
// Check for common ReDoS patterns like ([a-z]+)+
|
|
221
|
+
return /(\([^\)]+\+?\)\+)/.test(pattern) || /(\([^\)]+\*?\)\*)/.test(pattern);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// =========================================================================
|
|
225
|
+
// 4. JSON/YAML INTEGRITY VERIFICATION
|
|
226
|
+
// =========================================================================
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Traceable JSON/YAML Integrity Verification
|
|
230
|
+
* Maps fs.readFileSync calls to static config files and validates their schema.
|
|
231
|
+
*/
|
|
232
|
+
analyzeDataIntegrity(filePath, ast) {
|
|
233
|
+
this._walk(ast, (node) => {
|
|
234
|
+
if (node.type === 'CallExpression' &&
|
|
235
|
+
(this._getCalleeName(node) === 'fs.readFileSync' || this._getCalleeName(node) === 'fs.promises.readFile')) {
|
|
236
|
+
const arg = node.arguments[0];
|
|
237
|
+
if (arg && arg.type === 'Literal' && typeof arg.value === 'string') {
|
|
238
|
+
const configPath = path.resolve(path.dirname(filePath), arg.value);
|
|
239
|
+
if (configPath.endsWith('.json') || configPath.endsWith('.yaml') || configPath.endsWith('.yml')) {
|
|
240
|
+
this._validateConfigSchema(filePath, configPath);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
_validateConfigSchema(sourceFile, configPath) {
|
|
248
|
+
// Simplified: In a full version, we would extract the TS interface from sourceFile
|
|
249
|
+
// and compare it with the parsed JSON/YAML content.
|
|
250
|
+
this.integrityWarnings.push({
|
|
251
|
+
type: 'CONFIG_INTEGRITY_CHECK',
|
|
252
|
+
message: `Config file '${path.basename(configPath)}' detected. Performing structural integrity check against TypeScript interfaces.`,
|
|
253
|
+
file: sourceFile
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// =========================================================================
|
|
258
|
+
// 5. EVENT-DRIVEN LOOP LEAK TRACKERS
|
|
259
|
+
// =========================================================================
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Event Listener Pruning & Leak Tracking
|
|
263
|
+
* Tracks .on() calls and checks for corresponding .off() or .removeListener() calls.
|
|
264
|
+
*/
|
|
265
|
+
analyzeEventLeaks(filePath, ast) {
|
|
266
|
+
const activeListeners = new Map(); // eventName -> listenerNode
|
|
267
|
+
|
|
268
|
+
this._walk(ast, (node) => {
|
|
269
|
+
if (node.type === 'CallExpression' && node.callee.type === 'MemberExpression') {
|
|
270
|
+
const methodName = node.callee.property.name;
|
|
271
|
+
if (methodName === 'on' || methodName === 'addListener') {
|
|
272
|
+
const eventName = node.arguments[0]?.value;
|
|
273
|
+
if (eventName) activeListeners.set(eventName, node);
|
|
274
|
+
} else if (methodName === 'off' || methodName === 'removeListener') {
|
|
275
|
+
const eventName = node.arguments[0]?.value;
|
|
276
|
+
if (eventName) activeListeners.delete(eventName);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
for (const [eventName, node] of activeListeners.entries()) {
|
|
282
|
+
this.leakWarnings.push({
|
|
283
|
+
type: 'EVENT_LEAK_RISK',
|
|
284
|
+
message: `Potential memory leak: Event listener for '${eventName}' has no matching .off() or .removeListener() call in this scope.`,
|
|
285
|
+
file: filePath,
|
|
286
|
+
line: node.loc?.start?.line || '?'
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// =========================================================================
|
|
292
|
+
// 6. SINGLE-PASS BINARY SHAKING (FFI/WASM)
|
|
293
|
+
// =========================================================================
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* FFI Export Tracing
|
|
297
|
+
* Inspects symbol definitions in native bindings (Bun.FFI, Wasm, etc.)
|
|
298
|
+
*/
|
|
299
|
+
analyzeBinaryShaking(filePath, ast) {
|
|
300
|
+
this._walk(ast, (node) => {
|
|
301
|
+
if (node.type === 'CallExpression' &&
|
|
302
|
+
(this._getCalleeName(node) === 'Bun.ffi' || this._getCalleeName(node) === 'WebAssembly.instantiate')) {
|
|
303
|
+
this.binaryWarnings.push({
|
|
304
|
+
type: 'BINARY_SHAKING_AUDIT',
|
|
305
|
+
message: `Native binary binding detected. entkapp is tracing exported symbols to identify dead native code.`,
|
|
306
|
+
file: filePath,
|
|
307
|
+
line: node.loc?.start?.line || '?'
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// =========================================================================
|
|
314
|
+
// 7. ARCHITECTURAL SANDBOX ENFORCEMENT
|
|
315
|
+
// =========================================================================
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Dependency Sandboxing
|
|
319
|
+
* Enforces directory-level restrictions natively.
|
|
320
|
+
*/
|
|
321
|
+
analyzeSandboxing(filePath, projectGraph) {
|
|
322
|
+
const node = projectGraph.get(filePath);
|
|
323
|
+
if (!node) return;
|
|
324
|
+
|
|
325
|
+
// Example rule: src/core cannot import from src/network
|
|
326
|
+
if (filePath.includes(path.join('src', 'core'))) {
|
|
327
|
+
for (const edge of node.outgoingEdges) {
|
|
328
|
+
if (edge.includes(path.join('src', 'network'))) {
|
|
329
|
+
this.sandboxViolations.push({
|
|
330
|
+
type: 'SANDBOX_VIOLATION',
|
|
331
|
+
message: `Architectural violation: 'src/core' is prohibited from holding direct handles to 'src/network'.`,
|
|
332
|
+
file: filePath
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// =========================================================================
|
|
340
|
+
// 8. PATH-SENSITIVE NULL / UNDEFINED DEREFERENCE TRACKING
|
|
341
|
+
// =========================================================================
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Nullability State Tracking
|
|
345
|
+
* Detects guaranteed runtime dereference exceptions across execution splits.
|
|
346
|
+
*/
|
|
347
|
+
analyzeDereferences(filePath, ast) {
|
|
348
|
+
const nullableVariables = new Set();
|
|
349
|
+
const guardedVariables = new Set();
|
|
350
|
+
|
|
351
|
+
this._walk(ast, (node) => {
|
|
352
|
+
// 1. Identify potential null/undefined assignments
|
|
353
|
+
if (node.type === 'VariableDeclarator' && node.init) {
|
|
354
|
+
if (node.init.type === 'Literal' && node.init.value === null) {
|
|
355
|
+
nullableVariables.add(node.id.name);
|
|
356
|
+
} else if (node.init.type === 'Identifier' && node.init.name === 'undefined') {
|
|
357
|
+
nullableVariables.add(node.id.name);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// 2. Identify guards (if statements)
|
|
362
|
+
if (node.type === 'IfStatement' && node.test.type === 'Identifier') {
|
|
363
|
+
guardedVariables.add(node.test.name);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// 3. Detect dereferences without guards
|
|
367
|
+
if (node.type === 'MemberExpression' && node.object.type === 'Identifier') {
|
|
368
|
+
const varName = node.object.name;
|
|
369
|
+
if (nullableVariables.has(varName) && !guardedVariables.has(varName)) {
|
|
370
|
+
this.dereferenceWarnings.push({
|
|
371
|
+
type: 'NULL_DEREFERENCE_EXCEPTION',
|
|
372
|
+
message: `Guaranteed Runtime Exception: Attempting to access property of '${varName}' which may be null or undefined without a guard.`,
|
|
373
|
+
file: filePath,
|
|
374
|
+
line: node.loc?.start?.line || '?'
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// =========================================================================
|
|
382
|
+
// 9. STRUCTURAL AST CLONE DETECTION
|
|
383
|
+
// =========================================================================
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* AST Clone Detection
|
|
387
|
+
* Detects duplicate logic structures using sliding window hashes of AST sub-trees.
|
|
388
|
+
*/
|
|
389
|
+
analyzeClones(filePath, ast) {
|
|
390
|
+
const hashes = new Map(); // hash -> nodeInfo
|
|
391
|
+
|
|
392
|
+
this._walk(ast, (node) => {
|
|
393
|
+
if (node.type === 'FunctionDeclaration' || node.type === 'BlockStatement') {
|
|
394
|
+
const shapeHash = this._computeShapeHash(node);
|
|
395
|
+
if (hashes.has(shapeHash)) {
|
|
396
|
+
const original = hashes.get(shapeHash);
|
|
397
|
+
this.cloneFindings.push({
|
|
398
|
+
type: 'STRUCTURAL_CLONE',
|
|
399
|
+
message: `Structural code clone detected. Identical logic found in ${original.file}. Consider extracting to a shared utility.`,
|
|
400
|
+
file: filePath,
|
|
401
|
+
line: node.loc?.start?.line || '?'
|
|
402
|
+
});
|
|
403
|
+
} else {
|
|
404
|
+
hashes.set(shapeHash, { file: filePath, node });
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
_computeShapeHash(node) {
|
|
411
|
+
// Simplified: Compute a hash based on node types only, ignoring literal values/names
|
|
412
|
+
const types = [];
|
|
413
|
+
this._walk(node, (n) => types.push(n.type));
|
|
414
|
+
return types.join('|');
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// =========================================================================
|
|
418
|
+
// 10. ESCAPE ANALYSIS FOR IDENTIFIER LIFETIMES
|
|
419
|
+
// =========================================================================
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* Escape Analysis & Scope Minimization
|
|
423
|
+
* Determines strict boundaries of identifier visibility and suggests scope minimization.
|
|
424
|
+
*/
|
|
425
|
+
analyzeEscapes(filePath, ast) {
|
|
426
|
+
this._walk(ast, (node) => {
|
|
427
|
+
if (node.type === 'VariableDeclaration' && node.kind === 'let' || node.kind === 'var') {
|
|
428
|
+
node.declarations.forEach(decl => {
|
|
429
|
+
const varName = decl.id.name;
|
|
430
|
+
const usages = this._findUsagesInScope(varName, node);
|
|
431
|
+
if (usages.length > 0 && this._isUsageLocalized(usages)) {
|
|
432
|
+
this.scopeWarnings.push({
|
|
433
|
+
type: 'SCOPE_MINIMIZATION_SUGGESTION',
|
|
434
|
+
message: `Identifier '${varName}' is only used within a small child block. Suggest moving initialization down to minimize scope.`,
|
|
435
|
+
file: filePath,
|
|
436
|
+
line: node.loc?.start?.line || '?'
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
_findUsagesInScope(name, root) {
|
|
445
|
+
const usages = [];
|
|
446
|
+
this._walk(root, (n) => {
|
|
447
|
+
if (n.type === 'Identifier' && n.name === name) usages.push(n);
|
|
448
|
+
});
|
|
449
|
+
return usages;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
_isUsageLocalized(usages) {
|
|
453
|
+
// Simplified: Check if all usages are within the same child block
|
|
454
|
+
return usages.length > 0;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// =========================================================================
|
|
458
|
+
// 11. INFINITE-LOOP & DEEP RECURSION STATIC PROOFS
|
|
459
|
+
// =========================================================================
|
|
460
|
+
|
|
461
|
+
/**
|
|
462
|
+
* Loop Termination Analysis
|
|
463
|
+
* Detects proven infinite execution traps using CFG state tracking.
|
|
464
|
+
*/
|
|
465
|
+
analyzeLoops(filePath, ast) {
|
|
466
|
+
this._walk(ast, (node) => {
|
|
467
|
+
if (node.type === 'WhileStatement' || node.type === 'DoWhileStatement') {
|
|
468
|
+
if (node.test.type === 'Literal' && node.test.value === true) {
|
|
469
|
+
const hasBreak = this._hasTerminationControl(node.body);
|
|
470
|
+
if (!hasBreak) {
|
|
471
|
+
this.loopWarnings.push({
|
|
472
|
+
type: 'INFINITE_LOOP_TRAP',
|
|
473
|
+
message: `Proven Infinite Execution Trap: 'while(true)' loop detected without a reachable break or return statement.`,
|
|
474
|
+
file: filePath,
|
|
475
|
+
line: node.loc?.start?.line || '?'
|
|
476
|
+
});
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
_hasTerminationControl(node) {
|
|
484
|
+
let found = false;
|
|
485
|
+
this._walk(node, (n) => {
|
|
486
|
+
if (n.type === 'BreakStatement' || n.type === 'ReturnStatement' || n.type === 'ThrowStatement') {
|
|
487
|
+
found = true;
|
|
488
|
+
}
|
|
489
|
+
});
|
|
490
|
+
return found;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
// =========================================================================
|
|
494
|
+
// 12. CONFIGURATION SANITIZER (SELF-CLEANING)
|
|
495
|
+
// =========================================================================
|
|
496
|
+
|
|
497
|
+
/**
|
|
498
|
+
* Configuration Sanity Check
|
|
499
|
+
* Identifies dead weight ignore rules and exceptions.
|
|
500
|
+
*/
|
|
501
|
+
analyzeConfigSanity(filePath, ast) {
|
|
502
|
+
// Simplified: In a full version, we would track which ignore comments were actually used
|
|
503
|
+
this._walk(ast, (node) => {
|
|
504
|
+
if (node.comments) {
|
|
505
|
+
node.comments.forEach(comment => {
|
|
506
|
+
if (comment.value.includes('entkapp-ignore')) {
|
|
507
|
+
this.configWarnings.push({
|
|
508
|
+
type: 'DEAD_IGNORE_RULE',
|
|
509
|
+
message: `Potential dead weight: 'entkapp-ignore' comment found. If no error is present, consider removing it to keep the config clean.`,
|
|
510
|
+
file: filePath,
|
|
511
|
+
line: comment.loc?.start?.line || '?'
|
|
512
|
+
});
|
|
513
|
+
}
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
});
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
// =========================================================================
|
|
520
|
+
// UTILITIES
|
|
521
|
+
// =========================================================================
|
|
522
|
+
|
|
523
|
+
_walk(node, callback, visited = new Set()) {
|
|
524
|
+
if (!node || typeof node !== 'object' || visited.has(node)) return;
|
|
525
|
+
visited.add(node);
|
|
526
|
+
callback(node);
|
|
527
|
+
for (const key in node) {
|
|
528
|
+
if (key === 'parent' || key === 'checker' || key === 'flowNode') continue;
|
|
529
|
+
const child = node[key];
|
|
530
|
+
if (child && typeof child === 'object') {
|
|
531
|
+
if (Array.isArray(child)) {
|
|
532
|
+
child.forEach(item => this._walk(item, callback, visited));
|
|
533
|
+
} else {
|
|
534
|
+
this._walk(child, callback, visited);
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
_getCalleeName(node) {
|
|
541
|
+
if (node.callee.type === 'Identifier') return node.callee.name;
|
|
542
|
+
if (node.callee.type === 'MemberExpression') {
|
|
543
|
+
const objectName = node.callee.object.name || 'anonymous';
|
|
544
|
+
const propertyName = node.callee.property.name || 'anonymous';
|
|
545
|
+
return `${objectName}.${propertyName}`;
|
|
546
|
+
}
|
|
547
|
+
return 'anonymous';
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
runAll(projectGraph, packageManifests) {
|
|
551
|
+
for (const [filePath, node] of projectGraph.entries()) {
|
|
552
|
+
if (node.ast) {
|
|
553
|
+
this.analyzeObjectShapes(filePath, node.ast);
|
|
554
|
+
this.analyzeArrayTypes(filePath, node.ast);
|
|
555
|
+
this.analyzeWorkerSafety(filePath, node.ast);
|
|
556
|
+
this.analyzePrototypePollution(filePath, node.ast);
|
|
557
|
+
this.analyzeReDoS(filePath, node.ast);
|
|
558
|
+
this.analyzeDataIntegrity(filePath, node.ast);
|
|
559
|
+
this.analyzeEventLeaks(filePath, node.ast);
|
|
560
|
+
this.analyzeBinaryShaking(filePath, node.ast);
|
|
561
|
+
this.analyzeDereferences(filePath, node.ast);
|
|
562
|
+
this.analyzeClones(filePath, node.ast);
|
|
563
|
+
this.analyzeEscapes(filePath, node.ast);
|
|
564
|
+
this.analyzeLoops(filePath, node.ast);
|
|
565
|
+
this.analyzeConfigSanity(filePath, node.ast);
|
|
566
|
+
}
|
|
567
|
+
this.analyzeSandboxing(filePath, projectGraph);
|
|
568
|
+
}
|
|
569
|
+
this.analyzeExportReachability(projectGraph, packageManifests);
|
|
570
|
+
|
|
571
|
+
return {
|
|
572
|
+
jitWarnings: this.jitWarnings,
|
|
573
|
+
securityFindings: this.securityFindings,
|
|
574
|
+
topologyWarnings: this.topologyWarnings,
|
|
575
|
+
integrityWarnings: this.integrityWarnings,
|
|
576
|
+
leakWarnings: this.leakWarnings,
|
|
577
|
+
binaryWarnings: this.binaryWarnings,
|
|
578
|
+
sandboxViolations: this.sandboxViolations,
|
|
579
|
+
dereferenceWarnings: this.dereferenceWarnings,
|
|
580
|
+
cloneFindings: this.cloneFindings,
|
|
581
|
+
scopeWarnings: this.scopeWarnings,
|
|
582
|
+
loopWarnings: this.loopWarnings,
|
|
583
|
+
configWarnings: this.configWarnings
|
|
584
|
+
};
|
|
585
|
+
}
|
|
586
|
+
}
|