Skip to content

Commit 87f85f3

Browse files
committed
update
1 parent 8f1684b commit 87f85f3

10 files changed

Lines changed: 354 additions & 14 deletions

File tree

moon.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ version = "0.1.12"
55
preferred_target = "wasm"
66

77
import {
8-
"moonbitlang/parser@0.3.7",
8+
"moonbitlang/parser@0.3.8",
99
"moonbit-community/yaml@0.0.4",
1010
"moonbit-community/chalk@0.0.1",
1111
"moonbitlang/lexer@0.3.7",

rule/compile/metavar_expr.mbt

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -60,10 +60,11 @@ fn MetavarRewriteContext::rewrite_expr(
6060
index_loc~,
6161
loc~,
6262
)
63-
Array(exprs~, loc~) => Array(exprs=ctx.rewrite_expr_list(exprs), loc~)
63+
Array(exprs~, is_iter~, loc~) =>
64+
Array(exprs=ctx.rewrite_expr_list(exprs), is_iter~, loc~)
6465
Tuple(exprs~, loc~) => Tuple(exprs=ctx.rewrite_expr_list(exprs), loc~)
65-
ArraySpread(elems~, loc~) =>
66-
ArraySpread(elems=ctx.rewrite_spreadable_elems(elems), loc~)
66+
ArraySpread(elems~, is_iter~, loc~) =>
67+
ArraySpread(elems=ctx.rewrite_spreadable_elems(elems), is_iter~, loc~)
6768
Group(expr=inner, group~, loc~) =>
6869
Group(expr=ctx.rewrite_expr(inner), group~, loc~)
6970
Constraint(expr=inner, ty~, loc~) =>
@@ -150,6 +151,8 @@ fn MetavarRewriteContext::rewrite_expr(
150151
loc~,
151152
)
152153
Interp(elems~, loc~) => Interp(elems=ctx.rewrite_interp_elems(elems), loc~)
154+
BytesInterp(elems~, loc~) =>
155+
BytesInterp(elems=ctx.rewrite_interp_elems(elems), loc~)
153156
Is(expr=inner, pat~, loc~) =>
154157
Is(expr=ctx.rewrite_expr(inner), pat=ctx.rewrite_pattern(pat), loc~)
155158
Let(pattern~, expr=bound_expr, body~, loc~) =>
@@ -254,11 +257,12 @@ fn MetavarRewriteContext::rewrite_expr(
254257
label=ctx.rewrite_optional_label(label),
255258
loc~,
256259
)
257-
ListComprehension(kind~, guard_~, body~, loc~) =>
260+
ListComprehension(kind~, guard_~, body~, is_iter~, loc~) =>
258261
ListComprehension(
259262
kind~,
260263
guard_=ctx.rewrite_optional_expr(guard_),
261264
body=ctx.rewrite_expr(body),
265+
is_iter~,
262266
loc~,
263267
)
264268
ProofLet(binder~, expr=inner, loc~) =>
@@ -271,10 +275,11 @@ fn MetavarRewriteContext::rewrite_expr(
271275
ProofAssert(expr=ctx.rewrite_expr(inner), loc~)
272276
Raise(err_value~, loc~) =>
273277
Raise(err_value=ctx.rewrite_expr(err_value), loc~)
274-
TemplateWriting(expr=inner, template~, loc~) =>
278+
TemplateWriting(expr=inner, template~, is_conditional~, loc~) =>
275279
TemplateWriting(
276280
expr=ctx.rewrite_expr(inner),
277281
template=ctx.rewrite_expr(template),
282+
is_conditional~,
278283
loc~,
279284
)
280285
StaticAssert(asserts~, body~) =>

taint/engine.mbt

Lines changed: 147 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,18 @@ fn eval_expr(
249249
)
250250
None => normal_result(state, [])
251251
}
252+
Expr_Lexscan =>
253+
match node_child(expr, "expr") {
254+
Some(scrutinee) =>
255+
eval_lex_scan(
256+
scrutinee,
257+
node_child_items(expr, "cases"),
258+
state,
259+
spec,
260+
findings,
261+
)
262+
None => normal_result(state, [])
263+
}
252264
Expr_Try =>
253265
match node_child(expr, "body") {
254266
Some(body) =>
@@ -2049,6 +2061,56 @@ fn bind_lex_pattern(
20492061
}
20502062
}
20512063

2064+
///|
2065+
fn lex_scan_pattern_bound_roots(pattern : @untyped_ast.Node) -> Array[String] {
2066+
let roots : Array[String] = []
2067+
add_lex_scan_pattern_bound_roots(roots, pattern)
2068+
roots
2069+
}
2070+
2071+
///|
2072+
fn add_lex_scan_pattern_bound_roots(
2073+
roots : Array[String],
2074+
pattern : @untyped_ast.Node,
2075+
) -> Unit {
2076+
match pattern.kind {
2077+
LexScanCasePattern_Binder =>
2078+
if node_first_child(pattern) is Some(binder) {
2079+
add_binder_root_name(roots, binder)
2080+
}
2081+
LexScanCasePattern_Pattern =>
2082+
if node_first_child(pattern) is Some(pat) {
2083+
add_regex_pattern_bound_roots(roots, pat)
2084+
}
2085+
LexScanCasePattern_Wildcard => ()
2086+
_ => ()
2087+
}
2088+
}
2089+
2090+
///|
2091+
fn bind_lex_scan_pattern(
2092+
pattern : @untyped_ast.Node,
2093+
value : TaintTree,
2094+
state : TaintState,
2095+
) -> TaintState {
2096+
match pattern.kind {
2097+
LexScanCasePattern_Binder =>
2098+
if node_first_child(pattern) is Some(binder) {
2099+
bind_binder(binder, value, state)
2100+
} else {
2101+
state
2102+
}
2103+
LexScanCasePattern_Pattern =>
2104+
if node_first_child(pattern) is Some(pat) {
2105+
bind_regex_pattern(pat, value, state)
2106+
} else {
2107+
state
2108+
}
2109+
LexScanCasePattern_Wildcard => state
2110+
_ => state
2111+
}
2112+
}
2113+
20522114
///|
20532115
fn regex_pattern_bound_roots(pattern : @untyped_ast.Node) -> Array[String] {
20542116
let roots : Array[String] = []
@@ -2405,6 +2467,90 @@ fn eval_lex_case(
24052467
restore_result_scoped_roots(result, pattern_result.state, roots)
24062468
}
24072469

2470+
///|
2471+
fn eval_lex_scan(
2472+
scrutinee : @untyped_ast.Node,
2473+
cases : Array[@untyped_ast.Node],
2474+
state : TaintState,
2475+
spec : TaintSpec,
2476+
findings : Array[SinkFinding],
2477+
) -> EvalResult {
2478+
let scrutinee_result = eval_expr(scrutinee, state, spec, findings)
2479+
if scrutinee_result.flow != FlowNormal {
2480+
return scrutinee_result
2481+
}
2482+
let normal_states : Array[TaintState] = []
2483+
let mut branch_value : TaintTree = []
2484+
let exit_states : Array[TaintState] = []
2485+
let mut exit_flow = FlowNormal
2486+
let mut exit_value : TaintTree = []
2487+
for case in cases {
2488+
let case_result = eval_lex_scan_case(
2489+
case,
2490+
scrutinee_result.value,
2491+
scrutinee_result.state,
2492+
spec,
2493+
findings,
2494+
)
2495+
if case_result.flow == FlowNormal {
2496+
normal_states.push(case_result.state)
2497+
branch_value = tree_merge(branch_value, case_result.value)
2498+
} else {
2499+
if exit_states.is_empty() {
2500+
exit_flow = case_result.flow
2501+
exit_value = case_result.value
2502+
}
2503+
exit_states.push(case_result.state)
2504+
}
2505+
}
2506+
if normal_states.length() > 0 {
2507+
normal_result(state_merge_all(normal_states), branch_value)
2508+
} else if exit_states.length() > 0 {
2509+
flow_result(exit_flow, state_merge_all(exit_states), exit_value)
2510+
} else {
2511+
normal_result(scrutinee_result.state, [])
2512+
}
2513+
}
2514+
2515+
///|
2516+
fn eval_lex_scan_case(
2517+
case : @untyped_ast.Node,
2518+
value : TaintTree,
2519+
base_state : TaintState,
2520+
spec : TaintSpec,
2521+
findings : Array[SinkFinding],
2522+
) -> EvalResult {
2523+
let pattern = match node_child(case, "pat") {
2524+
Some(pattern) => pattern
2525+
None => return normal_result(base_state, [])
2526+
}
2527+
let body = match node_child(case, "body") {
2528+
Some(body) => body
2529+
None => return normal_result(base_state, [])
2530+
}
2531+
let roots = lex_scan_pattern_bound_roots(pattern)
2532+
let bound = bind_lex_scan_pattern(pattern, value, base_state)
2533+
let result = if node_optional_child(case, "guard") is Some(guard_expr) {
2534+
let guard_result = eval_condition_scope(guard_expr, bound, spec, findings)
2535+
if guard_result.flow == FlowNormal {
2536+
restore_result_scoped_roots(
2537+
eval_expr(body, guard_result.true_state, spec, findings),
2538+
guard_result.base_state,
2539+
guard_result.bound_roots,
2540+
)
2541+
} else {
2542+
{
2543+
state: guard_result.base_state,
2544+
value: guard_result.value,
2545+
flow: guard_result.flow,
2546+
}
2547+
}
2548+
} else {
2549+
eval_expr(body, bound, spec, findings)
2550+
}
2551+
restore_result_scoped_roots(result, base_state, roots)
2552+
}
2553+
24082554
///|
24092555
fn eval_try(
24102556
body : @untyped_ast.Node,
@@ -2856,7 +3002,7 @@ fn eval_unknown_expr(
28563002
}
28573003
None => normal_result(state, [])
28583004
}
2859-
Expr_Interp =>
3005+
Expr_Interp | Expr_BytesInterp =>
28603006
eval_interp_elems(node_child_items(expr, "elems"), state, spec, findings)
28613007
Expr_Map => {
28623008
let mut current = state

taint/taint_test.mbt

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -451,6 +451,49 @@ test "taint engine match case binder shadows outer root in case body" {
451451
assert_eq(result.findings.length(), 0)
452452
}
453453

454+
///|
455+
test "taint engine lexscan binder receives scrutinee taint" {
456+
let source =
457+
#|fn sample(input) {
458+
#| lexscan input {
459+
#| item => sink(item);
460+
#| _ => ()
461+
#| }
462+
#|}
463+
#|
464+
let result = analyze_single_entry_sink(source, "input")
465+
assert_eq(result.findings.length(), 1)
466+
}
467+
468+
///|
469+
test "taint engine lexscan regex alias feeds guard pattern binding" {
470+
let source =
471+
#|fn sample(input) {
472+
#| lexscan input {
473+
#| re"." as item if item is Some(part) => sink(part);
474+
#| _ => ()
475+
#| }
476+
#|}
477+
#|
478+
let result = analyze_single_entry_sink(source, "input")
479+
assert_eq(result.findings.length(), 1)
480+
}
481+
482+
///|
483+
test "taint engine restores lexscan binder before following expression" {
484+
let source =
485+
#|fn sample(input, clean) {
486+
#| lexscan clean {
487+
#| input => sink(input);
488+
#| _ => ()
489+
#| };
490+
#| sink(input)
491+
#|}
492+
#|
493+
let result = analyze_single_entry_sink(source, "input")
494+
assert_eq(result.findings.length(), 1)
495+
}
496+
454497
///|
455498
test "taint engine restores catch binder before following expression" {
456499
let source =

untyped_ast/contains_unshadowed.mbt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ fn contains_unshadowed_node(node : Node, id : String, shadowed~ : Bool) -> Bool
2121
Expr_ForEach => contains_foreach_unshadowed(node, id, shadowed)
2222
Expr_Match => contains_match_unshadowed(node, id, shadowed)
2323
Expr_Lexmatch => contains_lexmatch_unshadowed(node, id, shadowed)
24+
Expr_Lexscan => contains_lexscan_unshadowed(node, id, shadowed)
2425
Expr_ListComprehension =>
2526
contains_list_comprehension_unshadowed(node, id, shadowed)
2627
Expr_Function => contains_expr_function_unshadowed(node, id, shadowed)
@@ -164,6 +165,16 @@ fn contains_lexmatch_unshadowed(
164165
contains_case_list_unshadowed(node, "cases", "pat", id, shadowed)
165166
}
166167

168+
///|
169+
fn contains_lexscan_unshadowed(
170+
node : Node,
171+
id : String,
172+
shadowed : Bool,
173+
) -> Bool {
174+
contains_named_child_unshadowed(node, "expr", id, shadowed) ||
175+
contains_case_list_unshadowed(node, "cases", "pat", id, shadowed)
176+
}
177+
167178
///|
168179
fn contains_list_comprehension_unshadowed(
169180
node : Node,

0 commit comments

Comments
 (0)