···5757 unsupported native backends (POWER, riscv64 and s390x)
5858 (Miod Vallat, review by Nicolás Ojeda Bär)
59596060-- #7241, #12555: fix a soundness bug in the pattern-matching compiler
6161- when side-effects mutate the scrutinee during matching.
6060+- #7241, #12555, #13076, #13138: fix a soundness bug in the
6161+ pattern-matching compiler when side-effects mutate the scrutinee
6262+ during matching.
6263 Note that #7241 is not fully fixed yet, see the issue for the
6364 current status.
6465 (Gabriel Scherer, review by Nick Roberts)
+68-30
lambda/matching.ml
···10471047type 'a arg = {
10481048 arg : 'a;
10491049 binding_kind : let_kind;
10501050+ mut : mutable_flag;
10511051+ (** We track with a [mutable_flag] whether a mutable read was
10521052+ performed to access the corresponding sub-value of the
10531053+ scrutinee: an argument is [Mutable] if the path from the root of
10541054+ the value to the argument contains a mutable field. More
10551055+ precisely, a position is considered [Mutable] when accesses to
10561056+ the same position in different branches of the pattern
10571057+ matching -- outside the scope of the strict binding generated
10581058+ for the mutable read -- may observe a different value. *)
10501059}
1051106010521061type args = lambda arg list
···10871096 handlers : handler list;
10881097 or_matrix : 'matrix
10891098}
10991099+11001100+11011101+(* The composed mutability of two argument positions:
11021102+ is x.f.g a mutable position of x, depending whether f and g are mutable?
11031103+11041104+ Note that the following equations hold:
11051105+ - compose_mut mut Immutable = mut
11061106+ - compose_mut mut Mutable = Mutable
11071107+ but we do *not* use them in the code of get_expr_args_* below. We prefer
11081108+ to call [compose_mut] explicitly to make the logic more regular, make
11091109+ it obvious that we thought about how this value should evolve (or not).
11101110+*)
11111111+let compose_mut m1 m2 =
11121112+ match m1, m2 with
11131113+ | Immutable, Immutable -> Immutable
11141114+ | Mutable, _ | _, Mutable -> Mutable
1090111510911116(* Pattern matching after application of both the or-pat rule and the
10921117 mixture rule *)
···1601162616021627 If the rest doesn't generate any split, abort and do_not_precompile. *)
16031628 match args.rest with
16041604- | { arg = Lvar v; binding_kind } :: rargs -> (
16291629+ | { arg = Lvar v; _ } as first :: rargs -> (
16051630 (* We will use the name of the head column of the submatrix
16061631 we compile, and this is the *second* column of our argument. *)
16071632 match cls with
···16101635 do_not_precompile args cls def k
16111636 | _ -> (
16121637 (* Precompile *)
16131613- let var_args = {
16141614- first = { arg = Var v; binding_kind };
16151615- rest = rargs;
16161616- } in
16381638+ let var_args = { first = { first with arg = Var v }; rest = rargs } in
16171639 let var_cls =
16181640 List.map
16191641 (fun ((p, ps), act) ->
16201642 assert (simple_omega_like p);
16211621-16221643 (* we learned by pattern-matching on [args]
16231644 that [p::ps] has at least two arguments,
16241645 so [ps] must be non-empty *)
···18441865(** a submatrix after specializing by discriminant pattern;
18451866 [ctx] is the context shared by all rows. *)
1846186718471847-let make_matching get_expr_args head def ctx { first = { arg; _ }; rest } =
18481848- let def = Default_environment.specialize head def
18491849- and args = get_expr_args head (arg_of_pure arg) rest
18501850- and ctx = Context.specialize head ctx in
18681868+let make_matching get_expr_args head def ctx { first; rest } =
18691869+ let def = Default_environment.specialize head def in
18701870+ let first = { first with arg = arg_of_pure first.arg } in
18711871+ let args = get_expr_args head first rest in
18721872+ let ctx = Context.specialize head ctx in
18511873 { pm = { cases = []; args; default = def }; ctx; discr = head }
1852187418531853-let make_line_matching get_expr_args head def { first = { arg; _ }; rest } =
18751875+let make_line_matching get_expr_args head def { first; rest } =
18761876+ let first = { first with arg = arg_of_pure first.arg } in
18541877 { cases = [];
18551855- args = get_expr_args head (arg_of_pure arg) rest;
18781878+ args = get_expr_args head first rest;
18561879 default = Default_environment.specialize head def
18571880 }
18581881···19471970 | { pat_desc = Tpat_construct (_, _, args, _) } -> args @ rem
19481971 | _ -> assert false
1949197219501950-let get_expr_args_constr ~scopes head arg rem =
19731973+let get_expr_args_constr ~scopes head { arg; mut; _ } rem =
19511974 let cstr =
19521975 match head.pat_desc with
19531976 | Patterns.Head.Construct cstr -> cstr
···19611984 else
19621985 {
19631986 arg = Lprim (Pfield (pos, Pointer, Immutable), [ arg ], loc);
19871987+ mut = compose_mut mut Immutable;
19641988 binding_kind;
19651989 } :: make_args (pos + 1)
19661990 in
19671991 make_args first_pos
19681992 in
19691993 if cstr.cstr_inlined <> None then
19701970- { arg; binding_kind = Alias } :: rem
19941994+ { arg; binding_kind = Alias; mut } :: rem
19711995 else
19721996 match cstr.cstr_tag with
19731997 | Cstr_constant _
19741998 | Cstr_block _ ->
19751999 make_field_accesses Alias 0 (cstr.cstr_arity - 1) rem
19761976- | Cstr_unboxed -> { arg; binding_kind = Alias } :: rem
20002000+ | Cstr_unboxed -> { arg; binding_kind = Alias; mut } :: rem
19772001 | Cstr_extension _ -> make_field_accesses Alias 1 cstr.cstr_arity rem
1978200219792003let divide_constructor ~scopes ctx pm =
···1988201219892013let get_expr_args_variant_constant = drop_expr_arg
1990201419911991-let get_expr_args_variant_nonconst ~scopes head arg rem =
20152015+let get_expr_args_variant_nonconst ~scopes head { arg; mut; _ } rem =
19922016 let loc = head_loc ~scopes head in
19932017 {
19942018 arg = Lprim (Pfield (1, Pointer, Immutable), [ arg ], loc);
19952019 binding_kind = Alias;
20202020+ mut = compose_mut mut Immutable;
19962021 } :: rem
1997202219982023let divide_variant ~scopes row ctx { cases = cl; args; default = def } =
···21852210 tables (~ 250 elts); conditionals are better *)
21862211 inline_lazy_force_cond arg loc
2187221221882188-let get_expr_args_lazy ~scopes head arg rem =
22132213+let get_expr_args_lazy ~scopes head { arg; mut; _ } rem =
21892214 let loc = head_loc ~scopes head in
21902215 {
21912216 arg = inline_lazy_force arg loc;
21922217 binding_kind = Strict;
22182218+ mut = compose_mut mut Immutable;
22192219+ (* A lazy pattern is considered immutable, forcing its argument
22202220+ always returns the same value. *)
21932221 } :: rem
2194222221952223let divide_lazy ~scopes head ctx pm =
···22062234 | { pat_desc = Tpat_tuple args } -> args @ rem
22072235 | _ -> assert false
2208223622092209-let get_expr_args_tuple ~scopes head arg rem =
22372237+let get_expr_args_tuple ~scopes head { arg; mut; _ } rem =
22102238 let loc = head_loc ~scopes head in
22112239 let arity = Patterns.Head.arity head in
22122240 let rec make_args pos =
···22162244 {
22172245 arg = Lprim (Pfield (pos, Pointer, Immutable), [ arg ], loc);
22182246 binding_kind = Alias;
22472247+ mut = compose_mut mut Immutable;
22192248 } :: make_args (pos + 1)
22202249 in
22212250 make_args 0
···22412270 record_matching_line num_fields lbl_pat_list @ rem
22422271 | _ -> assert false
2243227222442244-let get_expr_args_record ~scopes head arg rem =
22732273+let get_expr_args_record ~scopes head { arg; mut; _ } rem =
22452274 let loc = head_loc ~scopes head in
22462275 let all_labels =
22472276 let open Patterns.Head in
···22722301 | Immutable -> Alias
22732302 | Mutable -> StrictOpt
22742303 in
22752275- { arg = access; binding_kind } :: make_args (pos + 1)
23042304+ {
23052305+ arg = access;
23062306+ binding_kind;
23072307+ mut = compose_mut mut lbl.lbl_mut;
23082308+ } :: make_args (pos + 1)
22762309 in
22772310 make_args 0
22782311···22992332 | { pat_desc = Tpat_array patl } -> patl @ rem
23002333 | _ -> assert false
2301233423022302-let get_expr_args_array ~scopes kind head arg rem =
23352335+let get_expr_args_array ~scopes kind head { arg; mut; _ } rem =
23032336 let len =
23042337 let open Patterns.Head in
23052338 match head.pat_desc with
···23192352 {
23202353 arg;
23212354 binding_kind = StrictOpt;
23552355+ mut = compose_mut mut Mutable;
23222356 } :: make_args (pos + 1)
23232357 in
23242358 make_args 0
···35683602 (m : (args, Typedtree.pattern Non_empty_row.t clause) pattern_matching) =
35693603 match m with
35703604 | { cases = []; args = [] } -> comp_exit ctx m.default
35713571- | { args = { arg; binding_kind } :: rest } ->
36053605+ | { args = { arg; binding_kind; _ } as first :: rest } ->
35723606 let v = arg_to_var arg m.cases in
35733607 bind_match_arg binding_kind v arg (
35743574- let args = { first = { arg = Var v; binding_kind = Alias }; rest } in
36083608+ let args = { first = { first with arg = Var v }; rest } in
35753609 let cases = List.map (half_simplify_nonempty ~arg:(Lvar v)) m.cases in
35763610 let m = { m with args; cases } in
35773611 let first_match, rem =
···38773911 Lstaticcatch (lam, (final_exit, []),
38783912 failure_handler ~scopes loc ~failer ())
38793913 end
39143914+39153915+let root_arg arg binding_kind =
39163916+ (* The mutability information denotes the mutability of a *position*
39173917+ inside the value, which indicates whether looking inside the
39183918+ value of the scrutinee is a pure operation. At the root we are
39193919+ immutable. *)
39203920+ { arg; binding_kind; mut = Immutable }
3880392138813922let compile_matching ~scopes loc ~failer repr arg pat_act_list partial =
38823923 let partial = check_partial pat_act_list partial in
38833883- let args = [ { arg; binding_kind = Strict } ] in
39243924+ let args = [ root_arg arg Strict ] in
38843925 let rows = map_on_rows (fun pat -> (pat, [])) pat_act_list in
38853926 let handler =
38863927 toplevel_handler ~scopes loc ~failer partial args rows
···40844125(* Easy case since variables are available *)
40854126let for_tupled_function ~scopes loc paraml pats_act_list partial =
40864127 let partial = check_partial_list pats_act_list partial in
40874087- let args =
40884088- List.map (fun id -> { arg = Lvar id; binding_kind = Strict }) paraml in
41284128+ let args = List.map (fun id -> root_arg (Lvar id) Strict) paraml in
40894129 let handler =
40904130 toplevel_handler ~scopes loc ~failer:Raise_match_failure
40914131 partial args pats_act_list in
···41704210 let sloc = Scoped_location.of_location ~scopes loc in
41714211 let args = List.map (fun id -> Lvar id) idl in
41724212 Lprim (Pmakeblock (0, Immutable, None), args, sloc) in
41734173- let input_args =
41744174- { first = { arg = Tuple arg; binding_kind = Strict }; rest = [] } in
42134213+ let input_args = { first = root_arg (Tuple arg) Strict; rest = [] } in
41754214 let handler =
41764215 let partial = check_partial pat_act_list partial in
41774216 let rows = map_on_rows (fun p -> (p, [])) pat_act_list in
···41844223 in
41854224 let next, nexts = split_and_precompile_half_simplified pm1_half in
41864225 let size = List.length idl in
41874187- let args =
41884188- List.map (fun id -> { arg = Lvar id; binding_kind = Alias }) idl in
42264226+ let args = List.map (fun id -> root_arg (Lvar id) Alias) idl in
41894227 let flat_next = flatten_precompiled size args next
41904228 and flat_nexts =
41914229 List.map (fun (e, pm) -> (e, flatten_precompiled size args pm)) nexts