[pulse] |- is now true only of isomorphic graphs

Summary:
Previously we would say that `lhs <= rhs` (or `lhs |- rhs`) when a
mapping existed between the abstract addresses of `lhs` and `rhs` such
that `mapping(lhs)` was a supergraph of `rhs`. In particular,
we had that `x |-> x' * x' |-> x'' |- x |-> x'`. This is not entirely
great, in particular once we get pairs of state representing footprint +
current state. I'm not sure I have an extremely compelling argument why
though, except that it's not the usual way we do implication in SL, but
there wasn't a compelling argument for the previous state of affairs
either.

This changes `|-` to be true only when `mapping(lhs) = rhs` (modulo only
considering the addresses reachable from the stack variables).

Reviewed By: jberdine

Differential Revision: D14568272

fbshipit-source-id: 1bb83950e
master
Jules Villard 6 years ago committed by Facebook Github Bot
parent 3126c4f5c2
commit 4cdb65c237

@ -83,7 +83,10 @@ module MakeHILDisjunctive (TransferFunctions : HILDisjReady) (DConfig : Disjunct
let elements disjuncts = List.map disjuncts ~f:(fun {astate} -> astate) let elements disjuncts = List.map disjuncts ~f:(fun {astate} -> astate)
let pp f disjuncts = PrettyPrintable.pp_collection ~pp_item:pp_disjunct f disjuncts let pp f disjuncts =
F.fprintf f "@[<v>%d disjuncts:@;%a@]" (List.length disjuncts)
(PrettyPrintable.pp_collection ~pp_item:pp_disjunct)
disjuncts
end end
type disjunct_t = Disjuncts.disjunct = {visited: bool; astate: TransferFunctions.Domain.t} type disjunct_t = Disjuncts.disjunct = {visited: bool; astate: TransferFunctions.Domain.t}
@ -91,15 +94,20 @@ module MakeHILDisjunctive (TransferFunctions : HILDisjReady) (DConfig : Disjunct
module Domain = struct module Domain = struct
type t = Disjuncts.t type t = Disjuncts.t
let join_normalize_into s_froms ~into:s_intos = let rev_filter_not_over_approximated disjuncts ~not_in =
(* ignore states in [s_froms] that are under-approximated in [s_intos] *) List.rev_filter disjuncts ~f:(fun disjunct ->
let s_from_not_in_intos = List.exists not_in ~f:(fun disj_not_in ->
List.rev_filter s_froms ~f:(fun s_from -> TransferFunctions.Domain.( <= ) ~lhs:disjunct.astate ~rhs:disj_not_in.astate )
List.exists s_intos ~f:(fun s_into ->
TransferFunctions.Domain.( <= ) ~rhs:s_from.astate ~lhs:s_into.astate )
|> not ) |> not )
in
List.rev_append s_from_not_in_intos s_intos
(* Ignore states in [lhs] that are over-approximated in [rhs] and vice-versa. Favors keeping
states in [lhs]. *)
let join_up_to_imply lhs rhs =
let rev_rhs_not_in_lhs = rev_filter_not_over_approximated rhs ~not_in:lhs in
(* cheeky: this is only used in pulse, whose (<=) is actually a symmetric relation so there's
no need to filter out elements of [lhs] *)
List.rev_append rev_rhs_not_in_lhs lhs
let join : t -> t -> t = let join : t -> t -> t =
@ -108,14 +116,10 @@ module MakeHILDisjunctive (TransferFunctions : HILDisjReady) (DConfig : Disjunct
else else
match DConfig.join_policy with match DConfig.join_policy with
| `NeverJoin -> | `NeverJoin ->
rhs @ lhs List.rev_append rhs lhs
| `UnderApproximateAfter n -> | `UnderApproximateAfter n ->
let lhs_length = List.length lhs in let lhs_length = List.length lhs in
if lhs_length >= n then lhs if lhs_length >= n then lhs else List.rev_append rhs lhs
else
(* do not add states from [rhs] (assumed to be the *new* states in the case of a loop)
that are already implied by some state in the [lhs] *)
join_normalize_into rhs ~into:lhs
let set_visited b disjuncts = let set_visited b disjuncts =
@ -123,20 +127,29 @@ module MakeHILDisjunctive (TransferFunctions : HILDisjReady) (DConfig : Disjunct
else List.map disjuncts ~f:(fun disjunct -> {disjunct with visited= b}) else List.map disjuncts ~f:(fun disjunct -> {disjunct with visited= b})
let rec ( <= ) ~lhs ~rhs = (** check if elements of [disj] appear in [of_] in the same order, using pointer equality on
if phys_equal lhs rhs then (* also takes care of the case [lhs = rhs = []] *) true abstract states to compare elements quickly *)
else let rec is_trivial_subset disj ~of_ =
(* quick check: [lhs <= rhs] if [rhs] is [something @ lhs] *) match (disj, of_) with
match rhs with [] -> false | _ :: rhs' -> ( <= ) ~lhs ~rhs:rhs' | [], _ ->
true
| x :: disj', y :: of' when phys_equal x.astate y.astate ->
is_trivial_subset disj' ~of_:of'
| _, _ :: of' ->
is_trivial_subset disj ~of_:of'
| _, [] ->
false
let ( <= ) ~lhs ~rhs = phys_equal lhs rhs || is_trivial_subset lhs ~of_:rhs
let widen ~prev ~next ~num_iters = let widen ~prev ~next ~num_iters =
let (`UnderApproximateAfterNumIterations max_iter) = DConfig.widen_policy in let (`UnderApproximateAfterNumIterations max_iter) = DConfig.widen_policy in
if phys_equal prev next || num_iters > max_iter then set_visited false prev if phys_equal prev next || num_iters > max_iter then set_visited false prev
else else
let prev = set_visited true prev in let visited_prev = set_visited true prev in
let post = join prev next in let post = join_up_to_imply visited_prev next in
if ( <= ) ~lhs:post ~rhs:prev then set_visited false post else post if ( <= ) ~lhs:post ~rhs:prev then set_visited false prev else post
let pp = Disjuncts.pp let pp = Disjuncts.pp
@ -145,9 +158,12 @@ module MakeHILDisjunctive (TransferFunctions : HILDisjReady) (DConfig : Disjunct
let exec_instr pre_disjuncts extras node instr = let exec_instr pre_disjuncts extras node instr =
List.fold pre_disjuncts ~init:[] ~f:(fun post_disjuncts pre_disjunct -> List.fold pre_disjuncts ~init:[] ~f:(fun post_disjuncts pre_disjunct ->
if pre_disjunct.visited then if pre_disjunct.visited then
(* SUBTLE: ignore pres that we know we have gone through already. This means that the (* SUBTLE/WORST HACK EVER: ignore pres that we know we have gone through already. This
invariant map at that program point will be junk since they are going to miss some means that the invariant map at that program point will be junk since they are going to
states, but the overall result will still be ok because the loop heads are ok. *) miss some states, but the overall result will still be ok because the loop heads are
ok.
This should really be implemented in {!AbstractInterpreter}. *)
post_disjuncts post_disjuncts
else else
let disjuncts' = TransferFunctions.exec_instr pre_disjunct.astate extras node instr in let disjuncts' = TransferFunctions.exec_instr pre_disjunct.astate extras node instr in

@ -283,9 +283,9 @@ let initial =
(** comparison between two elements of the domain to determine the [<=] relation (** comparison between two elements of the domain to determine the [<=] relation
Given two states [lhs] and [rhs], try to find a bijection [lhs_to_rhs]/[rhs_to_lhs] between the Given two states [lhs] and [rhs], try to find a bijection [lhs_to_rhs] (with inverse
addresses of [lhs] and [rhs] such that [lhs_to_rhs(lhs)] contains (is a supergraph of) [rhs]. [rhs_to_lhs]) between the addresses of [lhs] and [rhs] such that [lhs_to_rhs(reachable(lhs)) =
*) reachable(rhs)] (where addresses are reachable if they are reachable from stack variables). *)
module GraphComparison = struct module GraphComparison = struct
module AddressMap = PrettyPrintable.MakePPMap (AbstractAddress) module AddressMap = PrettyPrintable.MakePPMap (AbstractAddress)
@ -339,101 +339,95 @@ module GraphComparison = struct
`NotAlreadyVisited mapping' ) `NotAlreadyVisited mapping' )
type supergraph_relation = type isograph_relation =
| NotASupergraph (** no mapping was found that can make LHS bigger than RHS *) | NotIsomorphic (** no mapping was found that can make LHS the same as the RHS *)
| Supergraph of mapping (** [mapping(lhs)] is a supergraph of [rhs] *) | IsomorphicUpTo of mapping (** [mapping(lhs)] is isomorphic to [rhs] *)
(** can we extend [mapping] so that the subgraph of [lhs] rooted at [addr_lhs] is a supergraph of (** can we extend [mapping] so that the subgraph of [lhs] rooted at [addr_lhs] is isomorphic to
the subgraph of [rhs] rooted at [addr_rhs]? *) the subgraph of [rhs] rooted at [addr_rhs]? *)
let rec supergraph_map_from_address ~lhs ~addr_lhs ~rhs ~addr_rhs mapping = let rec isograph_map_from_address ~lhs ~addr_lhs ~rhs ~addr_rhs mapping =
L.d_printfln "%a<->%a@\n" AbstractAddress.pp addr_lhs AbstractAddress.pp addr_rhs ; L.d_printfln "%a<->%a@\n" AbstractAddress.pp addr_lhs AbstractAddress.pp addr_rhs ;
match record_equal mapping ~addr_lhs ~addr_rhs with match record_equal mapping ~addr_lhs ~addr_rhs with
| `AlreadyVisited -> | `AlreadyVisited ->
Supergraph mapping IsomorphicUpTo mapping
| `AliasingRHS | `AliasingLHS -> | `AliasingRHS | `AliasingLHS ->
NotASupergraph NotIsomorphic
| `NotAlreadyVisited mapping -> ( | `NotAlreadyVisited mapping -> (
match Memory.find_opt addr_rhs rhs.heap with let get_non_empty_cell = function
| None -> | None ->
(* nothing on the RHS: nothing more to check *) None
Supergraph mapping | Some (edges, attrs) when Memory.Edges.is_empty edges && Attributes.is_empty attrs ->
| Some (edges_rhs, attrs_rhs) -> ( (* this can happen because of [register_address] or because we don't care to delete empty
match Memory.find_opt addr_lhs lhs.heap with edges when removing edges *)
| None -> None
(* [RHS] is not a subgraph of [LHS] unless [addr_rhs] has no attributes and no edges | Some _ as some_cell ->
(could happen because of [register_address] or because we don't care to delete empty some_cell
edges when removing edges) *) in
if Memory.Edges.is_empty edges_rhs && Attributes.is_empty attrs_rhs then let lhs_cell_opt = Memory.find_opt addr_lhs lhs.heap |> get_non_empty_cell in
Supergraph mapping let rhs_cell_opt = Memory.find_opt addr_rhs rhs.heap |> get_non_empty_cell in
else NotASupergraph match (lhs_cell_opt, rhs_cell_opt) with
| Some (edges_lhs, attrs_lhs) -> | None, None ->
IsomorphicUpTo mapping
| Some _, None | None, Some _ ->
NotIsomorphic
| Some (edges_rhs, attrs_rhs), Some (edges_lhs, attrs_lhs) ->
(* continue the comparison recursively on all edges and attributes *) (* continue the comparison recursively on all edges and attributes *)
if Attributes.subset attrs_rhs attrs_lhs then if Attributes.equal attrs_rhs attrs_lhs then
let bindings_lhs = Memory.Edges.bindings edges_lhs in let bindings_lhs = Memory.Edges.bindings edges_lhs in
let bindings_rhs = Memory.Edges.bindings edges_rhs in let bindings_rhs = Memory.Edges.bindings edges_rhs in
supergraph_map_edges ~lhs ~edges_lhs:bindings_lhs ~rhs ~edges_rhs:bindings_rhs isograph_map_edges ~lhs ~edges_lhs:bindings_lhs ~rhs ~edges_rhs:bindings_rhs mapping
mapping else NotIsomorphic )
else NotASupergraph ) )
(** check that the supergraph relation can be extended for all edges *) (** check that the isograph relation can be extended for all edges *)
and supergraph_map_edges ~lhs ~edges_lhs ~rhs ~edges_rhs mapping = and isograph_map_edges ~lhs ~edges_lhs ~rhs ~edges_rhs mapping =
match (edges_lhs, edges_rhs) with match (edges_lhs, edges_rhs) with
| _, [] -> | [], [] ->
(* more edges on the LHS => supergraph *) IsomorphicUpTo mapping
Supergraph mapping
| (a_lhs, (addr_lhs, _trace_lhs)) :: edges_lhs, (a_rhs, (addr_rhs, _trace_rhs)) :: edges_rhs | (a_lhs, (addr_lhs, _trace_lhs)) :: edges_lhs, (a_rhs, (addr_rhs, _trace_rhs)) :: edges_rhs
when Memory.Access.equal a_lhs a_rhs -> ( when Memory.Access.equal a_lhs a_rhs -> (
(* check supergraph relation from the destination addresses *) (* check isograph relation from the destination addresses *)
match supergraph_map_from_address ~lhs ~addr_lhs ~rhs ~addr_rhs mapping with match isograph_map_from_address ~lhs ~addr_lhs ~rhs ~addr_rhs mapping with
| Supergraph mapping -> | IsomorphicUpTo mapping ->
(* ok: continue with the other edges *) (* ok: continue with the other edges *)
supergraph_map_edges ~lhs ~edges_lhs ~rhs ~edges_rhs mapping isograph_map_edges ~lhs ~edges_lhs ~rhs ~edges_rhs mapping
| NotASupergraph -> | NotIsomorphic ->
NotASupergraph ) NotIsomorphic )
| (a_lhs, _) :: edges_lhs, (a_rhs, _) :: _ when Memory.Access.compare a_lhs a_rhs < 0 -> | _ :: _, _ :: _ | [], _ :: _ | _ :: _, [] ->
(* [a_lhs] is not present on the RHS: that's ok: the LHS knows more than the RHS *) NotIsomorphic
supergraph_map_edges ~lhs ~edges_lhs ~rhs ~edges_rhs mapping
| _, _ :: _ ->
(* [a_rhs] is not present on the LHS: that's not ok: the RHS knows more than the LHS *)
NotASupergraph
(** check that the memory graph induced by the addresses in [lhs] reachable from the variables in (** check that the memory graph induced by the addresses in [lhs] reachable from the variables in
[stack_lhs] is a supergraph of the same graph in [rhs] starting from [stack_rhs], up to some [stack_lhs] is a isograph of the same graph in [rhs] starting from [stack_rhs], up to some
[mapping] *) [mapping] *)
let rec supergraph_map_from_stack ~lhs ~stack_lhs ~rhs ~stack_rhs mapping = let rec isograph_map_from_stack ~lhs ~stack_lhs ~rhs ~stack_rhs mapping =
match (stack_lhs, stack_rhs) with match (stack_lhs, stack_rhs) with
| _, [] -> | [], [] ->
Supergraph mapping IsomorphicUpTo mapping
| ( (var_lhs, (addr_lhs, _trace_lhs)) :: stack_lhs | ( (var_lhs, (addr_lhs, _trace_lhs)) :: stack_lhs
, (var_rhs, (addr_rhs, _trace_rhs)) :: stack_rhs ) , (var_rhs, (addr_rhs, _trace_rhs)) :: stack_rhs )
when Var.equal var_lhs var_rhs -> ( when Var.equal var_lhs var_rhs -> (
match supergraph_map_from_address ~lhs ~addr_lhs ~rhs ~addr_rhs mapping with match isograph_map_from_address ~lhs ~addr_lhs ~rhs ~addr_rhs mapping with
| Supergraph mapping -> | IsomorphicUpTo mapping ->
supergraph_map_from_stack ~lhs ~stack_lhs ~rhs ~stack_rhs mapping isograph_map_from_stack ~lhs ~stack_lhs ~rhs ~stack_rhs mapping
| NotASupergraph -> | NotIsomorphic ->
NotASupergraph ) NotIsomorphic )
| (var_lhs, _) :: stack_lhs, (var_rhs, _) :: _ when Var.compare var_lhs var_rhs < 0 -> | _ :: _, _ :: _ | [], _ :: _ | _ :: _, [] ->
(* [var_lhs] is not present on the RHS: that's ok: the LHS knows more than the RHS *) NotIsomorphic
supergraph_map_from_stack ~lhs ~stack_lhs ~rhs ~stack_rhs mapping
| _, _ :: _ ->
NotASupergraph let isograph_map ~lhs ~rhs mapping =
let supergraph_map ~lhs ~rhs mapping =
let stack_lhs = Stack.bindings lhs.stack in let stack_lhs = Stack.bindings lhs.stack in
let stack_rhs = Stack.bindings rhs.stack in let stack_rhs = Stack.bindings rhs.stack in
supergraph_map_from_stack ~lhs ~rhs ~stack_lhs ~stack_rhs mapping isograph_map_from_stack ~lhs ~rhs ~stack_lhs ~stack_rhs mapping
let is_supergraph ~lhs ~rhs mapping = let is_isograph ~lhs ~rhs mapping =
match supergraph_map ~lhs ~rhs mapping with Supergraph _ -> true | NotASupergraph -> false match isograph_map ~lhs ~rhs mapping with IsomorphicUpTo _ -> true | NotIsomorphic -> false
end end
let ( <= ) ~lhs ~rhs = let ( <= ) ~lhs ~rhs =
(* [lhs] implies [rhs] if it knows more facts than [rhs] *) phys_equal lhs rhs || GraphComparison.is_isograph ~lhs ~rhs GraphComparison.empty_mapping
phys_equal lhs rhs || GraphComparison.is_supergraph ~lhs ~rhs GraphComparison.empty_mapping
let pp fmt {heap; stack} = let pp fmt {heap; stack} =

@ -1,8 +1,8 @@
codetoanalyze/cpp/pulse/basics.cpp, multiple_invalidations_branch_bad, 6, USE_AFTER_DELETE, no_bucket, ERROR, [invalidated by call to `delete ptr` at line 60 here,accessed `*(ptr)` here] codetoanalyze/cpp/pulse/basics.cpp, multiple_invalidations_branch_bad, 6, USE_AFTER_DELETE, no_bucket, ERROR, [invalidated by call to `delete ptr` at line 58 here,accessed `*(ptr)` here]
codetoanalyze/cpp/pulse/basics.cpp, multiple_invalidations_loop_bad, 3, USE_AFTER_DELETE, no_bucket, ERROR, [invalidated by call to `delete ptr` at line 68 here,accessed `ptr` here] codetoanalyze/cpp/pulse/basics.cpp, multiple_invalidations_loop_bad, 3, USE_AFTER_DELETE, no_bucket, ERROR, [invalidated by call to `delete ptr` at line 68 here,accessed `ptr` here]
codetoanalyze/cpp/pulse/closures.cpp, implicit_ref_capture_destroy_invoke_bad, 6, USE_AFTER_DESTRUCTOR, no_bucket, ERROR, [returned from call to `S_S(&(s),&(0$?%__sil_tmpSIL_materialize_temp__n$12))`,`&(s)` captured as `s`,invalidated by destructor call `S_~S(s)` at line 29 here,accessed `&(f)` here] codetoanalyze/cpp/pulse/closures.cpp, implicit_ref_capture_destroy_invoke_bad, 6, USE_AFTER_DESTRUCTOR, no_bucket, ERROR, [returned from call to `S_S(&(s),&(0$?%__sil_tmpSIL_materialize_temp__n$12))`,`&(s)` captured as `s`,invalidated by destructor call `S_~S(s)` at line 29 here,accessed `&(f)` here]
codetoanalyze/cpp/pulse/closures.cpp, ref_capture_destroy_invoke_bad, 6, USE_AFTER_DESTRUCTOR, no_bucket, ERROR, [returned from call to `S_S(&(s))`,`&(s)` captured as `s`,invalidated by destructor call `S_~S(s)` at line 20 here,accessed `&(f)` here] codetoanalyze/cpp/pulse/closures.cpp, ref_capture_destroy_invoke_bad, 6, USE_AFTER_DESTRUCTOR, no_bucket, ERROR, [returned from call to `S_S(&(s))`,`&(s)` captured as `s`,invalidated by destructor call `S_~S(s)` at line 20 here,accessed `&(f)` here]
codetoanalyze/cpp/pulse/join.cpp, invalidate_node_alias_bad, 12, USE_AFTER_DELETE, no_bucket, ERROR, [assigned to `result`,invalidated by call to `delete result` at line 35 here,accessed `*(result)` here] codetoanalyze/cpp/pulse/join.cpp, invalidate_node_alias_bad, 12, USE_AFTER_DELETE, no_bucket, ERROR, [assigned to `result`,invalidated by call to `delete result` at line 30 here,accessed `*(result)` here]
codetoanalyze/cpp/pulse/returns.cpp, returns::return_deleted_bad, 4, USE_AFTER_DELETE, no_bucket, ERROR, [returned from call to `__new(sizeof(int))`,assigned to `x`,invalidated by call to `delete x` at line 112 here,accessed `x` here] codetoanalyze/cpp/pulse/returns.cpp, returns::return_deleted_bad, 4, USE_AFTER_DELETE, no_bucket, ERROR, [returned from call to `__new(sizeof(int))`,assigned to `x`,invalidated by call to `delete x` at line 112 here,accessed `x` here]
codetoanalyze/cpp/pulse/returns.cpp, returns::return_literal_stack_reference_bad, 0, STACK_VARIABLE_ADDRESS_ESCAPE, no_bucket, ERROR, [] codetoanalyze/cpp/pulse/returns.cpp, returns::return_literal_stack_reference_bad, 0, STACK_VARIABLE_ADDRESS_ESCAPE, no_bucket, ERROR, []
codetoanalyze/cpp/pulse/returns.cpp, returns::return_stack_pointer_bad, 1, STACK_VARIABLE_ADDRESS_ESCAPE, no_bucket, ERROR, [] codetoanalyze/cpp/pulse/returns.cpp, returns::return_stack_pointer_bad, 1, STACK_VARIABLE_ADDRESS_ESCAPE, no_bucket, ERROR, []

Loading…
Cancel
Save