@clear-capabilities/agentic-security-scanner 0.77.0 → 0.79.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/bin/.agentic-security/findings.json +1907 -0
- package/bin/.agentic-security/last-scan.json +1907 -0
- package/bin/.agentic-security/last-scan.json.sig +1 -0
- package/bin/.agentic-security/scan-history.json +166 -0
- package/bin/.agentic-security/streak.json +20 -0
- package/bin/agentic-security.js +55 -9
- package/dist/178.index.js +1 -1
- package/dist/384.index.js +1 -1
- package/dist/476.index.js +5 -5
- package/dist/637.index.js +1 -1
- package/dist/700.index.js +138 -0
- package/dist/718.index.js +159 -0
- package/dist/824.index.js +126 -0
- package/dist/838.index.js +1 -1
- package/dist/985.index.js +5 -0
- package/dist/agentic-security.mjs +32 -32
- package/dist/agentic-security.mjs.sha256 +1 -1
- package/package.json +4 -4
- package/src/dataflow/async-sequencing.js +16 -7
- package/src/dataflow/builtin-summaries.js +131 -0
- package/src/dataflow/catalog.js +107 -0
- package/src/dataflow/cross-repo.js +75 -1
- package/src/dataflow/engine.js +181 -8
- package/src/dataflow/implicit-flow.js +24 -6
- package/src/dataflow/stub-aware-filter.js +69 -11
- package/src/dataflow/summaries.js +28 -3
- package/src/engine-parallel.js +70 -0
- package/src/engine.js +270 -19
- package/src/integrations/index.js +2 -1
- package/src/ir/callgraph.js +27 -7
- package/src/ir/index.js +22 -1
- package/src/ir/parser-go.js +403 -0
- package/src/ir/parser-js.js +2 -0
- package/src/ir/parser-php.js +330 -0
- package/src/ir/parser-py.helper.py +137 -11
- package/src/ir/parser-rb.js +309 -0
- package/src/llm-validator/index.js +7 -5
- package/src/mcp/audit.js +5 -0
- package/src/posture/calibration-drift.js +2 -1
- package/src/posture/calibration.js +16 -1
- package/src/posture/fix-history.js +8 -2
- package/src/posture/profile.js +4 -5
- package/src/posture/rule-overrides.js +2 -3
- package/src/posture/rule-pack-signing.js +2 -3
- package/src/posture/rule-synthesis.js +5 -6
- package/src/posture/security-trend.js +4 -7
- package/src/posture/state-dir.js +124 -0
- package/src/posture/streak.js +3 -0
- package/src/posture/suppressions.js +5 -8
- package/src/posture/triage.js +16 -5
- package/src/posture/validator-metrics.js +3 -6
- package/src/report/index.js +23 -2
- package/src/sast/cache-poisoning.js +77 -0
- package/src/sast/comparison-safety.js +73 -0
- package/src/sast/db-taint.js +78 -0
- package/src/sast/graphql.js +127 -0
- package/src/sast/llm-stored-prompt.js +57 -0
- package/src/sast/mutation-xss.js +43 -0
- package/src/sast/nosql-injection.js +5 -0
- package/src/sast/null-byte-injection.js +76 -0
- package/src/sast/redos-nfa.js +338 -0
- package/src/sast/rust.js +26 -0
- package/src/sast/sensitive-data-logging.js +73 -0
- package/src/sast/weak-password-hash.js +77 -0
- package/src/sast/weak-randomness.js +100 -0
- package/src/sca/binary-metadata.js +124 -0
- package/src/sca/llm-function-extract.js +107 -0
- package/src/sca/py-package-functions.js +118 -0
- package/src/sca/vendor-detect.js +144 -0
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
// Go IR frontend.
|
|
2
|
+
//
|
|
3
|
+
// Regex-based, follows the parser-cs.js / parser-kt.js pattern. Focused on
|
|
4
|
+
// net/http, gin, echo, chi, gorm, database/sql surface area.
|
|
5
|
+
//
|
|
6
|
+
// What we model:
|
|
7
|
+
// - func declarations: top-level, method receivers, closures (top-level only)
|
|
8
|
+
// - := short declarations and = assignments
|
|
9
|
+
// - method / function calls
|
|
10
|
+
// - return
|
|
11
|
+
// - if / for / switch as linear blocks (body treated as straight-line)
|
|
12
|
+
// - defer / go as call nodes
|
|
13
|
+
// - fmt.Sprintf as template literal
|
|
14
|
+
// - multi-return first-target tracking: a, err := f()
|
|
15
|
+
//
|
|
16
|
+
// What we do NOT model:
|
|
17
|
+
// - goroutine channel taint (send/receive)
|
|
18
|
+
// - interface dispatch (dynamic method resolution)
|
|
19
|
+
// - generics (type params)
|
|
20
|
+
// - select statements (treated as noop)
|
|
21
|
+
// - struct field assignments (x.Field = val) beyond simple dotted targets
|
|
22
|
+
|
|
23
|
+
import * as crypto from 'node:crypto';
|
|
24
|
+
|
|
25
|
+
const FUNC_RE = new RegExp(
|
|
26
|
+
'(?:^|[\\n;{}])\\s*func\\s+' +
|
|
27
|
+
'(?:\\(\\s*(\\w+)\\s+\\*?([A-Za-z_]\\w*)\\s*\\)\\s+)?' + // optional receiver (g1=name, g2=type)
|
|
28
|
+
'([A-Za-z_]\\w*)' + // func name (g3)
|
|
29
|
+
'\\s*\\(([^)]*)\\)' + // params (g4)
|
|
30
|
+
'(?:\\s*(?:\\([^)]*\\)|[A-Za-z_*\\[\\]\\w.,\\s]*))?' + // optional return type(s)
|
|
31
|
+
'\\s*\\{', 'g');
|
|
32
|
+
|
|
33
|
+
function _splitStatements(body) {
|
|
34
|
+
const out = [];
|
|
35
|
+
let buf = '';
|
|
36
|
+
let depth = 0;
|
|
37
|
+
let inStr = null;
|
|
38
|
+
let inRaw = false;
|
|
39
|
+
let escape = false;
|
|
40
|
+
for (let i = 0; i < body.length; i++) {
|
|
41
|
+
const c = body[i];
|
|
42
|
+
if (escape) { buf += c; escape = false; continue; }
|
|
43
|
+
if (inStr) {
|
|
44
|
+
buf += c;
|
|
45
|
+
if (c === '\\' && inStr === '"') { escape = true; continue; }
|
|
46
|
+
if (c === inStr) inStr = null;
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
if (inRaw) {
|
|
50
|
+
buf += c;
|
|
51
|
+
if (c === '`') inRaw = false;
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
if (c === '"' || c === '\'') { inStr = c; buf += c; continue; }
|
|
55
|
+
if (c === '`') { inRaw = true; buf += c; continue; }
|
|
56
|
+
if (c === '/' && body[i + 1] === '/') {
|
|
57
|
+
while (i < body.length && body[i] !== '\n') i++;
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
if (c === '{' || c === '(' || c === '[') depth++;
|
|
61
|
+
if (c === '}' || c === ')' || c === ']') depth--;
|
|
62
|
+
if ((c === '\n' || c === ';') && depth === 0) {
|
|
63
|
+
const t = buf.trim();
|
|
64
|
+
if (t) out.push(t);
|
|
65
|
+
buf = '';
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
buf += c;
|
|
69
|
+
}
|
|
70
|
+
if (buf.trim()) out.push(buf.trim());
|
|
71
|
+
return out;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function _lowerExpr(text) {
|
|
75
|
+
const s = String(text || '').trim();
|
|
76
|
+
if (!s) return { kind: 'unknown' };
|
|
77
|
+
if (/^fmt\.Sprintf\s*\(/.test(s)) {
|
|
78
|
+
const inner = s.slice(s.indexOf('(') + 1, s.lastIndexOf(')'));
|
|
79
|
+
const parts = _splitTopLevelCommas(inner).map(_lowerExpr);
|
|
80
|
+
return { kind: 'tpl', parts };
|
|
81
|
+
}
|
|
82
|
+
if (/^"/.test(s) || /^`/.test(s)) return { kind: 'literal', value: s };
|
|
83
|
+
if (/^\d/.test(s)) return { kind: 'literal', value: s };
|
|
84
|
+
if (/^(true|false|nil)\b/.test(s)) return { kind: 'literal', value: s };
|
|
85
|
+
// Call: foo.Bar(args) or Bar(args)
|
|
86
|
+
const callMatch = s.match(/^([\w.]+)\s*\((.*)\)\s*$/s);
|
|
87
|
+
if (callMatch) {
|
|
88
|
+
const callee = callMatch[1];
|
|
89
|
+
const args = _splitTopLevelCommas(callMatch[2]).map(_lowerExpr);
|
|
90
|
+
return { kind: 'call', callee, args };
|
|
91
|
+
}
|
|
92
|
+
// String concat with +
|
|
93
|
+
if (s.includes('+') && /["'`]/.test(s)) {
|
|
94
|
+
const parts = _splitTopLevelPlus(s).map(_lowerExpr);
|
|
95
|
+
return { kind: 'tpl', parts };
|
|
96
|
+
}
|
|
97
|
+
// Member: a.b.c
|
|
98
|
+
if (/^[A-Za-z_][\w.]*$/.test(s)) {
|
|
99
|
+
const parts = s.split('.');
|
|
100
|
+
if (parts.length === 1) return { kind: 'ident', name: parts[0] };
|
|
101
|
+
let cur = { kind: 'ident', name: parts[0] };
|
|
102
|
+
for (let i = 1; i < parts.length; i++) cur = { kind: 'member', object: cur, prop: parts[i] };
|
|
103
|
+
return cur;
|
|
104
|
+
}
|
|
105
|
+
// Indexing: a[b] or a["key"]
|
|
106
|
+
if (/^[A-Za-z_][\w.]*\[/.test(s)) {
|
|
107
|
+
const lb = s.indexOf('[');
|
|
108
|
+
const base = s.slice(0, lb);
|
|
109
|
+
const parts = base.split('.');
|
|
110
|
+
let cur = { kind: 'ident', name: parts[0] };
|
|
111
|
+
for (let i = 1; i < parts.length; i++) cur = { kind: 'member', object: cur, prop: parts[i] };
|
|
112
|
+
return { kind: 'member', object: cur, prop: '[]' };
|
|
113
|
+
}
|
|
114
|
+
// Struct literal: Type{...}
|
|
115
|
+
if (/^[A-Za-z_]\w*\s*\{/.test(s)) {
|
|
116
|
+
return { kind: 'object', props: [] };
|
|
117
|
+
}
|
|
118
|
+
// Address-of / dereference
|
|
119
|
+
if (s.startsWith('&') || s.startsWith('*')) return _lowerExpr(s.slice(1));
|
|
120
|
+
return { kind: 'unknown' };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function _splitTopLevelCommas(s) {
|
|
124
|
+
const out = [];
|
|
125
|
+
let buf = '';
|
|
126
|
+
let depth = 0;
|
|
127
|
+
let inStr = null;
|
|
128
|
+
let inRaw = false;
|
|
129
|
+
for (let i = 0; i < s.length; i++) {
|
|
130
|
+
const c = s[i];
|
|
131
|
+
if (inStr) {
|
|
132
|
+
buf += c;
|
|
133
|
+
if (c === '\\' && inStr === '"') { i++; buf += s[i] || ''; continue; }
|
|
134
|
+
if (c === inStr) inStr = null;
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
if (inRaw) { buf += c; if (c === '`') inRaw = false; continue; }
|
|
138
|
+
if (c === '"') { inStr = c; buf += c; continue; }
|
|
139
|
+
if (c === '`') { inRaw = true; buf += c; continue; }
|
|
140
|
+
if (c === '(' || c === '{' || c === '[') depth++;
|
|
141
|
+
if (c === ')' || c === '}' || c === ']') depth--;
|
|
142
|
+
if (c === ',' && depth === 0) { out.push(buf.trim()); buf = ''; continue; }
|
|
143
|
+
buf += c;
|
|
144
|
+
}
|
|
145
|
+
if (buf.trim()) out.push(buf.trim());
|
|
146
|
+
return out;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function _splitTopLevelPlus(s) {
|
|
150
|
+
const out = [];
|
|
151
|
+
let buf = '';
|
|
152
|
+
let depth = 0;
|
|
153
|
+
let inStr = null;
|
|
154
|
+
for (let i = 0; i < s.length; i++) {
|
|
155
|
+
const c = s[i];
|
|
156
|
+
if (inStr) {
|
|
157
|
+
buf += c;
|
|
158
|
+
if (c === '\\' && inStr === '"') { i++; buf += s[i] || ''; continue; }
|
|
159
|
+
if (c === inStr) inStr = null;
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
if (c === '"' || c === '`') { inStr = c; buf += c; continue; }
|
|
163
|
+
if (c === '(' || c === '{' || c === '[') depth++;
|
|
164
|
+
if (c === ')' || c === '}' || c === ']') depth--;
|
|
165
|
+
if (c === '+' && depth === 0) { out.push(buf.trim()); buf = ''; continue; }
|
|
166
|
+
buf += c;
|
|
167
|
+
}
|
|
168
|
+
if (buf.trim()) out.push(buf.trim());
|
|
169
|
+
return out;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function _lowerStmt(stmt, line) {
|
|
173
|
+
const s = stmt.trim();
|
|
174
|
+
if (!s || s.startsWith('//')) return null;
|
|
175
|
+
// return
|
|
176
|
+
if (/^return\b/.test(s)) {
|
|
177
|
+
const rest = s.replace(/^return\s*/, '').trim();
|
|
178
|
+
// Multi-return: return a, b → take the first
|
|
179
|
+
const parts = _splitTopLevelCommas(rest);
|
|
180
|
+
const value = parts.length ? _lowerExpr(parts[0]) : null;
|
|
181
|
+
return { kind: 'return', line, value };
|
|
182
|
+
}
|
|
183
|
+
// defer / go: treat as call
|
|
184
|
+
if (/^(?:defer|go)\s+/.test(s)) {
|
|
185
|
+
const rest = s.replace(/^(?:defer|go)\s+/, '').trim();
|
|
186
|
+
const cm = rest.match(/^([\w.]+)\s*\((.*)\)\s*$/s);
|
|
187
|
+
if (cm) {
|
|
188
|
+
return { kind: 'call', line, callee: cm[1], args: _splitTopLevelCommas(cm[2]).map(_lowerExpr) };
|
|
189
|
+
}
|
|
190
|
+
return { kind: 'noop', line };
|
|
191
|
+
}
|
|
192
|
+
// Short variable declaration: a, b := expr or a := expr
|
|
193
|
+
const shortDecl = s.match(/^(\w+(?:\s*,\s*\w+)*)\s*:=\s*(.+)$/s);
|
|
194
|
+
if (shortDecl) {
|
|
195
|
+
const targets = shortDecl[1].split(',').map(t => t.trim());
|
|
196
|
+
const rhs = shortDecl[2].trim();
|
|
197
|
+
if (targets.length === 1) {
|
|
198
|
+
return { kind: 'assign', line, target: targets[0], source: _lowerExpr(rhs) };
|
|
199
|
+
}
|
|
200
|
+
// Multi-return: a, err := f() → assign first target
|
|
201
|
+
return { kind: 'assign', line, target: targets[0], source: _lowerExpr(rhs) };
|
|
202
|
+
}
|
|
203
|
+
// Regular assignment: a = expr or a.b = expr
|
|
204
|
+
const assign = s.match(/^([A-Za-z_][\w.]*)\s*=\s*(.+)$/s);
|
|
205
|
+
if (assign) {
|
|
206
|
+
return { kind: 'assign', line, target: assign[1], source: _lowerExpr(assign[2]) };
|
|
207
|
+
}
|
|
208
|
+
// var declaration: var name Type = expr or var name = expr
|
|
209
|
+
const varDecl = s.match(/^var\s+(\w+)\s+(?:\w[\w.*[\]]*\s*)?=\s*(.+)$/s);
|
|
210
|
+
if (varDecl) {
|
|
211
|
+
return { kind: 'assign', line, target: varDecl[1], source: _lowerExpr(varDecl[2]) };
|
|
212
|
+
}
|
|
213
|
+
// Statement-form call: obj.Method(args) or Method(args)
|
|
214
|
+
const cm = s.match(/^([\w.]+)\s*\((.*)\)\s*$/s);
|
|
215
|
+
if (cm) {
|
|
216
|
+
return { kind: 'call', line, callee: cm[1], args: _splitTopLevelCommas(cm[2]).map(_lowerExpr) };
|
|
217
|
+
}
|
|
218
|
+
return null;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function _extractBody(src, openBrace) {
|
|
222
|
+
let depth = 1;
|
|
223
|
+
let i = openBrace + 1;
|
|
224
|
+
let inStr = null;
|
|
225
|
+
let inRaw = false;
|
|
226
|
+
let escape = false;
|
|
227
|
+
while (i < src.length && depth > 0) {
|
|
228
|
+
const c = src[i];
|
|
229
|
+
if (escape) { escape = false; i++; continue; }
|
|
230
|
+
if (inStr) {
|
|
231
|
+
if (c === '\\' && inStr === '"') { escape = true; i++; continue; }
|
|
232
|
+
if (c === inStr) inStr = null;
|
|
233
|
+
i++; continue;
|
|
234
|
+
}
|
|
235
|
+
if (inRaw) { if (c === '`') inRaw = false; i++; continue; }
|
|
236
|
+
if (c === '"') { inStr = c; i++; continue; }
|
|
237
|
+
if (c === '`') { inRaw = true; i++; continue; }
|
|
238
|
+
if (c === '{') depth++;
|
|
239
|
+
else if (c === '}') depth--;
|
|
240
|
+
if (depth === 0) return { body: src.slice(openBrace + 1, i), end: i };
|
|
241
|
+
i++;
|
|
242
|
+
}
|
|
243
|
+
return null;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function _lineAt(src, idx) {
|
|
247
|
+
let line = 1;
|
|
248
|
+
for (let i = 0; i < idx && i < src.length; i++) if (src[i] === '\n') line++;
|
|
249
|
+
return line;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function _qid(file, name, line, body) {
|
|
253
|
+
const sha = crypto.createHash('sha256').update(body).digest('hex').slice(0, 8);
|
|
254
|
+
return `${file}::${name}@${line}#${sha}`;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function _parseGoParams(paramsText) {
|
|
258
|
+
if (!paramsText.trim()) return [];
|
|
259
|
+
const parts = _splitTopLevelCommas(paramsText);
|
|
260
|
+
const params = [];
|
|
261
|
+
for (const p of parts) {
|
|
262
|
+
const t = p.trim();
|
|
263
|
+
if (!t) continue;
|
|
264
|
+
// "name Type" or "name, name2 Type" or just "Type" (unnamed)
|
|
265
|
+
const tokens = t.split(/\s+/);
|
|
266
|
+
if (tokens.length >= 2) {
|
|
267
|
+
// Could be "name Type" or "name *Type" or "name ...Type"
|
|
268
|
+
const name = tokens[0].replace(/^\*/, '');
|
|
269
|
+
if (/^[a-z_]\w*$/i.test(name) && !/^(?:func|chan|map|interface|struct)$/.test(name)) {
|
|
270
|
+
params.push(name);
|
|
271
|
+
}
|
|
272
|
+
} else if (tokens.length === 1) {
|
|
273
|
+
// Single token — could be just a type (unnamed param) or a name
|
|
274
|
+
// In Go, if it looks like a lowercase identifier, it's likely a name
|
|
275
|
+
const t0 = tokens[0].replace(/^\*/, '').replace(/^\.\.\./, '');
|
|
276
|
+
if (/^[a-z_]\w*$/.test(t0) && !/^(?:int|string|bool|byte|rune|float32|float64|error|any|interface)$/.test(t0)) {
|
|
277
|
+
params.push(t0);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
return params;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
let _nid = 0;
|
|
285
|
+
function _nextId() { return `gn${++_nid}`; }
|
|
286
|
+
|
|
287
|
+
function _addNode(nodes, node) {
|
|
288
|
+
const id = _nextId();
|
|
289
|
+
node.succ = node.succ || [];
|
|
290
|
+
node.pred = node.pred || [];
|
|
291
|
+
nodes[id] = node;
|
|
292
|
+
return id;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function _link(nodes, src, dst) {
|
|
296
|
+
if (!nodes[src] || !nodes[dst]) return;
|
|
297
|
+
if (!nodes[src].succ.includes(dst)) nodes[src].succ.push(dst);
|
|
298
|
+
if (!nodes[dst].pred.includes(src)) nodes[dst].pred.push(src);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function _buildCfg(bodyText, nodes, prevId, startLine) {
|
|
302
|
+
const stmts = _splitStatements(bodyText);
|
|
303
|
+
let prev = prevId;
|
|
304
|
+
let line = startLine;
|
|
305
|
+
for (const stmt of stmts) {
|
|
306
|
+
const s = stmt.trim();
|
|
307
|
+
if (!s || s.startsWith('//')) { line++; continue; }
|
|
308
|
+
|
|
309
|
+
// if statement with brace body
|
|
310
|
+
const ifMatch = s.match(/^if\s+([\s\S]+?)\s*\{([\s\S]*)\}(?:\s*else\s*\{([\s\S]*)\})?\s*$/s) ||
|
|
311
|
+
s.match(/^if\s+([\s\S]+?)\s*\{([\s\S]*)\}\s*$/s);
|
|
312
|
+
if (ifMatch) {
|
|
313
|
+
const condText = ifMatch[1].replace(/;[^;]*$/, '').trim();
|
|
314
|
+
const thenBody = ifMatch[2];
|
|
315
|
+
const elseBody = ifMatch[3] || null;
|
|
316
|
+
const ifNode = _addNode(nodes, { kind: 'if', cond: _lowerExpr(condText), line });
|
|
317
|
+
_link(nodes, prev, ifNode);
|
|
318
|
+
const join = _addNode(nodes, { kind: 'noop', line });
|
|
319
|
+
const thenTail = _buildCfg(thenBody, nodes, ifNode, line + 1);
|
|
320
|
+
_link(nodes, thenTail, join);
|
|
321
|
+
if (elseBody) {
|
|
322
|
+
const elseTail = _buildCfg(elseBody, nodes, ifNode, line + 1);
|
|
323
|
+
_link(nodes, elseTail, join);
|
|
324
|
+
} else {
|
|
325
|
+
_link(nodes, ifNode, join);
|
|
326
|
+
}
|
|
327
|
+
prev = join;
|
|
328
|
+
line += (s.match(/\n/g) || []).length + 1;
|
|
329
|
+
continue;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// for loop with brace body
|
|
333
|
+
const forMatch = s.match(/^for\s+([\s\S]*?)\s*\{([\s\S]*)\}\s*$/s);
|
|
334
|
+
if (forMatch) {
|
|
335
|
+
const header = _addNode(nodes, { kind: 'loop-header', line });
|
|
336
|
+
_link(nodes, prev, header);
|
|
337
|
+
const loopBody = forMatch[2];
|
|
338
|
+
// for-range: extract loop variable assignment
|
|
339
|
+
const rangeMatch = forMatch[1].match(/^(\w+)(?:\s*,\s*(\w+))?\s*:=\s*range\s+(.+)$/s);
|
|
340
|
+
let bodyPrev = header;
|
|
341
|
+
if (rangeMatch) {
|
|
342
|
+
const loopVar = rangeMatch[2] || rangeMatch[1];
|
|
343
|
+
const iterExpr = rangeMatch[3];
|
|
344
|
+
const assignId = _addNode(nodes, { kind: 'assign', target: loopVar, source: _lowerExpr(iterExpr), line });
|
|
345
|
+
_link(nodes, header, assignId);
|
|
346
|
+
bodyPrev = assignId;
|
|
347
|
+
}
|
|
348
|
+
const bodyTail = _buildCfg(loopBody, nodes, bodyPrev, line + 1);
|
|
349
|
+
_link(nodes, bodyTail, header);
|
|
350
|
+
const join = _addNode(nodes, { kind: 'noop', line });
|
|
351
|
+
_link(nodes, header, join);
|
|
352
|
+
prev = join;
|
|
353
|
+
line += (s.match(/\n/g) || []).length + 1;
|
|
354
|
+
continue;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// Regular statement
|
|
358
|
+
const node = _lowerStmt(s, line);
|
|
359
|
+
if (!node) { line++; continue; }
|
|
360
|
+
const id = _addNode(nodes, node);
|
|
361
|
+
_link(nodes, prev, id);
|
|
362
|
+
prev = id;
|
|
363
|
+
line += (s.match(/\n/g) || []).length + 1;
|
|
364
|
+
}
|
|
365
|
+
return prev;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
export function parseGoFile(file, code) {
|
|
369
|
+
if (!file || typeof code !== 'string') return null;
|
|
370
|
+
if (!/\.go$/i.test(file)) return null;
|
|
371
|
+
if (code.length > 1_000_000) return null;
|
|
372
|
+
|
|
373
|
+
const functions = [];
|
|
374
|
+
FUNC_RE.lastIndex = 0;
|
|
375
|
+
_nid = 0;
|
|
376
|
+
let m;
|
|
377
|
+
while ((m = FUNC_RE.exec(code)) !== null) {
|
|
378
|
+
const receiverName = m[1] || null;
|
|
379
|
+
const name = m[3];
|
|
380
|
+
const paramsText = m[4] || '';
|
|
381
|
+
const params = _parseGoParams(paramsText);
|
|
382
|
+
if (receiverName && !params.includes(receiverName)) {
|
|
383
|
+
params.unshift(receiverName);
|
|
384
|
+
}
|
|
385
|
+
const braceIdx = code.indexOf('{', m.index + m[0].length - 1);
|
|
386
|
+
if (braceIdx < 0) continue;
|
|
387
|
+
const extracted = _extractBody(code, braceIdx);
|
|
388
|
+
if (!extracted) continue;
|
|
389
|
+
const startLine = _lineAt(code, m.index);
|
|
390
|
+
const nodes = {};
|
|
391
|
+
const entry = _addNode(nodes, { kind: 'entry', line: startLine });
|
|
392
|
+
const exit = _addNode(nodes, { kind: 'exit', line: startLine });
|
|
393
|
+
const tail = _buildCfg(extracted.body, nodes, entry, startLine + 1);
|
|
394
|
+
_link(nodes, tail, exit);
|
|
395
|
+
functions.push({
|
|
396
|
+
qid: _qid(file, name, startLine, extracted.body),
|
|
397
|
+
name, line: startLine, params, file,
|
|
398
|
+
cfg: { entry, exit, nodes },
|
|
399
|
+
});
|
|
400
|
+
FUNC_RE.lastIndex = extracted.end + 1;
|
|
401
|
+
}
|
|
402
|
+
return functions.length ? { file, functions, topLevel: null } : null;
|
|
403
|
+
}
|
package/src/ir/parser-js.js
CHANGED
|
@@ -86,6 +86,7 @@ function exprOf(n) {
|
|
|
86
86
|
};
|
|
87
87
|
case 'ArrayExpression': return { kind: 'array', elements: (n.elements || []).map(exprOf) };
|
|
88
88
|
case 'SpreadElement': return exprOf(n.argument);
|
|
89
|
+
case 'ThisExpression': return { kind: 'ident', name: '_this_' };
|
|
89
90
|
default: return { kind: 'unknown' };
|
|
90
91
|
}
|
|
91
92
|
}
|
|
@@ -94,6 +95,7 @@ function exprOf(n) {
|
|
|
94
95
|
function lhsPath(n) {
|
|
95
96
|
if (!n) return null;
|
|
96
97
|
if (n.type === 'Identifier') return n.name;
|
|
98
|
+
if (n.type === 'ThisExpression') return '_this_';
|
|
97
99
|
if (n.type === 'MemberExpression') {
|
|
98
100
|
const base = lhsPath(n.object);
|
|
99
101
|
const prop = n.computed ? '*' : (n.property?.name || '*');
|