@clear-capabilities/agentic-security-scanner 0.195.13 → 0.195.14
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/CHANGELOG.md +17 -0
- package/dist/{1f01fe10027e772f73a1.json → 4fe5c024ca0626112826.json} +2 -2
- package/dist/agentic-security.mjs +2 -2
- package/dist/agentic-security.mjs.sha256 +1 -1
- package/package.json +2 -2
- package/src/dataflow/engine.js +19 -4
- package/src/language/CLAUDE.md +2 -0
- package/src/language/haskell-ir.js +80 -13
- package/src/language/haskell-models.js +11 -0
- package/src/language/haskell-parser.js +104 -4
- package/src/language/haskell-syntax.js +83 -4
|
@@ -18,6 +18,11 @@ const KW = new Set(['case', 'class', 'data', 'default', 'deriving', 'do', 'else'
|
|
|
18
18
|
const NUMERIC_TYPE = /^\(?\s*(?:(?:Maybe|IO|ActionM|Handler|Either\s+String)\s+)?\(?\s*(?:Int|Integer|Int(?:8|16|32|64)|Word|Word(?:8|16|32|64)|Double|Float|Natural|Bool|Scientific)\s*\)?\s*\)?$/;
|
|
19
19
|
// The module enables Template Haskell (a LANGUAGE pragma; a cabal `default-extensions` entry is not seen here, a stated limit).
|
|
20
20
|
const TH_PRAGMA = /\{-#\s*LANGUAGE\b[^#]*\bTemplateHaskell\b/;
|
|
21
|
+
// OverloadedRecordDot: `r.field` (no spaces, lowercase field) is a field selection, `(.field)` a selector section. OverloadedLabels: `#field`
|
|
22
|
+
// in prefix position is a label value (IHP's `get #name r`, `set #name v r`). Both read from the LANGUAGE pragma only, as above.
|
|
23
|
+
const RECORD_DOT_PRAGMA = /\{-#\s*LANGUAGE\b[^#]*\bOverloadedRecordDot\b/;
|
|
24
|
+
const LABELS_PRAGMA = /\{-#\s*LANGUAGE\b[^#]*\bOverloadedLabels\b/;
|
|
25
|
+
const RECDO_PRAGMA = /\{-#\s*LANGUAGE\b[^#]*\bRecursiveDo\b/;
|
|
21
26
|
const RESERVED_OPS = new Set(['=', '|', '->', '<-', '::', '=>', '\\', '@', '~', '..']);
|
|
22
27
|
const SYM = new Set('!#$%&*+./<=>?@\\^|-~:'.split(''));
|
|
23
28
|
const OPEN = { '(': ')', '[': ']', '{': '}' };
|
|
@@ -36,6 +41,9 @@ export function lexHs(src, opts = {}) {
|
|
|
36
41
|
const T = [];
|
|
37
42
|
const n = src.length;
|
|
38
43
|
const thSplices = !!opts.th;
|
|
44
|
+
const recordDot = !!opts.recordDot;
|
|
45
|
+
const labels = !!opts.labels;
|
|
46
|
+
const recursiveDo = !!opts.recursiveDo;
|
|
39
47
|
let i = 0;
|
|
40
48
|
let line = 1;
|
|
41
49
|
let col = 0;
|
|
@@ -138,7 +146,9 @@ export function lexHs(src, opts = {}) {
|
|
|
138
146
|
let k = i;
|
|
139
147
|
while (isIdChar(src[k])) k++;
|
|
140
148
|
const v = src.slice(i, k);
|
|
141
|
-
|
|
149
|
+
// `mdo` (RecursiveDo) is a `do` whose statements may refer to later ones; for the lowering that is a `do`. (`rec` blocks are not read.)
|
|
150
|
+
if (v === 'mdo' && recursiveDo) push('kw', 'do', {}, k - i);
|
|
151
|
+
else push(KW.has(v) ? 'kw' : 'var', v, {}, k - i);
|
|
142
152
|
continue;
|
|
143
153
|
}
|
|
144
154
|
if (c === '(' || c === ')' || c === '[' || c === ']' || c === ',' || c === ';' || c === '`' || c === '{' || c === '}') {
|
|
@@ -153,6 +163,22 @@ export function lexHs(src, opts = {}) {
|
|
|
153
163
|
const prefix = !(isIdChar(prev) || isSym(prev) || prev === ')' || prev === ']' || prev === '}' || prev === '"' || prev === "'");
|
|
154
164
|
if (prefix && (src[k] === '(' || (isIdStart(src[k]) && !isUpper(src[k])))) { push('splice', src.slice(i, k), {}, k - i); continue; }
|
|
155
165
|
}
|
|
166
|
+
// `r.field` / `(e).field` / `r.a.b` with OverloadedRecordDot: a `.` touching an identifier, `)` or `}` on its left and a lowercase name on
|
|
167
|
+
// its right is a field selection (`sel` token), never composition (GHC reads it the same way under the extension). `(.field)` is the
|
|
168
|
+
// selector section: a `.` touching `(` on its left.
|
|
169
|
+
if (c === '.' && recordDot && isIdStart(src[i + 1]) && !isUpper(src[i + 1]) && i > 0 && (isIdChar(src[i - 1]) || src[i - 1] === ')' || src[i - 1] === '}' || src[i - 1] === '(')) {
|
|
170
|
+
let m = i + 1;
|
|
171
|
+
while (isIdChar(src[m])) m++;
|
|
172
|
+
push('sel', src.slice(i + 1, m), { section: src[i - 1] === '(' }, m - i);
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
// `#field` with OverloadedLabels, in prefix position: a label value.
|
|
176
|
+
if (c === '#' && labels && isIdStart(src[i + 1]) && !isUpper(src[i + 1]) && (i === 0 || !(isIdChar(src[i - 1]) || isSym(src[i - 1]) || src[i - 1] === ')' || src[i - 1] === ']'))) {
|
|
177
|
+
let m = i + 1;
|
|
178
|
+
while (isIdChar(src[m])) m++;
|
|
179
|
+
push('label', src.slice(i + 1, m), {}, m - i);
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
156
182
|
if (isSym(c)) {
|
|
157
183
|
let j = i;
|
|
158
184
|
while (isSym(src[j])) j++;
|
|
@@ -196,7 +222,7 @@ const BT_FIX = { elem: [4, 'l'], notElem: [4, 'l'], div: [7, 'l'], mod: [7, 'l']
|
|
|
196
222
|
* @returns {{tokens:object[], module:object|null, imports:object[], decls:object[], sigs:object[], data:object[], classes:object[], instances:object[], errors:object[], boundaries:object[]}}
|
|
197
223
|
*/
|
|
198
224
|
export function parseSyntax(src, opts = {}) {
|
|
199
|
-
const T = lexHs(src, { th: TH_PRAGMA.test(src) });
|
|
225
|
+
const T = lexHs(src, { th: TH_PRAGMA.test(src), recordDot: RECORD_DOT_PRAGMA.test(src), labels: LABELS_PRAGMA.test(src), recursiveDo: RECDO_PRAGMA.test(src) });
|
|
200
226
|
const mate = matchBrackets(T);
|
|
201
227
|
const N = T.length;
|
|
202
228
|
const errors = [];
|
|
@@ -469,6 +495,19 @@ export function parseSyntax(src, opts = {}) {
|
|
|
469
495
|
pos = blk.b;
|
|
470
496
|
return { t: 'lamcase', alts: withIndent(blk.indent, () => blk.items.map(([a, b]) => parseAlt(a, b))), line: t.line };
|
|
471
497
|
}
|
|
498
|
+
// `\cases` (GHC 9.4): alternatives with SEVERAL patterns each, `0 y -> ..; x y -> ..`.
|
|
499
|
+
if (peek() && peek().t === 'var' && peek().v === 'cases') {
|
|
500
|
+
const blk = blockAfter(pos, lim, layoutIndent);
|
|
501
|
+
pos = blk.b;
|
|
502
|
+
const parseAltMulti = (a, b) => {
|
|
503
|
+
const k = findTop(a, b, (x) => isRop(x, '->') || isRop(x, '|'));
|
|
504
|
+
if (k < 0) { err('expected-arrow', T[a], 'case alternative has no `->`'); return { pats: [{ t: 'pwild' }], rhs: { guards: null, body: { t: 'unknown' }, where: [] }, line: T[a].line, bad: true }; }
|
|
505
|
+
const pats = [];
|
|
506
|
+
for (let q = a; q < k;) { const r = apatAt(q, k); if (!r) break; pats.push(r[0]); q = r[1]; }
|
|
507
|
+
return { pats, rhs: parseRhs(k, b, '->'), line: T[a].line };
|
|
508
|
+
};
|
|
509
|
+
return { t: 'lamcases', alts: withIndent(blk.indent, () => blk.items.map(([a, b]) => parseAltMulti(a, b))), line: t.line };
|
|
510
|
+
}
|
|
472
511
|
const params = [];
|
|
473
512
|
while (pos < lim && !isRop(T[pos], '->')) {
|
|
474
513
|
const r = apatAt(pos, lim);
|
|
@@ -532,7 +571,7 @@ export function parseSyntax(src, opts = {}) {
|
|
|
532
571
|
function startsAexp() {
|
|
533
572
|
const t = peek();
|
|
534
573
|
if (!t) return false;
|
|
535
|
-
if (t.t === 'var' || t.t === 'con' || t.t === 'int' || t.t === 'float' || t.t === 'str' || t.t === 'chr' || t.t === 'splice') return true;
|
|
574
|
+
if (t.t === 'var' || t.t === 'con' || t.t === 'int' || t.t === 'float' || t.t === 'str' || t.t === 'chr' || t.t === 'splice' || t.t === 'label') return true;
|
|
536
575
|
return isSp(t, '(') || isSp(t, '[');
|
|
537
576
|
}
|
|
538
577
|
function fexp() {
|
|
@@ -565,6 +604,7 @@ export function parseSyntax(src, opts = {}) {
|
|
|
565
604
|
else if (peek() && peek().t === 'var') pos++;
|
|
566
605
|
return { t: 'splice', line: t.line };
|
|
567
606
|
}
|
|
607
|
+
else if (t.t === 'label') { pos++; e = { t: 'label', name: t.v, line: t.line }; }
|
|
568
608
|
else { err('unexpected-token', t, `unexpected token ${t.v}`); pos++; return { t: 'unknown', line: t.line }; }
|
|
569
609
|
while (isSp(peek(), '{') && mate[pos] > pos && mate[pos] < lim && (e.t === 'con' || e.t === 'var' || e.t === 'rec' || e.t === 'paren')) {
|
|
570
610
|
const close = mate[pos];
|
|
@@ -580,6 +620,8 @@ export function parseSyntax(src, opts = {}) {
|
|
|
580
620
|
e = e.t === 'con' ? { t: 'rec', con: e, fields, line: t.line } : { t: 'recupd', base: e, fields, line: t.line };
|
|
581
621
|
pos = close + 1;
|
|
582
622
|
}
|
|
623
|
+
// `e.field` (OverloadedRecordDot): the field selector applied to `e`, which the IR lowers to a member read of a known record field.
|
|
624
|
+
while (peek() && peek().t === 'sel' && !peek().section) { const s = peek(); pos++; e = { t: 'app', f: { t: 'var', name: s.v, qual: null, line: s.line }, args: [e], line: s.line, recordDot: true }; }
|
|
583
625
|
return e;
|
|
584
626
|
}
|
|
585
627
|
const isOpTok = (x) => x && x.t === 'op';
|
|
@@ -594,6 +636,8 @@ export function parseSyntax(src, opts = {}) {
|
|
|
594
636
|
}
|
|
595
637
|
const [s, e] = parts[0];
|
|
596
638
|
const first = T[s];
|
|
639
|
+
// `(.field)` (OverloadedRecordDot): the selector function itself.
|
|
640
|
+
if (first && first.t === 'sel' && first.section && e - s === 1) return { t: 'var', name: first.v, qual: null, line };
|
|
597
641
|
if (isOpTok(first) && e - s === 1) return { t: 'var', name: first.v, qual: first.q || null, op: true, line };
|
|
598
642
|
if (isOpTok(first) && first.v !== '-') return { t: 'rsec', op: first.v, qual: first.q || null, e: sub(s + 1, e, expr0), line };
|
|
599
643
|
if (isSp(first, '`') && T[s + 2] && isSp(T[s + 2], '`') && e - s > 3) return { t: 'rsec', op: T[s + 1].v, qual: T[s + 1].q || null, e: sub(s + 3, e, expr0), line, backtick: true };
|
|
@@ -700,6 +744,9 @@ export function parseSyntax(src, opts = {}) {
|
|
|
700
744
|
return out;
|
|
701
745
|
}
|
|
702
746
|
function parseDecl(a, b) {
|
|
747
|
+
// `pattern P :: T` / `pattern P x = ..` (PatternSynonyms): a synonym, recorded by name, not a function called `pattern`. Its right-hand
|
|
748
|
+
// side is not modelled (a match on `P` is a constructor pattern to the IR, which is sound for taint: it binds what it binds).
|
|
749
|
+
if (T[a].t === 'var' && T[a].v === 'pattern' && T[a + 1] && T[a + 1].t === 'con' && a + 1 < b) return { t: 'patsyn', name: T[a + 1].v, line: T[a].line };
|
|
703
750
|
const mark = findTop(a, b, (x) => x.t === 'rop' && (x.v === '=' || x.v === '|' || x.v === '::'));
|
|
704
751
|
if (mark < 0) { err('expected-declaration', T[a], 'item is neither a binding nor a signature'); return null; }
|
|
705
752
|
if (T[mark].v === '::') {
|
|
@@ -754,7 +801,39 @@ export function parseSyntax(src, opts = {}) {
|
|
|
754
801
|
let nameTok = null;
|
|
755
802
|
for (let q = a + 1; q < (eq < 0 ? b : eq); q++) if (T[q].t === 'con') { nameTok = T[q]; break; }
|
|
756
803
|
const decl = { t: 'data', newtype: isNewtype, name: nameTok ? nameTok.v : null, constructors: [], line: T[a].line };
|
|
757
|
-
if (eq < 0)
|
|
804
|
+
if (eq < 0) {
|
|
805
|
+
// GADT syntax: `data T where { A :: Int -> T; B :: { fld :: String } -> T }`. Each item names one or more constructors, their record
|
|
806
|
+
// fields when written with braces, else an arity of one per top-level arrow; the result type is not read (no type checking).
|
|
807
|
+
const w = findTop(a, b, (x) => isKw(x, 'where'));
|
|
808
|
+
if (w < 0) return decl;
|
|
809
|
+
const blk = blockAfter(w, b, layoutIndent);
|
|
810
|
+
for (const [s, e] of blk.items) {
|
|
811
|
+
const names = [];
|
|
812
|
+
let i = s;
|
|
813
|
+
while (i < e && T[i].t === 'con') { names.push(T[i].v); i++; if (isSp(T[i], ',')) i++; else break; }
|
|
814
|
+
if (!names.length || !isRop(T[i], '::')) continue;
|
|
815
|
+
i++;
|
|
816
|
+
while (i < e && ((T[i].t === 'var' && T[i].v === 'forall') || T[i].t === 'op' || isRop(T[i], '=>'))) i = findTop(i, e, (x) => isRop(x, '=>')) >= 0 ? findTop(i, e, (x) => isRop(x, '=>')) + 1 : e;
|
|
817
|
+
let fields = null;
|
|
818
|
+
let arity = 0;
|
|
819
|
+
if (isSp(T[i], '{') && mate[i] > i) {
|
|
820
|
+
fields = [];
|
|
821
|
+
const close = mate[i];
|
|
822
|
+
let j = i + 1;
|
|
823
|
+
while (j < close) {
|
|
824
|
+
const ns = [];
|
|
825
|
+
while (j < close && T[j].t === 'var') { ns.push(T[j].v); j++; if (isSp(T[j], ',')) j++; else break; }
|
|
826
|
+
if (isRop(T[j], '::')) { j++; while (j < close && !isSp(T[j], ',')) j = Math.min(close, groupEnd(j)); j++; } else break;
|
|
827
|
+
fields.push(...ns);
|
|
828
|
+
}
|
|
829
|
+
arity = fields.length;
|
|
830
|
+
} else {
|
|
831
|
+
for (let j = i; j < e;) { if (isRop(T[j], '->')) arity++; j = Math.min(e, groupEnd(j)); }
|
|
832
|
+
}
|
|
833
|
+
for (const n of names) decl.constructors.push({ name: n, fields, arity });
|
|
834
|
+
}
|
|
835
|
+
return decl;
|
|
836
|
+
}
|
|
758
837
|
let end = findTop(eq + 1, b, (x) => isKw(x, 'deriving'));
|
|
759
838
|
if (end < 0) end = b;
|
|
760
839
|
for (const [s0, e] of splitTop(eq + 1, end, (x) => isRop(x, '|'))) {
|