You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

468 lines
15 KiB

(*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*)
(** Iterative Breadth-First Bounded Exploration
The analysis' semantics of control flow. *)
type exec_opts = {bound: int; skip_throw: bool; function_summaries: bool}
module Stack : sig
type t
type as_inlined_location = t [@@deriving compare, sexp_of]
val empty : t
val push_call :
Llair.block
-> retreating:bool
-> bound:int
-> return:Llair.jump
-> Domain.from_call
-> ?throw:Llair.jump
-> t
-> t option
val pop_return : t -> (Domain.from_call * Llair.jump * t) option
val pop_throw :
t
-> init:'a
[sledge] Rework function return value passing Summary: The current handling of the formal return variable scope is not correct. Since it is passed as an actual argument to the return continuation, it is manipulated as if it was a local variable of the caller. However, its scope is not ended with the caller's locals, leading to clashes. This diff reworks the passing of return values to avoid this problem, mainly by introducing a notion of temporary variables during parameter passing. This essentially has the effect of taking a function spec { P } f(x) { λv. Q } and generating a "temporary" variable v, applying the post λv. Q to it to obtain the pre-state for the call to the return continuation k(v). Being a temporary variable just means that it goes out of scope just after parameter passing. This amounts to a long-winded way of applying the post-state to the formal parameter of the return continuation without violating scopes or SSA. This diff also separates the manipulation of the symbolic states as they proceed from: 1. the pre-state before the return instruction; 2. the exit-state after the return instruction (including the binding of the returned value to the return formal variable); 3. the post-state, where the locals are existentially quantified; and 4. the return-state, which is expressed in terms of actual args instead of formal parameters. Also in support of summarization, formal return and throw parameters are no longer tracked on the analyzer's stack. Note that these changes involve changing the locals of blocks and functions to no longer include the formal parameters. Reviewed By: kren1 Differential Revision: D15912148 fbshipit-source-id: e41dd6e42
6 years ago
-> unwind:(Var.t list -> Var.Set.t -> Domain.from_call -> 'a -> 'a)
-> (Domain.from_call * Llair.jump * t * 'a) option
end = struct
type t =
| Return of
{ retreating: bool
(** return from a call not known to be nonrecursive *)
; dst: Llair.Jump.t
[sledge] Rework function return value passing Summary: The current handling of the formal return variable scope is not correct. Since it is passed as an actual argument to the return continuation, it is manipulated as if it was a local variable of the caller. However, its scope is not ended with the caller's locals, leading to clashes. This diff reworks the passing of return values to avoid this problem, mainly by introducing a notion of temporary variables during parameter passing. This essentially has the effect of taking a function spec { P } f(x) { λv. Q } and generating a "temporary" variable v, applying the post λv. Q to it to obtain the pre-state for the call to the return continuation k(v). Being a temporary variable just means that it goes out of scope just after parameter passing. This amounts to a long-winded way of applying the post-state to the formal parameter of the return continuation without violating scopes or SSA. This diff also separates the manipulation of the symbolic states as they proceed from: 1. the pre-state before the return instruction; 2. the exit-state after the return instruction (including the binding of the returned value to the return formal variable); 3. the post-state, where the locals are existentially quantified; and 4. the return-state, which is expressed in terms of actual args instead of formal parameters. Also in support of summarization, formal return and throw parameters are no longer tracked on the analyzer's stack. Note that these changes involve changing the locals of blocks and functions to no longer include the formal parameters. Reviewed By: kren1 Differential Revision: D15912148 fbshipit-source-id: e41dd6e42
6 years ago
; params: Var.t list
; locals: Var.Set.t
; from_call: Domain.from_call
; stk: t }
| Throw of Llair.Jump.t * t
| Empty
[@@deriving sexp_of]
type as_inlined_location = t [@@deriving sexp_of]
(* Treat a stack as a code location in a hypothetical expansion of the
program where all non-recursive functions have been completely inlined.
In particular, this means to compare stacks as if all Return frames for
recursive calls had been removed. Additionally, the from_call info in
Return frames is ignored. *)
let rec compare_as_inlined_location x y =
if x == y then 0
else
match (x, y) with
| Return {retreating= true; stk= x}, y
|x, Return {retreating= true; stk= y} ->
compare_as_inlined_location x y
| Return {dst= j; stk= x}, Return {dst= k; stk= y} -> (
match Llair.Jump.compare j k with
| 0 -> compare_as_inlined_location x y
| n -> n )
| Return _, _ -> -1
| _, Return _ -> 1
| Throw (j, x), Throw (k, y) -> (
match Llair.Jump.compare j k with
| 0 -> compare_as_inlined_location x y
| n -> n )
| Throw _, _ -> -1
| _, Throw _ -> 1
| Empty, Empty -> 0
let rec print_abbrev fs = function
| Return {retreating= false; stk= s} ->
print_abbrev fs s ;
Format.pp_print_char fs 'R'
| Return {retreating= true; stk= s} ->
print_abbrev fs s ;
Format.pp_print_string fs "R↑"
| Throw (_, s) ->
print_abbrev fs s ;
Format.pp_print_char fs 'T'
| Empty -> ()
let invariant s =
Invariant.invariant [%here] s [%sexp_of: t]
@@ fun () ->
match s with
| Return _ | Throw (_, Return _) | Empty -> ()
| Throw _ -> fail "malformed stack: %a" print_abbrev s ()
let empty = Empty |> check invariant
let push_return ~retreating dst params locals from_call stk =
Return {retreating; dst; params; locals; from_call; stk}
|> check invariant
let push_throw jmp stk =
(match jmp with None -> stk | Some jmp -> Throw (jmp, stk))
|> check invariant
let push_call (entry : Llair.block) ~retreating ~bound ~return from_call
[sledge] Rework function return value passing Summary: The current handling of the formal return variable scope is not correct. Since it is passed as an actual argument to the return continuation, it is manipulated as if it was a local variable of the caller. However, its scope is not ended with the caller's locals, leading to clashes. This diff reworks the passing of return values to avoid this problem, mainly by introducing a notion of temporary variables during parameter passing. This essentially has the effect of taking a function spec { P } f(x) { λv. Q } and generating a "temporary" variable v, applying the post λv. Q to it to obtain the pre-state for the call to the return continuation k(v). Being a temporary variable just means that it goes out of scope just after parameter passing. This amounts to a long-winded way of applying the post-state to the formal parameter of the return continuation without violating scopes or SSA. This diff also separates the manipulation of the symbolic states as they proceed from: 1. the pre-state before the return instruction; 2. the exit-state after the return instruction (including the binding of the returned value to the return formal variable); 3. the post-state, where the locals are existentially quantified; and 4. the return-state, which is expressed in terms of actual args instead of formal parameters. Also in support of summarization, formal return and throw parameters are no longer tracked on the analyzer's stack. Note that these changes involve changing the locals of blocks and functions to no longer include the formal parameters. Reviewed By: kren1 Differential Revision: D15912148 fbshipit-source-id: e41dd6e42
6 years ago
?throw stk =
[%Trace.call fun {pf} -> pf "%a" print_abbrev stk]
;
let rec count_f_in_stack acc f = function
| Return {stk= next_frame; dst= dest_block} ->
count_f_in_stack
(if Llair.Jump.equal dest_block f then acc + 1 else acc)
f next_frame
| _ -> acc
in
let n = count_f_in_stack 0 return stk in
( if n > bound then None
else
Some
(push_throw throw
(push_return ~retreating return entry.params entry.parent.locals
from_call stk)) )
|>
[%Trace.retn fun {pf} _ ->
pf "%d of %a on stack" n Llair.Jump.pp return]
let rec pop_return = function
| Throw (_, stk) -> pop_return stk
| Return {from_call; dst; stk} -> Some (from_call, dst, stk)
| Empty -> None
[sledge] Rework function return value passing Summary: The current handling of the formal return variable scope is not correct. Since it is passed as an actual argument to the return continuation, it is manipulated as if it was a local variable of the caller. However, its scope is not ended with the caller's locals, leading to clashes. This diff reworks the passing of return values to avoid this problem, mainly by introducing a notion of temporary variables during parameter passing. This essentially has the effect of taking a function spec { P } f(x) { λv. Q } and generating a "temporary" variable v, applying the post λv. Q to it to obtain the pre-state for the call to the return continuation k(v). Being a temporary variable just means that it goes out of scope just after parameter passing. This amounts to a long-winded way of applying the post-state to the formal parameter of the return continuation without violating scopes or SSA. This diff also separates the manipulation of the symbolic states as they proceed from: 1. the pre-state before the return instruction; 2. the exit-state after the return instruction (including the binding of the returned value to the return formal variable); 3. the post-state, where the locals are existentially quantified; and 4. the return-state, which is expressed in terms of actual args instead of formal parameters. Also in support of summarization, formal return and throw parameters are no longer tracked on the analyzer's stack. Note that these changes involve changing the locals of blocks and functions to no longer include the formal parameters. Reviewed By: kren1 Differential Revision: D15912148 fbshipit-source-id: e41dd6e42
6 years ago
let pop_throw stk ~init ~unwind =
let rec pop_throw_ state = function
| Return {params; locals; from_call; stk} ->
pop_throw_ (unwind params locals from_call state) stk
[sledge] Rework function return value passing Summary: The current handling of the formal return variable scope is not correct. Since it is passed as an actual argument to the return continuation, it is manipulated as if it was a local variable of the caller. However, its scope is not ended with the caller's locals, leading to clashes. This diff reworks the passing of return values to avoid this problem, mainly by introducing a notion of temporary variables during parameter passing. This essentially has the effect of taking a function spec { P } f(x) { λv. Q } and generating a "temporary" variable v, applying the post λv. Q to it to obtain the pre-state for the call to the return continuation k(v). Being a temporary variable just means that it goes out of scope just after parameter passing. This amounts to a long-winded way of applying the post-state to the formal parameter of the return continuation without violating scopes or SSA. This diff also separates the manipulation of the symbolic states as they proceed from: 1. the pre-state before the return instruction; 2. the exit-state after the return instruction (including the binding of the returned value to the return formal variable); 3. the post-state, where the locals are existentially quantified; and 4. the return-state, which is expressed in terms of actual args instead of formal parameters. Also in support of summarization, formal return and throw parameters are no longer tracked on the analyzer's stack. Note that these changes involve changing the locals of blocks and functions to no longer include the formal parameters. Reviewed By: kren1 Differential Revision: D15912148 fbshipit-source-id: e41dd6e42
6 years ago
| Throw (dst, Return {from_call; stk}) ->
Some (from_call, dst, stk, state)
| Empty -> None
| Throw _ as stk -> violates invariant stk
in
pop_throw_ init stk
end
module Work : sig
type t
val init : Domain.t -> Llair.block -> int -> t
type x
val skip : x
val seq : x -> x -> x
val add :
?prev:Llair.block
-> retreating:bool
-> Stack.t
-> Domain.t
-> Llair.block
-> x
val run : f:(Stack.t -> Domain.t -> Llair.block -> x) -> t -> unit
end = struct
module Edge = struct
module T = struct
type t =
{ dst: Llair.Block.t
; src: Llair.Block.t option
; stk: Stack.as_inlined_location }
[@@deriving compare, sexp_of]
end
include T
include Comparator.Make (T)
let pp fs {dst; src} =
Format.fprintf fs "#%i %s <--%a" dst.sort_index dst.lbl
(Option.pp "%a" (fun fs (src : Llair.Block.t) ->
Format.fprintf fs " #%i %s" src.sort_index src.lbl ))
src
end
module Depths = struct
type t = int Map.M(Edge).t
let empty = Map.empty (module Edge)
let find = Map.find
let set = Map.set
let join x y =
Map.merge x y ~f:(fun ~key:_ -> function
| `Left d | `Right d -> Some d
| `Both (d1, d2) -> Some (Int.max d1 d2) )
end
type priority = int * Edge.t [@@deriving compare]
type priority_queue = priority Fheap.t
type waiting_states = (Domain.t * Depths.t) list Map.M(Llair.Block).t
type t = priority_queue * waiting_states * int
type x = Depths.t -> t -> t
let empty_waiting_states : waiting_states = Map.empty (module Llair.Block)
let pp_priority fs (n, e) = Format.fprintf fs "%i: %a" n Edge.pp e
let pp fs pq =
Format.fprintf fs "@[%a@]"
(List.pp " ::@ " pp_priority)
(Sequence.to_list (Fheap.to_sequence pq))
let skip _ w = w
let seq x y d w = y d (x d w)
let add ?prev ~retreating stk state curr depths ((pq, ws, bound) as work)
=
let edge = {Edge.dst= curr; src= prev; stk} in
let depth = Option.value (Depths.find depths edge) ~default:0 in
let depth = if retreating then depth + 1 else depth in
if depth > bound then (
[%Trace.info "prune: %i: %a" depth Edge.pp edge] ;
work )
else
let pq = Fheap.add pq (depth, edge) in
[%Trace.info "@[<6>enqueue %i: %a@ | %a@]" depth Edge.pp edge pp pq] ;
let depths = Depths.set depths ~key:edge ~data:depth in
let ws = Map.add_multi ws ~key:curr ~data:(state, depths) in
(pq, ws, bound)
let init state curr bound =
add ~retreating:false Stack.empty state curr Depths.empty
(Fheap.create ~cmp:compare_priority, empty_waiting_states, bound)
let rec run ~f (pq0, ws, bnd) =
match Fheap.pop pq0 with
| Some ((_, ({Edge.dst; stk} as edge)), pq) -> (
match Map.find_and_remove ws dst with
| Some (state :: states, ws) ->
let join (qa, da) (q, d) = (Domain.join q qa, Depths.join d da) in
let qs, depths = List.fold ~f:join ~init:state states in
run ~f (f stk qs dst depths (pq, ws, bnd))
| _ ->
[%Trace.info "done: %a" Edge.pp edge] ;
run ~f (pq, ws, bnd) )
| None -> [%Trace.info "queue empty"] ; ()
end
let exec_goto stk state block ({dst; retreating} : Llair.jump) =
Work.add ~prev:block ~retreating stk state dst
[sledge] Rework function return value passing Summary: The current handling of the formal return variable scope is not correct. Since it is passed as an actual argument to the return continuation, it is manipulated as if it was a local variable of the caller. However, its scope is not ended with the caller's locals, leading to clashes. This diff reworks the passing of return values to avoid this problem, mainly by introducing a notion of temporary variables during parameter passing. This essentially has the effect of taking a function spec { P } f(x) { λv. Q } and generating a "temporary" variable v, applying the post λv. Q to it to obtain the pre-state for the call to the return continuation k(v). Being a temporary variable just means that it goes out of scope just after parameter passing. This amounts to a long-winded way of applying the post-state to the formal parameter of the return continuation without violating scopes or SSA. This diff also separates the manipulation of the symbolic states as they proceed from: 1. the pre-state before the return instruction; 2. the exit-state after the return instruction (including the binding of the returned value to the return formal variable); 3. the post-state, where the locals are existentially quantified; and 4. the return-state, which is expressed in terms of actual args instead of formal parameters. Also in support of summarization, formal return and throw parameters are no longer tracked on the analyzer's stack. Note that these changes involve changing the locals of blocks and functions to no longer include the formal parameters. Reviewed By: kren1 Differential Revision: D15912148 fbshipit-source-id: e41dd6e42
6 years ago
let exec_jump ?temps stk state block ({dst; args} as jmp : Llair.jump) =
let state = Domain.jump args dst.params ?temps state in
exec_goto stk state block jmp
let exec_call ~opts stk state block ({dst; args; retreating} : Llair.jump)
(return : Llair.jump) throw globals =
[%Trace.call fun {pf} ->
pf "%a from %a" Var.pp dst.parent.name.var Var.pp
return.dst.parent.name.var]
;
let dnf_states =
if opts.function_summaries then Domain.dnf state else [state]
in
List.fold ~init:Work.skip dnf_states ~f:(fun acc state ->
let locals = dst.parent.locals in
let state, from_call =
Domain.call ~summaries:opts.function_summaries args dst.params
locals globals state
in
Work.seq acc
( match
Stack.push_call ~bound:opts.bound dst ~retreating ~return
from_call ?throw stk
with
| Some stk -> Work.add stk ~prev:block ~retreating state dst
| None -> Work.skip ) )
|>
[%Trace.retn fun {pf} _ -> pf ""]
let summary_table = Hashtbl.create (module Var)
let exec_return ~opts stk pre_state (block : Llair.block) exp =
[%Trace.call fun {pf} -> pf "from %a" Var.pp block.parent.name.var]
;
[sledge] Rework function return value passing Summary: The current handling of the formal return variable scope is not correct. Since it is passed as an actual argument to the return continuation, it is manipulated as if it was a local variable of the caller. However, its scope is not ended with the caller's locals, leading to clashes. This diff reworks the passing of return values to avoid this problem, mainly by introducing a notion of temporary variables during parameter passing. This essentially has the effect of taking a function spec { P } f(x) { λv. Q } and generating a "temporary" variable v, applying the post λv. Q to it to obtain the pre-state for the call to the return continuation k(v). Being a temporary variable just means that it goes out of scope just after parameter passing. This amounts to a long-winded way of applying the post-state to the formal parameter of the return continuation without violating scopes or SSA. This diff also separates the manipulation of the symbolic states as they proceed from: 1. the pre-state before the return instruction; 2. the exit-state after the return instruction (including the binding of the returned value to the return formal variable); 3. the post-state, where the locals are existentially quantified; and 4. the return-state, which is expressed in terms of actual args instead of formal parameters. Also in support of summarization, formal return and throw parameters are no longer tracked on the analyzer's stack. Note that these changes involve changing the locals of blocks and functions to no longer include the formal parameters. Reviewed By: kren1 Differential Revision: D15912148 fbshipit-source-id: e41dd6e42
6 years ago
( match Stack.pop_return stk with
| Some (from_call, retn_site, stk) ->
[sledge] Rework function return value passing Summary: The current handling of the formal return variable scope is not correct. Since it is passed as an actual argument to the return continuation, it is manipulated as if it was a local variable of the caller. However, its scope is not ended with the caller's locals, leading to clashes. This diff reworks the passing of return values to avoid this problem, mainly by introducing a notion of temporary variables during parameter passing. This essentially has the effect of taking a function spec { P } f(x) { λv. Q } and generating a "temporary" variable v, applying the post λv. Q to it to obtain the pre-state for the call to the return continuation k(v). Being a temporary variable just means that it goes out of scope just after parameter passing. This amounts to a long-winded way of applying the post-state to the formal parameter of the return continuation without violating scopes or SSA. This diff also separates the manipulation of the symbolic states as they proceed from: 1. the pre-state before the return instruction; 2. the exit-state after the return instruction (including the binding of the returned value to the return formal variable); 3. the post-state, where the locals are existentially quantified; and 4. the return-state, which is expressed in terms of actual args instead of formal parameters. Also in support of summarization, formal return and throw parameters are no longer tracked on the analyzer's stack. Note that these changes involve changing the locals of blocks and functions to no longer include the formal parameters. Reviewed By: kren1 Differential Revision: D15912148 fbshipit-source-id: e41dd6e42
6 years ago
let freturn = block.parent.freturn in
let exit_state =
match (freturn, exp) with
| Some freturn, Some return_val ->
Domain.exec_return pre_state freturn return_val
| None, None -> pre_state
| _ -> violates Llair.Func.invariant block.parent
in
let function_summary = exit_state in
if opts.function_summaries then
Hashtbl.add_multi summary_table ~key:block.parent.name.var
~data:function_summary ;
let post_state =
Domain.post block.parent.locals from_call exit_state
in
[sledge] Rework function return value passing Summary: The current handling of the formal return variable scope is not correct. Since it is passed as an actual argument to the return continuation, it is manipulated as if it was a local variable of the caller. However, its scope is not ended with the caller's locals, leading to clashes. This diff reworks the passing of return values to avoid this problem, mainly by introducing a notion of temporary variables during parameter passing. This essentially has the effect of taking a function spec { P } f(x) { λv. Q } and generating a "temporary" variable v, applying the post λv. Q to it to obtain the pre-state for the call to the return continuation k(v). Being a temporary variable just means that it goes out of scope just after parameter passing. This amounts to a long-winded way of applying the post-state to the formal parameter of the return continuation without violating scopes or SSA. This diff also separates the manipulation of the symbolic states as they proceed from: 1. the pre-state before the return instruction; 2. the exit-state after the return instruction (including the binding of the returned value to the return formal variable); 3. the post-state, where the locals are existentially quantified; and 4. the return-state, which is expressed in terms of actual args instead of formal parameters. Also in support of summarization, formal return and throw parameters are no longer tracked on the analyzer's stack. Note that these changes involve changing the locals of blocks and functions to no longer include the formal parameters. Reviewed By: kren1 Differential Revision: D15912148 fbshipit-source-id: e41dd6e42
6 years ago
let retn_state =
Domain.retn block.parent.entry.params from_call post_state
in
exec_jump stk retn_state block
~temps:(Option.fold ~f:Set.add freturn ~init:Var.Set.empty)
( match freturn with
| Some freturn -> Llair.Jump.push_arg (Exp.var freturn) retn_site
| None -> retn_site )
| None -> Work.skip )
|>
[%Trace.retn fun {pf} _ ->
pf "@[<v>%t@]" (fun fs ->
Hashtbl.iteri summary_table ~f:(fun ~key ~data ->
Format.fprintf fs "@[<v>%a:@ @[%a@]@]@ " Var.pp key
(List.pp "@," Domain.pp) data ) )]
[sledge] Rework function return value passing Summary: The current handling of the formal return variable scope is not correct. Since it is passed as an actual argument to the return continuation, it is manipulated as if it was a local variable of the caller. However, its scope is not ended with the caller's locals, leading to clashes. This diff reworks the passing of return values to avoid this problem, mainly by introducing a notion of temporary variables during parameter passing. This essentially has the effect of taking a function spec { P } f(x) { λv. Q } and generating a "temporary" variable v, applying the post λv. Q to it to obtain the pre-state for the call to the return continuation k(v). Being a temporary variable just means that it goes out of scope just after parameter passing. This amounts to a long-winded way of applying the post-state to the formal parameter of the return continuation without violating scopes or SSA. This diff also separates the manipulation of the symbolic states as they proceed from: 1. the pre-state before the return instruction; 2. the exit-state after the return instruction (including the binding of the returned value to the return formal variable); 3. the post-state, where the locals are existentially quantified; and 4. the return-state, which is expressed in terms of actual args instead of formal parameters. Also in support of summarization, formal return and throw parameters are no longer tracked on the analyzer's stack. Note that these changes involve changing the locals of blocks and functions to no longer include the formal parameters. Reviewed By: kren1 Differential Revision: D15912148 fbshipit-source-id: e41dd6e42
6 years ago
let exec_throw stk pre_state (block : Llair.block) exc =
[%Trace.call fun {pf} -> pf "from %a" Var.pp block.parent.name.var]
;
let unwind params scope from_call state =
Domain.retn params from_call (Domain.post scope from_call state)
in
( match Stack.pop_throw stk ~unwind ~init:pre_state with
| Some (from_call, retn_site, stk, unwind_state) ->
[sledge] Rework function return value passing Summary: The current handling of the formal return variable scope is not correct. Since it is passed as an actual argument to the return continuation, it is manipulated as if it was a local variable of the caller. However, its scope is not ended with the caller's locals, leading to clashes. This diff reworks the passing of return values to avoid this problem, mainly by introducing a notion of temporary variables during parameter passing. This essentially has the effect of taking a function spec { P } f(x) { λv. Q } and generating a "temporary" variable v, applying the post λv. Q to it to obtain the pre-state for the call to the return continuation k(v). Being a temporary variable just means that it goes out of scope just after parameter passing. This amounts to a long-winded way of applying the post-state to the formal parameter of the return continuation without violating scopes or SSA. This diff also separates the manipulation of the symbolic states as they proceed from: 1. the pre-state before the return instruction; 2. the exit-state after the return instruction (including the binding of the returned value to the return formal variable); 3. the post-state, where the locals are existentially quantified; and 4. the return-state, which is expressed in terms of actual args instead of formal parameters. Also in support of summarization, formal return and throw parameters are no longer tracked on the analyzer's stack. Note that these changes involve changing the locals of blocks and functions to no longer include the formal parameters. Reviewed By: kren1 Differential Revision: D15912148 fbshipit-source-id: e41dd6e42
6 years ago
let fthrow = block.parent.fthrow in
let exit_state = Domain.exec_return unwind_state fthrow exc in
let post_state =
Domain.post block.parent.locals from_call exit_state
in
[sledge] Rework function return value passing Summary: The current handling of the formal return variable scope is not correct. Since it is passed as an actual argument to the return continuation, it is manipulated as if it was a local variable of the caller. However, its scope is not ended with the caller's locals, leading to clashes. This diff reworks the passing of return values to avoid this problem, mainly by introducing a notion of temporary variables during parameter passing. This essentially has the effect of taking a function spec { P } f(x) { λv. Q } and generating a "temporary" variable v, applying the post λv. Q to it to obtain the pre-state for the call to the return continuation k(v). Being a temporary variable just means that it goes out of scope just after parameter passing. This amounts to a long-winded way of applying the post-state to the formal parameter of the return continuation without violating scopes or SSA. This diff also separates the manipulation of the symbolic states as they proceed from: 1. the pre-state before the return instruction; 2. the exit-state after the return instruction (including the binding of the returned value to the return formal variable); 3. the post-state, where the locals are existentially quantified; and 4. the return-state, which is expressed in terms of actual args instead of formal parameters. Also in support of summarization, formal return and throw parameters are no longer tracked on the analyzer's stack. Note that these changes involve changing the locals of blocks and functions to no longer include the formal parameters. Reviewed By: kren1 Differential Revision: D15912148 fbshipit-source-id: e41dd6e42
6 years ago
let retn_state =
Domain.retn block.parent.entry.params from_call post_state
in
exec_jump stk retn_state block
~temps:(Set.add Var.Set.empty fthrow)
(Llair.Jump.push_arg (Exp.var fthrow) retn_site)
| None -> Work.skip )
|>
[%Trace.retn fun {pf} _ -> pf ""]
let exec_skip_func :
Stack.t -> Domain.t -> Llair.block -> Llair.jump -> Work.x =
fun stk state block ({dst; args} as return) ->
Report.unknown_call block.term ;
let return =
if List.is_empty dst.params then return
else
let args =
List.fold_right dst.params ~init:args ~f:(fun param args ->
Exp.nondet (Var.name param) :: args )
in
{return with args}
in
exec_jump stk state block return
let exec_term :
opts:exec_opts
-> Llair.t
-> Stack.t
-> Domain.t
-> Llair.block
-> Work.x =
fun ~opts pgm stk state block ->
[%Trace.info "exec %a" Llair.Term.pp block.term] ;
match block.term with
| Switch {key; tbl; els} ->
Vector.fold tbl
~f:(fun x (case, jump) ->
[sledge] Rework function return value passing Summary: The current handling of the formal return variable scope is not correct. Since it is passed as an actual argument to the return continuation, it is manipulated as if it was a local variable of the caller. However, its scope is not ended with the caller's locals, leading to clashes. This diff reworks the passing of return values to avoid this problem, mainly by introducing a notion of temporary variables during parameter passing. This essentially has the effect of taking a function spec { P } f(x) { λv. Q } and generating a "temporary" variable v, applying the post λv. Q to it to obtain the pre-state for the call to the return continuation k(v). Being a temporary variable just means that it goes out of scope just after parameter passing. This amounts to a long-winded way of applying the post-state to the formal parameter of the return continuation without violating scopes or SSA. This diff also separates the manipulation of the symbolic states as they proceed from: 1. the pre-state before the return instruction; 2. the exit-state after the return instruction (including the binding of the returned value to the return formal variable); 3. the post-state, where the locals are existentially quantified; and 4. the return-state, which is expressed in terms of actual args instead of formal parameters. Also in support of summarization, formal return and throw parameters are no longer tracked on the analyzer's stack. Note that these changes involve changing the locals of blocks and functions to no longer include the formal parameters. Reviewed By: kren1 Differential Revision: D15912148 fbshipit-source-id: e41dd6e42
6 years ago
match Domain.exec_assume state (Exp.eq key case) with
| Some state -> exec_jump stk state block jump |> Work.seq x
| None -> x )
~init:
( match
[sledge] Rework function return value passing Summary: The current handling of the formal return variable scope is not correct. Since it is passed as an actual argument to the return continuation, it is manipulated as if it was a local variable of the caller. However, its scope is not ended with the caller's locals, leading to clashes. This diff reworks the passing of return values to avoid this problem, mainly by introducing a notion of temporary variables during parameter passing. This essentially has the effect of taking a function spec { P } f(x) { λv. Q } and generating a "temporary" variable v, applying the post λv. Q to it to obtain the pre-state for the call to the return continuation k(v). Being a temporary variable just means that it goes out of scope just after parameter passing. This amounts to a long-winded way of applying the post-state to the formal parameter of the return continuation without violating scopes or SSA. This diff also separates the manipulation of the symbolic states as they proceed from: 1. the pre-state before the return instruction; 2. the exit-state after the return instruction (including the binding of the returned value to the return formal variable); 3. the post-state, where the locals are existentially quantified; and 4. the return-state, which is expressed in terms of actual args instead of formal parameters. Also in support of summarization, formal return and throw parameters are no longer tracked on the analyzer's stack. Note that these changes involve changing the locals of blocks and functions to no longer include the formal parameters. Reviewed By: kren1 Differential Revision: D15912148 fbshipit-source-id: e41dd6e42
6 years ago
Domain.exec_assume state
(Vector.fold tbl ~init:(Exp.bool true)
~f:(fun b (case, _) -> Exp.and_ (Exp.dq key case) b))
with
| Some state -> exec_jump stk state block els
| None -> Work.skip )
| Iswitch {ptr; tbl} ->
Vector.fold tbl ~init:Work.skip ~f:(fun x (jump : Llair.jump) ->
match
[sledge] Rework function return value passing Summary: The current handling of the formal return variable scope is not correct. Since it is passed as an actual argument to the return continuation, it is manipulated as if it was a local variable of the caller. However, its scope is not ended with the caller's locals, leading to clashes. This diff reworks the passing of return values to avoid this problem, mainly by introducing a notion of temporary variables during parameter passing. This essentially has the effect of taking a function spec { P } f(x) { λv. Q } and generating a "temporary" variable v, applying the post λv. Q to it to obtain the pre-state for the call to the return continuation k(v). Being a temporary variable just means that it goes out of scope just after parameter passing. This amounts to a long-winded way of applying the post-state to the formal parameter of the return continuation without violating scopes or SSA. This diff also separates the manipulation of the symbolic states as they proceed from: 1. the pre-state before the return instruction; 2. the exit-state after the return instruction (including the binding of the returned value to the return formal variable); 3. the post-state, where the locals are existentially quantified; and 4. the return-state, which is expressed in terms of actual args instead of formal parameters. Also in support of summarization, formal return and throw parameters are no longer tracked on the analyzer's stack. Note that these changes involve changing the locals of blocks and functions to no longer include the formal parameters. Reviewed By: kren1 Differential Revision: D15912148 fbshipit-source-id: e41dd6e42
6 years ago
Domain.exec_assume state
(Exp.eq ptr
(Exp.label
~parent:(Var.name jump.dst.parent.name.var)
~name:jump.dst.lbl))
with
| Some state -> exec_jump stk state block jump |> Work.seq x
| None -> x )
| Call {call= {dst; args; retreating}; return; throw} -> (
match
let lookup name =
Option.to_list (Llair.Func.find pgm.functions name)
in
Domain.resolve_callee lookup dst state
with
| [] -> exec_skip_func stk state block return
| callees ->
List.fold callees ~init:Work.skip ~f:(fun x callee ->
( match
Domain.exec_intrinsic state
(List.hd return.dst.params)
callee.name.var args
with
| Some (Error ()) ->
Report.invalid_access_term state block.term ;
Work.skip
| Some (Ok state) -> exec_goto stk state block return
| None when Llair.Func.is_undefined callee ->
exec_skip_func stk state block return
| None ->
exec_call ~opts stk state block
{dst= callee.entry; args; retreating}
return throw pgm.globals )
|> Work.seq x ) )
| Return {exp} -> exec_return ~opts stk state block exp
| Throw {exc} ->
if opts.skip_throw then Work.skip else exec_throw stk state block exc
| Unreachable -> Work.skip
let exec_inst :
Domain.t -> Llair.inst -> (Domain.t, Domain.t * Llair.inst) result =
fun state inst ->
Domain.exec_inst state inst
|> Result.map_error ~f:(fun () -> (state, inst))
let exec_block :
opts:exec_opts
-> Llair.t
-> Stack.t
-> Domain.t
-> Llair.block
-> Work.x =
fun ~opts pgm stk state block ->
[%Trace.info "exec %a" Llair.Block.pp block] ;
match Vector.fold_result ~f:exec_inst ~init:state block.cmnd with
| Ok state -> exec_term ~opts pgm stk state block
| Error (state, inst) ->
Report.invalid_access_inst state inst ;
Work.skip
let harness : opts:exec_opts -> Llair.t -> (int -> Work.t) option =
fun ~opts pgm ->
let entry_points = Config.find_list "entry-points" in
List.find_map entry_points ~f:(fun name ->
Llair.Func.find pgm.functions (Var.program name) )
|> function
| Some {locals; entry= {params= []} as block} ->
Some
(Work.init
(fst
(Domain.call ~summaries:opts.function_summaries [] [] locals
pgm.globals (Domain.init pgm.globals)))
block)
| _ -> None
let exec_pgm : exec_opts -> Llair.t -> unit =
fun opts pgm ->
[%Trace.call fun {pf} -> pf "@]@,@["]
;
( match harness ~opts pgm with
| Some work -> Work.run ~f:(exec_block ~opts pgm) (work opts.bound)
| None -> fail "no applicable harness" () )
|>
[%Trace.retn fun {pf} _ -> pf ""]