diff options
Diffstat (limited to 'compiler/Invariants.ml')
-rw-r--r-- | compiler/Invariants.ml | 794 |
1 files changed, 794 insertions, 0 deletions
diff --git a/compiler/Invariants.ml b/compiler/Invariants.ml new file mode 100644 index 00000000..4a3364a6 --- /dev/null +++ b/compiler/Invariants.ml @@ -0,0 +1,794 @@ +(* The following module defines functions to check that some invariants + * are always maintained by evaluation contexts *) + +module T = Types +module V = Values +module E = Expressions +module C = Contexts +module Subst = Substitute +module A = LlbcAst +module L = Logging +open Cps +open TypesUtils +open InterpreterUtils +open InterpreterBorrowsCore + +(** The local logger *) +let log = L.invariants_log + +type borrow_info = { + loan_kind : T.ref_kind; + loan_in_abs : bool; + (* true if the loan was found in an abstraction *) + loan_ids : V.BorrowId.Set.t; + borrow_ids : V.BorrowId.Set.t; +} +[@@deriving show] + +type outer_borrow_info = { + outer_borrow : bool; + (* true if the value is borrowed *) + outer_shared : bool; (* true if the value is borrowed as shared *) +} + +let set_outer_mut (info : outer_borrow_info) : outer_borrow_info = + { info with outer_borrow = true } + +let set_outer_shared (_info : outer_borrow_info) : outer_borrow_info = + { outer_borrow = true; outer_shared = true } + +let ids_reprs_to_string (indent : string) + (reprs : V.BorrowId.id V.BorrowId.Map.t) : string = + V.BorrowId.Map.to_string (Some indent) V.BorrowId.to_string reprs + +let borrows_infos_to_string (indent : string) + (infos : borrow_info V.BorrowId.Map.t) : string = + V.BorrowId.Map.to_string (Some indent) show_borrow_info infos + +type borrow_kind = Mut | Shared | Inactivated + +(** Check that: + - loans and borrows are correctly related + - a two-phase borrow can't point to a value inside an abstraction + *) +let check_loans_borrows_relation_invariant (ctx : C.eval_ctx) : unit = + (* Link all the borrow ids to a representant - necessary because of shared + * borrows/loans *) + let ids_reprs : V.BorrowId.id V.BorrowId.Map.t ref = + ref V.BorrowId.Map.empty + in + (* Link all the id representants to a borrow information *) + let borrows_infos : borrow_info V.BorrowId.Map.t ref = + ref V.BorrowId.Map.empty + in + let context_to_string () : string = + eval_ctx_to_string ctx ^ "- representants:\n" + ^ ids_reprs_to_string " " !ids_reprs + ^ "\n- info:\n" + ^ borrows_infos_to_string " " !borrows_infos + in + (* Ignored loans - when we find an ignored loan while building the borrows_infos + * map, we register it in this list; once the borrows_infos map is completely + * built, we check that all the borrow ids of the ignored loans are in this + * map *) + let ignored_loans : (T.ref_kind * V.BorrowId.id) list ref = ref [] in + + (* first, register all the loans *) + (* Some utilities to register the loans *) + let register_ignored_loan (rkind : T.ref_kind) (bid : V.BorrowId.id) : unit = + ignored_loans := (rkind, bid) :: !ignored_loans + in + + let register_shared_loan (loan_in_abs : bool) (bids : V.BorrowId.Set.t) : unit + = + let reprs = !ids_reprs in + let infos = !borrows_infos in + (* Use the first borrow id as representant *) + let repr_bid = V.BorrowId.Set.min_elt bids in + assert (not (V.BorrowId.Map.mem repr_bid infos)); + (* Insert the mappings to the representant *) + let reprs = + V.BorrowId.Set.fold + (fun bid reprs -> + assert (not (V.BorrowId.Map.mem bid reprs)); + V.BorrowId.Map.add bid repr_bid reprs) + bids reprs + in + (* Insert the loan info *) + let info = + { + loan_kind = T.Shared; + loan_in_abs; + loan_ids = bids; + borrow_ids = V.BorrowId.Set.empty; + } + in + let infos = V.BorrowId.Map.add repr_bid info infos in + (* Update *) + ids_reprs := reprs; + borrows_infos := infos + in + + let register_mut_loan (loan_in_abs : bool) (bid : V.BorrowId.id) : unit = + let reprs = !ids_reprs in + let infos = !borrows_infos in + (* Sanity checks *) + assert (not (V.BorrowId.Map.mem bid reprs)); + assert (not (V.BorrowId.Map.mem bid infos)); + (* Add the mapping for the representant *) + let reprs = V.BorrowId.Map.add bid bid reprs in + (* Add the mapping for the loan info *) + let info = + { + loan_kind = T.Mut; + loan_in_abs; + loan_ids = V.BorrowId.Set.singleton bid; + borrow_ids = V.BorrowId.Set.empty; + } + in + let infos = V.BorrowId.Map.add bid info infos in + (* Update *) + ids_reprs := reprs; + borrows_infos := infos + in + + let loans_visitor = + object + inherit [_] C.iter_eval_ctx as super + + method! visit_Var _ binder v = + let inside_abs = false in + super#visit_Var inside_abs binder v + + method! visit_Abs _ abs = + let inside_abs = true in + super#visit_Abs inside_abs abs + + method! visit_loan_content inside_abs lc = + (* Register the loan *) + let _ = + match lc with + | V.SharedLoan (bids, _) -> register_shared_loan inside_abs bids + | V.MutLoan bid -> register_mut_loan inside_abs bid + in + (* Continue exploring *) + super#visit_loan_content inside_abs lc + + method! visit_aloan_content inside_abs lc = + let _ = + match lc with + | V.AMutLoan (bid, _) -> register_mut_loan inside_abs bid + | V.ASharedLoan (bids, _, _) -> register_shared_loan inside_abs bids + | V.AIgnoredMutLoan (bid, _) -> register_ignored_loan T.Mut bid + | V.AIgnoredSharedLoan _ + | V.AEndedMutLoan { given_back = _; child = _; given_back_meta = _ } + | V.AEndedSharedLoan (_, _) + | V.AEndedIgnoredMutLoan + { given_back = _; child = _; given_back_meta = _ } -> + (* Do nothing *) + () + in + (* Continue exploring *) + super#visit_aloan_content inside_abs lc + end + in + + (* Visit *) + let inside_abs = false in + loans_visitor#visit_eval_ctx inside_abs ctx; + + (* Then, register all the borrows *) + (* Some utilities to register the borrows *) + let find_info (bid : V.BorrowId.id) : borrow_info = + (* Find the representant *) + match V.BorrowId.Map.find_opt bid !ids_reprs with + | Some repr_bid -> + (* Lookup the info *) + V.BorrowId.Map.find repr_bid !borrows_infos + | None -> + let err = + "find_info: could not find the representant of borrow " + ^ V.BorrowId.to_string bid ^ ":\nContext:\n" ^ context_to_string () + in + log#serror err; + failwith err + in + + let update_info (bid : V.BorrowId.id) (info : borrow_info) : unit = + (* Find the representant *) + let repr_bid = V.BorrowId.Map.find bid !ids_reprs in + (* Update the info *) + let infos = + V.BorrowId.Map.update repr_bid + (fun x -> + match x with Some _ -> Some info | None -> failwith "Unreachable") + !borrows_infos + in + borrows_infos := infos + in + + let register_ignored_borrow = register_ignored_loan in + + let register_borrow (kind : borrow_kind) (bid : V.BorrowId.id) : unit = + (* Lookup the info *) + let info = find_info bid in + (* Check that the borrow kind is consistent *) + (match (info.loan_kind, kind) with + | T.Shared, (Shared | Inactivated) | T.Mut, Mut -> () + | _ -> failwith "Invariant not satisfied"); + (* An inactivated borrow can't point to a value inside an abstraction *) + assert (kind <> Inactivated || not info.loan_in_abs); + (* Insert the borrow id *) + let borrow_ids = info.borrow_ids in + assert (not (V.BorrowId.Set.mem bid borrow_ids)); + let info = { info with borrow_ids = V.BorrowId.Set.add bid borrow_ids } in + (* Update the info in the map *) + update_info bid info + in + + let borrows_visitor = + object + inherit [_] C.iter_eval_ctx as super + + method! visit_abstract_shared_borrows _ asb = + let visit asb = + match asb with + | V.AsbBorrow bid -> register_borrow Shared bid + | V.AsbProjReborrows _ -> () + in + List.iter visit asb + + method! visit_borrow_content env bc = + (* Register the loan *) + let _ = + match bc with + | V.SharedBorrow (_, bid) -> register_borrow Shared bid + | V.MutBorrow (bid, _) -> register_borrow Mut bid + | V.InactivatedMutBorrow (_, bid) -> register_borrow Inactivated bid + in + (* Continue exploring *) + super#visit_borrow_content env bc + + method! visit_aborrow_content env bc = + let _ = + match bc with + | V.AMutBorrow (_, bid, _) -> register_borrow Mut bid + | V.ASharedBorrow bid -> register_borrow Shared bid + | V.AIgnoredMutBorrow (Some bid, _) -> register_ignored_borrow Mut bid + | V.AIgnoredMutBorrow (None, _) + | V.AEndedMutBorrow _ | V.AEndedIgnoredMutBorrow _ + | V.AEndedSharedBorrow | V.AProjSharedBorrow _ -> + (* Do nothing *) + () + in + (* Continue exploring *) + super#visit_aborrow_content env bc + end + in + + (* Visit *) + borrows_visitor#visit_eval_ctx () ctx; + + (* Debugging *) + log#ldebug + (lazy ("\nAbout to check context invariant:\n" ^ context_to_string ())); + + (* Finally, check that everything is consistant *) + (* First, check all the ignored loans are present at the proper place *) + List.iter + (fun (rkind, bid) -> + let info = find_info bid in + assert (info.loan_kind = rkind)) + !ignored_loans; + + (* Then, check the borrow infos *) + V.BorrowId.Map.iter + (fun _ info -> + (* Note that we can't directly compare the sets - I guess they are + * different depending on the order in which we add the elements... *) + assert ( + V.BorrowId.Set.elements info.loan_ids + = V.BorrowId.Set.elements info.borrow_ids); + match info.loan_kind with + | T.Mut -> assert (V.BorrowId.Set.cardinal info.loan_ids = 1) + | T.Shared -> ()) + !borrows_infos + +(** Check that: + - borrows/loans can't contain ⊥ or inactivated mut borrows + - shared loans can't contain mutable loans + *) +let check_borrowed_values_invariant (config : C.config) (ctx : C.eval_ctx) : + unit = + let visitor = + object + inherit [_] C.iter_eval_ctx as super + + method! visit_Bottom info = + (* No ⊥ inside borrowed values *) + assert (config.C.allow_bottom_below_borrow || not info.outer_borrow) + + method! visit_ABottom _info = + (* ⊥ inside an abstraction is not the same as in a regular value *) + () + + method! visit_loan_content info lc = + (* Update the info *) + let info = + match lc with + | V.SharedLoan (_, _) -> set_outer_shared info + | V.MutLoan _ -> + (* No mutable loan inside a shared loan *) + assert (not info.outer_shared); + set_outer_mut info + in + (* Continue exploring *) + super#visit_loan_content info lc + + method! visit_borrow_content info bc = + (* Update the info *) + let info = + match bc with + | V.SharedBorrow _ -> set_outer_shared info + | V.InactivatedMutBorrow _ -> + assert (not info.outer_borrow); + set_outer_shared info + | V.MutBorrow (_, _) -> set_outer_mut info + in + (* Continue exploring *) + super#visit_borrow_content info bc + + method! visit_aloan_content info lc = + (* Update the info *) + let info = + match lc with + | V.AMutLoan (_, _) -> set_outer_mut info + | V.ASharedLoan (_, _, _) -> set_outer_shared info + | V.AEndedMutLoan { given_back = _; child = _; given_back_meta = _ } + -> + set_outer_mut info + | V.AEndedSharedLoan (_, _) -> set_outer_shared info + | V.AIgnoredMutLoan (_, _) -> set_outer_mut info + | V.AEndedIgnoredMutLoan + { given_back = _; child = _; given_back_meta = _ } -> + set_outer_mut info + | V.AIgnoredSharedLoan _ -> set_outer_shared info + in + (* Continue exploring *) + super#visit_aloan_content info lc + + method! visit_aborrow_content info bc = + (* Update the info *) + let info = + match bc with + | V.AMutBorrow (_, _, _) -> set_outer_mut info + | V.ASharedBorrow _ | V.AEndedSharedBorrow -> set_outer_shared info + | V.AIgnoredMutBorrow _ | V.AEndedMutBorrow _ + | V.AEndedIgnoredMutBorrow _ -> + set_outer_mut info + | V.AProjSharedBorrow _ -> set_outer_shared info + in + (* Continue exploring *) + super#visit_aborrow_content info bc + end + in + + (* Explore *) + let info = { outer_borrow = false; outer_shared = false } in + visitor#visit_eval_ctx info ctx + +let check_constant_value_type (cv : V.constant_value) (ty : T.ety) : unit = + match (cv, ty) with + | V.Scalar sv, T.Integer int_ty -> assert (sv.int_ty = int_ty) + | V.Bool _, T.Bool | V.Char _, T.Char | V.String _, T.Str -> () + | _ -> failwith "Erroneous typing" + +let check_typing_invariant (ctx : C.eval_ctx) : unit = + (* TODO: the type of aloans doens't make sense: they have a type + * of the shape [& (mut) T] where they should have type [T]... + * This messes a bit the type invariant checks when checking the + * children. In order to isolate the problem (for future modifications) + * we introduce function, so that we can easily spot all the involved + * places. + * *) + let aloan_get_expected_child_type (ty : 'r T.ty) : 'r T.ty = + let _, ty, _ = ty_get_ref ty in + ty + in + + let visitor = + object + inherit [_] C.iter_eval_ctx as super + method! visit_abs _ abs = super#visit_abs (Some abs) abs + + method! visit_typed_value info tv = + (* Check the current pair (value, type) *) + (match (tv.V.value, tv.V.ty) with + | V.Concrete cv, ty -> check_constant_value_type cv ty + (* ADT case *) + | V.Adt av, T.Adt (T.AdtId def_id, regions, tys) -> + (* Retrieve the definition to check the variant id, the number of + * parameters, etc. *) + let def = C.ctx_lookup_type_decl ctx def_id in + (* Check the number of parameters *) + assert (List.length regions = List.length def.region_params); + assert (List.length tys = List.length def.type_params); + (* Check that the variant id is consistent *) + (match (av.V.variant_id, def.T.kind) with + | Some variant_id, T.Enum variants -> + assert (T.VariantId.to_int variant_id < List.length variants) + | None, T.Struct _ -> () + | _ -> failwith "Erroneous typing"); + (* Check that the field types are correct *) + let field_types = + Subst.type_decl_get_instantiated_field_etypes def av.V.variant_id + tys + in + let fields_with_types = + List.combine av.V.field_values field_types + in + List.iter + (fun ((v, ty) : V.typed_value * T.ety) -> assert (v.V.ty = ty)) + fields_with_types + (* Tuple case *) + | V.Adt av, T.Adt (T.Tuple, regions, tys) -> + assert (regions = []); + assert (av.V.variant_id = None); + (* Check that the fields have the proper values - and check that there + * are as many fields as field types at the same time *) + let fields_with_types = List.combine av.V.field_values tys in + List.iter + (fun ((v, ty) : V.typed_value * T.ety) -> assert (v.V.ty = ty)) + fields_with_types + (* Assumed type case *) + | V.Adt av, T.Adt (T.Assumed aty_id, regions, tys) -> ( + assert (av.V.variant_id = None || aty_id = T.Option); + match (aty_id, av.V.field_values, regions, tys) with + (* Box *) + | T.Box, [ inner_value ], [], [ inner_ty ] + | T.Option, [ inner_value ], [], [ inner_ty ] -> + assert (inner_value.V.ty = inner_ty) + | T.Option, _, [], [ _ ] -> + (* Option::None: nothing to check *) + () + | T.Vec, fvs, [], [ vec_ty ] -> + List.iter + (fun (v : V.typed_value) -> assert (v.ty = vec_ty)) + fvs + | _ -> failwith "Erroneous type") + | V.Bottom, _ -> (* Nothing to check *) () + | V.Borrow bc, T.Ref (_, ref_ty, rkind) -> ( + match (bc, rkind) with + | V.SharedBorrow (_, bid), T.Shared + | V.InactivatedMutBorrow (_, bid), T.Mut -> ( + (* Lookup the borrowed value to check it has the proper type *) + let _, glc = lookup_loan ek_all bid ctx in + match glc with + | Concrete (V.SharedLoan (_, sv)) + | Abstract (V.ASharedLoan (_, sv, _)) -> + assert (sv.V.ty = ref_ty) + | _ -> failwith "Inconsistent context") + | V.MutBorrow (_, bv), T.Mut -> + assert ( + (* Check that the borrowed value has the proper type *) + bv.V.ty = ref_ty) + | _ -> failwith "Erroneous typing") + | V.Loan lc, ty -> ( + match lc with + | V.SharedLoan (_, sv) -> assert (sv.V.ty = ty) + | V.MutLoan bid -> ( + (* Lookup the borrowed value to check it has the proper type *) + let glc = lookup_borrow ek_all bid ctx in + match glc with + | Concrete (V.MutBorrow (_, bv)) -> assert (bv.V.ty = ty) + | Abstract (V.AMutBorrow (_, _, sv)) -> + assert (Subst.erase_regions sv.V.ty = ty) + | _ -> failwith "Inconsistent context")) + | V.Symbolic sv, ty -> + let ty' = Subst.erase_regions sv.V.sv_ty in + assert (ty' = ty) + | _ -> failwith "Erroneous typing"); + (* Continue exploring to inspect the subterms *) + super#visit_typed_value info tv + + (* TODO: there is a lot of duplication with {!visit_typed_value} + * which is quite annoying. There might be a way of factorizing + * that by factorizing the definitions of value and avalue, but + * the generation of visitors then doesn't work properly (TODO: + * report that). Still, it is actually not that problematic + * because this code shouldn't change a lot in the future, + * so the cost of maintenance should be pretty low. + * *) + method! visit_typed_avalue info atv = + (* Check the current pair (value, type) *) + (match (atv.V.value, atv.V.ty) with + | V.AConcrete cv, ty -> + check_constant_value_type cv (Subst.erase_regions ty) + (* ADT case *) + | V.AAdt av, T.Adt (T.AdtId def_id, regions, tys) -> + (* Retrieve the definition to check the variant id, the number of + * parameters, etc. *) + let def = C.ctx_lookup_type_decl ctx def_id in + (* Check the number of parameters *) + assert (List.length regions = List.length def.region_params); + assert (List.length tys = List.length def.type_params); + (* Check that the variant id is consistent *) + (match (av.V.variant_id, def.T.kind) with + | Some variant_id, T.Enum variants -> + assert (T.VariantId.to_int variant_id < List.length variants) + | None, T.Struct _ -> () + | _ -> failwith "Erroneous typing"); + (* Check that the field types are correct *) + let field_types = + Subst.type_decl_get_instantiated_field_rtypes def av.V.variant_id + regions tys + in + let fields_with_types = + List.combine av.V.field_values field_types + in + List.iter + (fun ((v, ty) : V.typed_avalue * T.rty) -> assert (v.V.ty = ty)) + fields_with_types + (* Tuple case *) + | V.AAdt av, T.Adt (T.Tuple, regions, tys) -> + assert (regions = []); + assert (av.V.variant_id = None); + (* Check that the fields have the proper values - and check that there + * are as many fields as field types at the same time *) + let fields_with_types = List.combine av.V.field_values tys in + List.iter + (fun ((v, ty) : V.typed_avalue * T.rty) -> assert (v.V.ty = ty)) + fields_with_types + (* Assumed type case *) + | V.AAdt av, T.Adt (T.Assumed aty_id, regions, tys) -> ( + assert (av.V.variant_id = None); + match (aty_id, av.V.field_values, regions, tys) with + (* Box *) + | T.Box, [ boxed_value ], [], [ boxed_ty ] -> + assert (boxed_value.V.ty = boxed_ty) + | _ -> failwith "Erroneous type") + | V.ABottom, _ -> (* Nothing to check *) () + | V.ABorrow bc, T.Ref (_, ref_ty, rkind) -> ( + match (bc, rkind) with + | V.AMutBorrow (_, _, av), T.Mut -> + (* Check that the child value has the proper type *) + assert (av.V.ty = ref_ty) + | V.ASharedBorrow bid, T.Shared -> ( + (* Lookup the borrowed value to check it has the proper type *) + let _, glc = lookup_loan ek_all bid ctx in + match glc with + | Concrete (V.SharedLoan (_, sv)) + | Abstract (V.ASharedLoan (_, sv, _)) -> + assert (sv.V.ty = Subst.erase_regions ref_ty) + | _ -> failwith "Inconsistent context") + | V.AIgnoredMutBorrow (_opt_bid, av), T.Mut -> + assert (av.V.ty = ref_ty) + | ( V.AEndedIgnoredMutBorrow + { given_back_loans_proj; child; given_back_meta = _ }, + T.Mut ) -> + assert (given_back_loans_proj.V.ty = ref_ty); + assert (child.V.ty = ref_ty) + | V.AProjSharedBorrow _, T.Shared -> () + | _ -> failwith "Inconsistent context") + | V.ALoan lc, aty -> ( + match lc with + | V.AMutLoan (bid, child_av) | V.AIgnoredMutLoan (bid, child_av) + -> ( + let borrowed_aty = aloan_get_expected_child_type aty in + assert (child_av.V.ty = borrowed_aty); + (* Lookup the borrowed value to check it has the proper type *) + let glc = lookup_borrow ek_all bid ctx in + match glc with + | Concrete (V.MutBorrow (_, bv)) -> + assert (bv.V.ty = Subst.erase_regions borrowed_aty) + | Abstract (V.AMutBorrow (_, _, sv)) -> + assert ( + Subst.erase_regions sv.V.ty + = Subst.erase_regions borrowed_aty) + | _ -> failwith "Inconsistent context") + | V.ASharedLoan (_, sv, child_av) | V.AEndedSharedLoan (sv, child_av) + -> + let borrowed_aty = aloan_get_expected_child_type aty in + assert (sv.V.ty = Subst.erase_regions borrowed_aty); + (* TODO: the type of aloans doesn't make sense, see above *) + assert (child_av.V.ty = borrowed_aty) + | V.AEndedMutLoan { given_back; child; given_back_meta = _ } + | V.AEndedIgnoredMutLoan { given_back; child; given_back_meta = _ } + -> + let borrowed_aty = aloan_get_expected_child_type aty in + assert (given_back.V.ty = borrowed_aty); + assert (child.V.ty = borrowed_aty) + | V.AIgnoredSharedLoan child_av -> + assert (child_av.V.ty = aloan_get_expected_child_type aty)) + | V.ASymbolic aproj, ty -> ( + let ty1 = Subst.erase_regions ty in + match aproj with + | V.AProjLoans (sv, _) -> + let ty2 = Subst.erase_regions sv.V.sv_ty in + assert (ty1 = ty2); + (* Also check that the symbolic values contain regions of interest - + * otherwise they should have been reduced to [_] *) + let abs = Option.get info in + assert (ty_has_regions_in_set abs.regions sv.V.sv_ty) + | V.AProjBorrows (sv, proj_ty) -> + let ty2 = Subst.erase_regions sv.V.sv_ty in + assert (ty1 = ty2); + (* Also check that the symbolic values contain regions of interest - + * otherwise they should have been reduced to [_] *) + let abs = Option.get info in + assert (ty_has_regions_in_set abs.regions proj_ty) + | V.AEndedProjLoans (_msv, given_back_ls) -> + List.iter + (fun (_, proj) -> + match proj with + | V.AProjBorrows (_sv, ty') -> assert (ty' = ty) + | V.AEndedProjBorrows _ | V.AIgnoredProjBorrows -> () + | _ -> failwith "Unexpected") + given_back_ls + | V.AEndedProjBorrows _ | V.AIgnoredProjBorrows -> ()) + | V.AIgnored, _ -> () + | _ -> failwith "Erroneous typing"); + (* Continue exploring to inspect the subterms *) + super#visit_typed_avalue info atv + end + in + visitor#visit_eval_ctx (None : V.abs option) ctx + +type proj_borrows_info = { + abs_id : V.AbstractionId.id; + regions : T.RegionId.Set.t; + proj_ty : T.rty; + as_shared_value : bool; (** True if the value is below a shared borrow *) +} +[@@deriving show] + +type proj_loans_info = { + abs_id : V.AbstractionId.id; + regions : T.RegionId.Set.t; +} +[@@deriving show] + +type sv_info = { + ty : T.rty; + env_count : int; + aproj_borrows : proj_borrows_info list; + aproj_loans : proj_loans_info list; +} +[@@deriving show] + +(** Check the invariants over the symbolic values. + + - a symbolic value can't be both in proj_borrows and in the concrete env + (this is why we preemptively expand copyable symbolic values) + - if a symbolic value contains regions: there is at most one occurrence + of this value in the concrete env + - if there is an aproj_borrows in the environment, there must also be a + corresponding aproj_loans + - aproj_loans are mutually disjoint + - TODO: aproj_borrows are mutually disjoint + - the union of the aproj_loans contains the aproj_borrows applied on the + same symbolic values + *) +let check_symbolic_values (_config : C.config) (ctx : C.eval_ctx) : unit = + (* Small utility *) + let module M = V.SymbolicValueId.Map in + let infos : sv_info M.t ref = ref M.empty in + let lookup_info (sv : V.symbolic_value) : sv_info = + match M.find_opt sv.V.sv_id !infos with + | Some info -> info + | None -> + { ty = sv.sv_ty; env_count = 0; aproj_borrows = []; aproj_loans = [] } + in + let update_info (sv : V.symbolic_value) (info : sv_info) = + infos := M.add sv.sv_id info !infos + in + let add_env_sv (sv : V.symbolic_value) : unit = + let info = lookup_info sv in + let info = { info with env_count = info.env_count + 1 } in + update_info sv info + in + let add_aproj_borrows (sv : V.symbolic_value) abs_id regions proj_ty + as_shared_value : unit = + let info = lookup_info sv in + let binfo = { abs_id; regions; proj_ty; as_shared_value } in + let info = { info with aproj_borrows = binfo :: info.aproj_borrows } in + update_info sv info + in + let add_aproj_loans (sv : V.symbolic_value) abs_id regions : unit = + let info = lookup_info sv in + let linfo = { abs_id; regions } in + let info = { info with aproj_loans = linfo :: info.aproj_loans } in + update_info sv info + in + (* Visitor *) + let obj = + object + inherit [_] C.iter_eval_ctx as super + method! visit_abs _ abs = super#visit_abs (Some abs) abs + method! visit_Symbolic _ sv = add_env_sv sv + + method! visit_abstract_shared_borrows abs asb = + let abs = Option.get abs in + let visit asb = + match asb with + | V.AsbBorrow _ -> () + | AsbProjReborrows (sv, proj_ty) -> + add_aproj_borrows sv abs.abs_id abs.regions proj_ty true + in + List.iter visit asb + + method! visit_aproj abs aproj = + (let abs = Option.get abs in + match aproj with + | AProjLoans (sv, _) -> add_aproj_loans sv abs.abs_id abs.regions + | AProjBorrows (sv, proj_ty) -> + add_aproj_borrows sv abs.abs_id abs.regions proj_ty false + | AEndedProjLoans _ | AEndedProjBorrows _ | AIgnoredProjBorrows -> ()); + super#visit_aproj abs aproj + end + in + (* Collect the information *) + obj#visit_eval_ctx None ctx; + log#ldebug + (lazy + ("check_symbolic_values: collected information:\n" + ^ V.SymbolicValueId.Map.to_string (Some " ") show_sv_info !infos)); + (* Check *) + let check_info _id info = + (* TODO: check that: + * - the borrows are mutually disjoint + *) + (* A symbolic value can't be both in the regular environment and inside + * projectors of borrows in abstractions *) + assert (info.env_count = 0 || info.aproj_borrows = []); + (* A symbolic value containing borrows can't be duplicated (i.e., copied): + * it must be expanded first *) + if ty_has_borrows ctx.type_context.type_infos info.ty then + assert (info.env_count <= 1); + (* A duplicated symbolic value is necessarily primitively copyable *) + assert (info.env_count <= 1 || ty_is_primitively_copyable info.ty); + + assert (info.aproj_borrows = [] || info.aproj_loans <> []); + (* At the same time: + * - check that the loans don't intersect + * - compute the set of regions for which we project loans + *) + (* Check that the loan projectors contain the region projectors *) + let loan_regions = + List.fold_left + (fun regions linfo -> + let regions = + T.RegionId.Set.fold + (fun rid regions -> + assert (not (T.RegionId.Set.mem rid regions)); + T.RegionId.Set.add rid regions) + regions linfo.regions + in + regions) + T.RegionId.Set.empty info.aproj_loans + in + (* Check that the union of the loan projectors contains the borrow projections. *) + List.iter + (fun binfo -> + assert ( + projection_contains info.ty loan_regions binfo.proj_ty binfo.regions)) + info.aproj_borrows; + () + in + + M.iter check_info !infos + +let check_invariants (config : C.config) (ctx : C.eval_ctx) : unit = + if config.C.check_invariants then ( + log#ldebug (lazy "Checking invariants"); + check_loans_borrows_relation_invariant ctx; + check_borrowed_values_invariant config ctx; + check_typing_invariant ctx; + check_symbolic_values config ctx) + else log#ldebug (lazy "Not checking invariants (check is not activated)") + +(** Same as {!check_invariants}, but written in CPS *) +let cf_check_invariants (config : C.config) : cm_fun = + fun cf ctx -> + check_invariants config ctx; + cf ctx |