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.

358 lines
14 KiB

(*
* Copyright (c) 2018-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*)
open! IStd
module L = Logging
module AbstractAddress = PulseDomain.AbstractAddress
module Attribute = PulseDomain.Attribute
module Attributes = PulseDomain.Attributes
module Memory = PulseDomain.Memory
module Stack = PulseDomain.Stack
open Result.Monad_infix
type t = PulseDomain.t = {heap: Memory.t; stack: Stack.t}
type 'a access_result = ('a, PulseDiagnostic.t) result
(** Check that the address is not known to be invalid *)
let check_addr_access actor (address, trace) astate =
match Memory.get_invalidation address astate.heap with
| Some invalidated_by ->
Error
(PulseDiagnostic.AccessToInvalidAddress {invalidated_by; accessed_by= actor; address; trace})
| None ->
Ok astate
(** Walk the heap starting from [addr] and following [path]. Stop either at the element before last
and return [new_addr] if [overwrite_last] is [Some new_addr], or go until the end of the path if it
is [None]. Create more addresses into the heap as needed to follow the [path]. Check that each
address reached is valid. *)
let rec walk ~dereference_to_ignore actor ~on_last addr_trace path astate =
let check_addr_access_optional actor addr_trace astate =
match dereference_to_ignore with
| Some 0 ->
Ok astate
| _ ->
check_addr_access actor addr_trace astate
in
match (path, on_last) with
| [], `Access ->
Ok (astate, addr_trace)
| [], `Overwrite _ ->
L.die InternalError "Cannot overwrite last address in empty path"
| [a], `Overwrite new_addr_trace ->
check_addr_access_optional actor addr_trace astate
>>| fun astate ->
let heap = Memory.add_edge_and_back_edge (fst addr_trace) a new_addr_trace astate.heap in
({astate with heap}, new_addr_trace)
| a :: path, _ -> (
check_addr_access_optional actor addr_trace astate
>>= fun astate ->
let dereference_to_ignore =
Option.map ~f:(fun index -> max 0 (index - 1)) dereference_to_ignore
in
let addr = fst addr_trace in
match Memory.find_edge_opt addr a astate.heap with
| None ->
let addr_trace' = (AbstractAddress.mk_fresh (), []) in
let heap = Memory.add_edge_and_back_edge addr a addr_trace' astate.heap in
let astate = {astate with heap} in
walk ~dereference_to_ignore actor ~on_last addr_trace' path astate
| Some addr_trace' ->
walk ~dereference_to_ignore actor ~on_last addr_trace' path astate )
let write_var var new_addr_trace astate =
let astate, var_address_of =
match Stack.find_opt var astate.stack with
| Some (addr, _) ->
(astate, addr)
| None ->
let addr = AbstractAddress.mk_fresh () in
let stack = Stack.add var (addr, None) astate.stack in
({astate with stack}, addr)
in
(* Update heap with var_address_of -*-> new_addr *)
let heap = Memory.add_edge var_address_of HilExp.Access.Dereference new_addr_trace astate.heap in
{astate with heap}
let ends_with_addressof = function HilExp.AccessExpression.AddressOf _ -> true | _ -> false
let last_dereference access_list =
let rec last_dereference_inner access_list index result =
match access_list with
| [] ->
result
| HilExp.Access.Dereference :: rest ->
last_dereference_inner rest (index + 1) (Some index)
| _ :: rest ->
last_dereference_inner rest (index + 1) result
in
last_dereference_inner access_list 0 None
let rec to_accesses location access_expr astate =
let exception Failed_fold of PulseDiagnostic.t in
try
HilExp.AccessExpression.to_accesses_fold access_expr ~init:astate
~f_array_offset:(fun astate hil_exp_opt ->
match hil_exp_opt with
| None ->
(astate, AbstractAddress.mk_fresh ())
| Some hil_exp -> (
match eval_hil_exp location hil_exp astate with
| Ok result ->
result
| Error diag ->
raise (Failed_fold diag) ) )
|> Result.return
with Failed_fold diag -> Error diag
(** add addresses to the state to give an address to the destination of the given access path *)
and walk_access_expr ~on_last astate access_expr location =
to_accesses location access_expr astate
>>= fun (astate, (access_var, _), access_list) ->
let dereference_to_ignore =
if ends_with_addressof access_expr then last_dereference access_list else None
in
if Config.write_html then
L.d_printfln "Accessing %a -> [%a]" Var.pp access_var
(Pp.seq ~sep:"," Memory.Access.pp)
access_list ;
match (on_last, access_list) with
| `Overwrite new_addr_trace, [] ->
Ok (write_var access_var new_addr_trace astate, new_addr_trace)
| `Access, _ | `Overwrite _, _ :: _ -> (
let astate, base_addr_trace =
match Stack.find_opt access_var astate.stack with
| Some (addr, init_loc_opt) ->
let trace =
Option.value_map init_loc_opt ~default:[] ~f:(fun init_loc ->
[PulseTrace.VariableDeclaration init_loc] )
in
(astate, (addr, trace))
| None ->
let addr = AbstractAddress.mk_fresh () in
let stack = Stack.add access_var (addr, None) astate.stack in
({astate with stack}, (addr, []))
in
match access_list with
| [HilExp.Access.TakeAddress] ->
Ok (astate, base_addr_trace)
| _ ->
let actor = {PulseDiagnostic.access_expr; location} in
walk ~dereference_to_ignore actor ~on_last base_addr_trace
(HilExp.Access.Dereference :: access_list)
astate )
(** Use the stack and heap to walk the access path represented by the given expression down to an
abstract address representing what the expression points to.
Return an error state if it traverses some known invalid address or if the end destination is
known to be invalid. *)
and materialize_address astate access_expr = walk_access_expr ~on_last:`Access astate access_expr
and read location access_expr astate =
materialize_address astate access_expr location
>>= fun (astate, addr_trace) ->
let actor = {PulseDiagnostic.access_expr; location} in
check_addr_access actor addr_trace astate >>| fun astate -> (astate, addr_trace)
and read_all location access_exprs astate =
List.fold_result access_exprs ~init:astate ~f:(fun astate access_expr ->
read location access_expr astate >>| fst )
and eval_hil_exp location (hil_exp : HilExp.t) astate =
match hil_exp with
| AccessExpression access_expr ->
read location access_expr astate >>| fun (astate, (addr, _)) -> (astate, addr)
| _ ->
read_all location (HilExp.get_access_exprs hil_exp) astate
>>| fun astate -> (astate, AbstractAddress.mk_fresh ())
(** Use the stack and heap to walk the access path represented by the given expression down to an
abstract address representing what the expression points to, and replace that with the given
address.
Return an error state if it traverses some known invalid address. *)
let overwrite_address astate access_expr new_addr_trace =
walk_access_expr ~on_last:(`Overwrite new_addr_trace) astate access_expr
(** Add the given address to the set of know invalid addresses. *)
let mark_invalid actor address astate =
{astate with heap= Memory.invalidate address actor astate.heap}
let havoc_var trace var astate = write_var var (AbstractAddress.mk_fresh (), trace) astate
let havoc trace location (access_expr : HilExp.AccessExpression.t) astate =
overwrite_address astate access_expr (AbstractAddress.mk_fresh (), trace) location >>| fst
let write location access_expr addr astate =
overwrite_address astate access_expr addr location >>| fun (astate, _) -> astate
let invalidate cause location access_expr astate =
materialize_address astate access_expr location
>>= fun (astate, addr_trace) ->
check_addr_access {access_expr; location} addr_trace astate
>>| mark_invalid cause (fst addr_trace)
let invalidate_array_elements cause location access_expr astate =
materialize_address astate access_expr location
>>= fun (astate, addr_trace) ->
check_addr_access {access_expr; location} addr_trace astate
>>| fun astate ->
match Memory.find_opt (fst addr_trace) astate.heap with
| None ->
astate
| Some (edges, _) ->
Memory.Edges.fold
(fun access (dest_addr, _) astate ->
match (access : Memory.Access.t) with
| ArrayAccess _ ->
mark_invalid cause dest_addr astate
| _ ->
astate )
edges astate
let check_address_of_local_variable proc_desc address astate =
let proc_location = Procdesc.get_loc proc_desc in
let proc_name = Procdesc.get_proc_name proc_desc in
let check_address_of_cpp_temporary () =
Memory.find_opt address astate.heap
|> Option.fold_result ~init:() ~f:(fun () (_, attrs) ->
Container.fold_result ~fold:(IContainer.fold_of_pervasives_fold ~fold:Attributes.fold)
attrs ~init:() ~f:(fun () attr ->
match attr with
| Attribute.AddressOfCppTemporary (variable, location_opt) ->
let location = Option.value ~default:proc_location location_opt in
Error (PulseDiagnostic.StackVariableAddressEscape {variable; location})
| _ ->
Ok () ) )
in
let check_address_of_stack_variable () =
Container.fold_result ~fold:(IContainer.fold_of_pervasives_map_fold ~fold:Stack.fold)
astate.stack ~init:() ~f:(fun () (variable, (var_address, init_location)) ->
if
AbstractAddress.equal var_address address
&& ( Var.is_cpp_temporary variable
|| Var.is_local_to_procedure proc_name variable
&& not (Procdesc.is_captured_var proc_desc variable) )
then
let location = Option.value ~default:proc_location init_location in
Error (PulseDiagnostic.StackVariableAddressEscape {variable; location})
else Ok () )
in
check_address_of_cpp_temporary () >>= check_address_of_stack_variable >>| fun () -> astate
let mark_address_of_cpp_temporary location variable address heap =
Memory.add_attributes address
(Attributes.singleton (AddressOfCppTemporary (variable, location)))
heap
let remove_vars vars astate =
let heap =
List.fold vars ~init:astate.heap ~f:(fun heap var ->
match Stack.find_opt var astate.stack with
| Some (address, location) when Var.is_cpp_temporary var ->
(* TODO: it would be good to record the location of the temporary creation in the
stack and save it here in the attribute for reporting *)
mark_address_of_cpp_temporary location var address heap
| _ ->
heap )
in
let stack = List.fold ~f:(fun stack var -> Stack.remove var stack) ~init:astate.stack vars in
if phys_equal stack astate.stack && phys_equal heap astate.heap then astate else {stack; heap}
let record_var_decl_location location var astate =
let addr =
match Stack.find_opt var astate.stack with
| Some (addr, _) ->
addr
| None ->
AbstractAddress.mk_fresh ()
in
let stack = Stack.add var (addr, Some location) astate.stack in
{astate with stack}
module Closures = struct
open Result.Monad_infix
let check_captured_addresses location lambda addr astate =
match Memory.find_opt addr astate.heap with
| None ->
Ok astate
| Some (_, attributes) ->
IContainer.iter_result ~fold:(IContainer.fold_of_pervasives_fold ~fold:Attributes.fold)
attributes ~f:(function
| Attribute.Closure (_, captured) ->
IContainer.iter_result ~fold:List.fold captured ~f:(fun addr_trace ->
check_addr_access {access_expr= lambda; location} addr_trace astate
>>| fun _ -> () )
| _ ->
Ok () )
>>| fun () -> astate
let write location access_expr pname captured astate =
let closure_addr = AbstractAddress.mk_fresh () in
write location access_expr
(closure_addr, [PulseTrace.Assignment {lhs= access_expr; location}])
astate
>>| fun astate ->
{ astate with
heap=
Memory.add_attributes closure_addr
(Attributes.singleton (Closure (pname, captured)))
astate.heap }
let record location access_expr pname captured astate =
List.fold_result captured ~init:(astate, [])
~f:(fun ((astate, captured) as result) (captured_as, captured_exp) ->
match captured_exp with
| HilExp.AccessExpression (AddressOf access_expr as captured_access_expr) ->
read location access_expr astate
>>= fun (astate, (address, trace)) ->
let new_trace =
PulseTrace.Capture {captured_as; captured= captured_access_expr; location} :: trace
in
Ok (astate, (address, new_trace) :: captured)
| _ ->
Ok result )
>>= fun (astate, captured_addresses) ->
write location access_expr pname captured_addresses astate
end
module StdVector = struct
open Result.Monad_infix
let is_reserved location vector_access_expr astate =
read location vector_access_expr astate
>>| fun (astate, (addr, _)) -> (astate, Memory.is_std_vector_reserved addr astate.heap)
let mark_reserved location vector_access_expr astate =
read location vector_access_expr astate
>>| fun (astate, (addr, _)) -> {astate with heap= Memory.std_vector_reserve addr astate.heap}
end