Skip to content

Commit f813418

Browse files
committed
extend inside-expr && inside-toplevel
1 parent 1e920ff commit f813418

43 files changed

Lines changed: 1629 additions & 764 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

cli/scan.mbt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -336,8 +336,8 @@ fn anonymous_pattern_rule(
336336
rule_id: pattern.shape,
337337
description: "Anonymous CLI pattern.",
338338
definition: Structural({
339-
inside_expr: None,
340-
inside_toplevel: None,
339+
inside_expr: [],
340+
inside_toplevel: [],
341341
patterns: [pattern],
342342
patterns_not: [],
343343
patterns_not_mode: PruneOnNegative,

docs/RuleSpec.md

Lines changed: 69 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -66,11 +66,12 @@ Only these top-level keys are accepted:
6666
- `patterns` (optional for structural rules): non-empty YAML array
6767
- `patterns-not` (optional for structural rules): non-empty YAML array using
6868
the same object schema as `patterns`
69-
- `inside-expr` (optional for structural rules): YAML mapping using the same
70-
`shape` and optional `guard` schema as `patterns`, used as an outer context
71-
- `inside-toplevel` (optional for structural rules): YAML mapping using the
72-
same `shape` and optional `guard` schema as `inside-expr`; its shape is parsed
73-
as one MoonBit top-level item
69+
- `inside-expr` (optional for structural rules): non-empty YAML array using the
70+
same `shape` and optional `guard` object schema as `patterns`; entries are
71+
ordered alternative outer expression contexts
72+
- `inside-toplevel` (optional for structural rules): non-empty YAML array using
73+
the same object schema as `inside-expr`; each shape is parsed as one MoonBit
74+
top-level item
7475
- `taint` (required for taint rules): YAML mapping
7576

7677
Unknown top-level keys are rejected.
@@ -109,18 +110,18 @@ is invalid in these entries.
109110

110111
## Shapes
111112

112-
An ordinary `shape` must be a single MoonBit expression snippet.
113-
`inside-toplevel.shape` must be exactly one MoonBit top-level item.
113+
An ordinary `shape` must be a single MoonBit expression snippet. Each
114+
`inside-toplevel` entry's `shape` must be exactly one MoonBit top-level item.
114115

115116
Valid expression shapes include calls, method calls, field accesses,
116117
operators, blocks, conditionals, loops, matches, lambdas, collection literals,
117118
record expressions, and other expression-sized MoonBit syntax. Ordinary
118119
`patterns`, `patterns-not`, and `inside-expr` shapes are not a whole file,
119120
top-level declaration, package fragment, or import list.
120121

121-
`inside-toplevel.shape` is parsed as one top-level item, such as a function,
122-
top-level `let`, `test`, method `impl`, view, or top-level expression. It is
123-
one item and cannot represent a whole file or import list.
122+
Each `inside-toplevel` shape is parsed as one top-level item, such as a
123+
function, top-level `let`, `test`, method `impl`, view, or top-level expression.
124+
It is one item and cannot represent a whole file or import list.
124125

125126
Shapes are structural:
126127

@@ -714,47 +715,56 @@ context. It may be used with `patterns`, with `patterns-not`, or with both.
714715
```yaml
715716
id: wrapped-target
716717
description: |
717-
Match a target call only inside wrapper(...).
718+
Match a target call inside either supported context.
718719
inside-expr:
719-
shape: wrapper($(prefix:exp), __TARGET__)
720+
- shape: wrapper($(prefix:exp), __TARGET__)
721+
- shape: container($(prefix:exp), __TARGET__)
720722
patterns:
721723
- shape: target.call($(prefix:exp))
722724
```
723725

724-
`inside-expr` is a YAML mapping. Its `shape` is parsed as one MoonBit
725-
expression snippet, and its optional `guard` filters `id` and `const` captures
726-
declared by that outer shape.
726+
`inside-expr` is a non-empty YAML array of pattern objects. Each `shape` is
727+
parsed as one MoonBit expression snippet, and its optional `guard` filters `id`
728+
and `const` captures declared by that outer shape. Entries are ordered
729+
alternatives.
727730

728731
Additional rules:
729732

730-
- `inside-expr` must contain exactly one `__TARGET__` occurrence in a
731-
binding-capable position.
733+
- Every `inside-expr` entry must contain exactly one `__TARGET__` occurrence in
734+
a binding-capable position.
732735
- `__TARGET__` must occupy a whole expression position, such as a whole call
733736
argument, receiver, or block expression. If it appears only as a label or
734737
other non-expression value, no target subtree can be searched.
735738
- `__TARGET__` is reserved and must not be used as an metavar name.
736739
- Entries in `patterns` and `patterns-not` must not contain `__TARGET__` in a
737740
binding-capable position.
738-
- Metavars declared by `inside-expr` remain visible when matching the
739-
inner pattern entries; inner shapes reference them by repeating the same
740-
metavar form.
741-
- Inner `patterns` and `patterns-not` must not use a visible `inside-expr`
742-
metavar name with a different kind.
741+
- Captures declared by the selected `inside-expr` entry remain visible when
742+
matching the inner pattern entries; inner shapes reference them by repeating
743+
the same metavar form.
744+
- Any capture reused by an inner `patterns` or `patterns-not` entry must be
745+
declared by every `inside-expr` alternative with the same kind. This includes
746+
named ellipsis captures and their ellipsis kinds. Outer captures that are not
747+
referenced by an inner entry may differ between alternatives.
743748

744749
Runtime behavior:
745750

746-
- the current expression is first matched against `inside-expr`
747-
- if it matches, the subtree captured by `__TARGET__` is searched
751+
- the current expression tries eligible `inside-expr` entries in YAML order
752+
- an entry whose shape does not match, or whose guard fails, falls through to
753+
the next entry
754+
- the first entry whose shape and guard both match selects the captured
755+
`__TARGET__` subtree and bindings
756+
- once an entry is selected, later alternatives are not tried even if inner
757+
matching produces no finding
748758
- when `patterns` is present, each expression in the captured subtree first
749759
tries the ordered positive patterns using the bindings established by
750-
`inside-expr`; a positive hit is recorded and its matched subtree covers any
751-
nested negative matches
760+
the selected outer entry; a positive hit is recorded and its matched subtree
761+
covers any nested negative matches
752762
- when `patterns` and `patterns-not` are both present, a candidate that fails
753763
all positive patterns is then checked against `patterns-not` using the
754-
`inside-expr` bindings; a negative match outside a positive-hit subtree
764+
the selected outer bindings; a negative match outside a positive-hit subtree
755765
rejects the whole outer match
756766
- when `patterns` is absent, every expression in the captured subtree is
757-
checked against `patterns-not` using the `inside-expr` bindings; if none of
767+
checked against `patterns-not` using the selected outer bindings; if none of
758768
them match, the outer expression produces one hit
759769
- if an inner positive or negative pattern references an inherited `id` capture
760770
with the same inline `$(name:id)` form, that candidate is skipped when the
@@ -769,39 +779,42 @@ outer expression do not produce additional findings. With only `patterns-not`,
769779

770780
### `inside-toplevel`
771781

772-
`inside-toplevel` restricts a structural rule to matches inside one MoonBit
773-
top-level item. It uses the same object schema and target-subtree semantics as
774-
`inside-expr`. Its `shape` is parsed as exactly one top-level item, not as an
775-
expression.
782+
`inside-toplevel` restricts a structural rule to matches inside selected
783+
MoonBit top-level items. It is a non-empty ordered array using the same object
784+
schema and target-subtree semantics as `inside-expr`. Each entry's `shape` is
785+
parsed as exactly one top-level item, not as an expression.
776786

777787
```yaml
778788
id: safe-function-target
779789
description: |
780790
Match calls only in selected top-level functions.
781791
inside-toplevel:
782-
shape: |
783-
fn $(name:id)($(param:id) : Int) -> Int { __TARGET__ }
784-
guard:
785-
$name: "^safe_"
792+
- shape: |
793+
fn $(name:id)($(param:id) : Int) -> Int { __TARGET__ }
794+
guard:
795+
$name: "^safe_"
786796
patterns:
787797
- shape: call($(param:id))
788798
```
789799

790800
Additional rules:
791801

792802
- `inside-toplevel` and `inside-expr` are mutually exclusive.
793-
- `inside-toplevel` must contain exactly one `__TARGET__` occurrence in a
794-
binding-capable expression position within the top-level item.
803+
- Every `inside-toplevel` entry must contain exactly one `__TARGET__`
804+
occurrence in a binding-capable expression position within the top-level
805+
item.
795806
- The top-level item itself may declare `id` and `const` captures, and its
796807
optional `guard` may filter those captures.
797-
- Metavars declared by `inside-toplevel` remain visible to inner `patterns` and
798-
`patterns-not`, using the same inherited binding and kind-consistency rules as
799-
`inside-expr`.
808+
- Captures declared by the selected `inside-toplevel` entry remain visible to
809+
inner `patterns` and `patterns-not`, using the same all-alternatives
810+
declaration and kind-consistency rules as `inside-expr`.
800811
- `inside-toplevel` is not supported on taint rules.
801812

802-
The candidate top-level item is first matched against `inside-toplevel`; if it
803-
matches, the expression subtree captured by `__TARGET__` is searched with the
804-
same inherited-binding and negative-coverage behavior as `inside-expr`.
813+
The candidate top-level item tries eligible `inside-toplevel` entries in YAML
814+
order. The first entry whose shape and guard both match selects the target and
815+
bindings; later alternatives are not tried after selection. The expression
816+
subtree captured by `__TARGET__` is searched with the same inherited-binding
817+
and negative-coverage behavior as `inside-expr`.
805818
Reporting differs: with `patterns`, every inner positive hit produces a
806819
finding whose `loc` is the inner match location. With only `patterns-not`, one
807820
finding is produced at the matched top-level item location.
@@ -933,8 +946,8 @@ A rule set or rule file is rejected when any of these conditions occurs:
933946
- `inside-expr` appears on a taint rule
934947
- `inside-toplevel` appears on a taint rule
935948
- both `inside-expr` and `inside-toplevel` appear
936-
- `inside-expr` has a non-mapping value
937-
- `inside-toplevel` has a non-mapping value
949+
- `inside-expr` or `inside-toplevel` is not an array or is empty
950+
- an `inside-expr` or `inside-toplevel` entry is not a mapping
938951
- `inside-expr` is present without `patterns` or `patterns-not`
939952
- `inside-toplevel` is present without `patterns` or `patterns-not`
940953
- `patterns` is not an array or is empty
@@ -953,7 +966,8 @@ A rule set or rule file is rejected when any of these conditions occurs:
953966
- `guard` appears in any taint clause
954967
- `metavars` appears in any pattern object
955968
- `shape` is not valid as one MoonBit expression
956-
- `inside-toplevel.shape` is not exactly one valid MoonBit top-level item
969+
- an `inside-toplevel` entry's `shape` is not exactly one valid MoonBit
970+
top-level item
957971
- a shape uses an unsupported metavar kind
958972
- a shape uses the same metavar name across multiple metavar kinds
959973
- a bare `$name` cannot be inferred to one compatible kind
@@ -970,12 +984,16 @@ A rule set or rule file is rejected when any of these conditions occurs:
970984
- an ellipsis kind is incompatible with its list position, conflicts with
971985
another typed occurrence, or shares a name with a normal metavar
972986
- a guard regex is invalid
973-
- `inside-expr` does not contain exactly one binding-capable `__TARGET__`
974-
- `inside-toplevel` does not contain exactly one binding-capable `__TARGET__`
987+
- an `inside-expr` entry does not contain exactly one binding-capable
988+
`__TARGET__`
989+
- an `inside-toplevel` entry does not contain exactly one binding-capable
990+
`__TARGET__`
975991
- a structural `patterns` or `patterns-not` entry contains binding-capable
976992
`__TARGET__`
977993
- a structural `patterns` or `patterns-not` entry uses an inherited
978994
`inside-expr` or `inside-toplevel` metavar name with a different kind
995+
- a capture reused by `patterns` or `patterns-not` is missing from any outer
996+
alternative, or a named ellipsis is declared with a different ellipsis kind
979997
- a taint source contains binding-capable `__SOURCE__`
980998
- a taint sink or sanitizer does not contain exactly one binding-capable
981999
`__SOURCE__`
@@ -1022,7 +1040,7 @@ id: unsafe-wrapper
10221040
description: |
10231041
Match a sink only under an unsafe wrapper.
10241042
inside-expr:
1025-
shape: unsafe(__TARGET__)
1043+
- shape: unsafe(__TARGET__)
10261044
patterns:
10271045
- shape: sink($_)
10281046
```
@@ -1056,7 +1074,7 @@ id: wrapper-without-danger
10561074
description: |
10571075
Match wrappers whose payload contains no danger call.
10581076
inside-expr:
1059-
shape: wrapper(__TARGET__)
1077+
- shape: wrapper(__TARGET__)
10601078
patterns-not:
10611079
- shape: danger()
10621080
```

0 commit comments

Comments
 (0)