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.

564 lines
22 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.
*)
open! IStd
module L = Logging
open PulseBasicInterface
open PulseDomainInterface
type t = AbductiveDomain.t
module Import = struct
type access_mode = Read | Write | NoAccess
type 'abductive_domain_t execution_domain_base_t = 'abductive_domain_t ExecutionDomain.base_t =
| ContinueProgram of 'abductive_domain_t
| ExitProgram of AbductiveDomain.summary
| AbortProgram of AbductiveDomain.summary
| LatentAbortProgram of {astate: AbductiveDomain.summary; latent_issue: LatentIssue.t}
| LatentInvalidAccess of
{ astate: AbductiveDomain.summary
; address: AbstractValue.t
; must_be_valid: Trace.t
; calling_context: (CallEvent.t * Location.t) list }
| ISLLatentMemoryError of AbductiveDomain.summary
type 'astate base_error = 'astate AccessResult.error =
| PotentialInvalidAccess of {astate: 'astate; address: AbstractValue.t; must_be_valid: Trace.t}
| PotentialInvalidAccessSummary of
{astate: AbductiveDomain.summary; address: AbstractValue.t; must_be_valid: Trace.t}
| ReportableError of {astate: 'astate; diagnostic: Diagnostic.t}
| ISLError of 'astate
include IResult.Let_syntax
let ( let<*> ) x f = match x with Error _ as err -> [err] | Ok y -> f y
let ( let<+> ) x f =
match x with Error _ as err -> [err] | Ok y -> [Ok (ExecutionDomain.ContinueProgram (f y))]
end
include Import
let check_addr_access access_mode location (address, history) astate =
let access_trace = Trace.Immediate {location; history} in
let* astate =
AddressAttributes.check_valid access_trace address astate
|> Result.map_error ~f:(fun (invalidation, invalidation_trace) ->
ReportableError
{ diagnostic=
Diagnostic.AccessToInvalidAddress
{calling_context= []; invalidation; invalidation_trace; access_trace}
; astate } )
in
match access_mode with
| Read ->
AddressAttributes.check_initialized access_trace address astate
|> Result.map_error ~f:(fun () ->
ReportableError
{ diagnostic=
Diagnostic.ReadUninitializedValue {calling_context= []; trace= access_trace}
; astate } )
| Write ->
Ok (AbductiveDomain.initialize address astate)
| NoAccess ->
Ok astate
let check_and_abduce_addr_access_isl access_mode location (address, history) ?(null_noop = false)
astate =
let access_trace = Trace.Immediate {location; history} in
AddressAttributes.check_valid_isl access_trace address ~null_noop astate
|> List.map ~f:(fun access_result ->
let* astate = AccessResult.of_abductive_access_result access_trace access_result in
match access_mode with
| Read ->
AddressAttributes.check_initialized access_trace address astate
|> Result.map_error ~f:(fun () ->
ReportableError
{ diagnostic=
Diagnostic.ReadUninitializedValue
{calling_context= []; trace= access_trace}
; astate } )
| Write ->
Ok (AbductiveDomain.initialize address astate)
| NoAccess ->
Ok astate )
module Closures = struct
module Memory = AbductiveDomain.Memory
let fake_capture_field_prefix = "__capture_"
let string_of_capture_mode = function
| Pvar.ByReference ->
"by_ref_"
| Pvar.ByValue ->
"by_value_"
let fake_captured_by_ref_field_prefix =
Printf.sprintf "%s%s" fake_capture_field_prefix (string_of_capture_mode Pvar.ByReference)
let mk_fake_field ~id mode =
Fieldname.make
(Typ.CStruct (QualifiedCppName.of_list ["std"; "function"]))
(Printf.sprintf "%s%s%d" fake_capture_field_prefix (string_of_capture_mode mode) id)
let is_captured_by_ref_fake_access (access : _ HilExp.Access.t) =
match access with
| FieldAccess fieldname
when String.is_prefix ~prefix:fake_captured_by_ref_field_prefix
(Fieldname.to_string fieldname) ->
true
| _ ->
false
let mk_capture_edges captured =
List.foldi captured ~init:Memory.Edges.empty ~f:(fun id edges (mode, addr, trace) ->
Memory.Edges.add (HilExp.Access.FieldAccess (mk_fake_field ~id mode)) (addr, trace) edges )
let check_captured_addresses action lambda_addr (astate : t) =
match AbductiveDomain.find_post_cell_opt lambda_addr astate with
| None ->
Ok astate
| Some (edges, attributes) ->
let+ () =
IContainer.iter_result ~fold:Attributes.fold attributes ~f:(function
| Attribute.Closure _ ->
IContainer.iter_result ~fold:Memory.Edges.fold edges ~f:(fun (access, addr_trace) ->
if is_captured_by_ref_fake_access access then
let+ _ = check_addr_access Read action addr_trace astate in
()
else Ok () )
| _ ->
Ok () )
in
astate
let record location pname captured astate =
let captured_addresses =
List.filter_map captured ~f:(fun (captured_as, (address_captured, trace_captured), mode) ->
let new_trace = ValueHistory.Capture {captured_as; mode; location} :: trace_captured in
Some (mode, address_captured, new_trace) )
in
let closure_addr_hist = (AbstractValue.mk_fresh (), [ValueHistory.Assignment location]) in
let fake_capture_edges = mk_capture_edges captured_addresses in
let astate =
AbductiveDomain.set_post_cell closure_addr_hist
(fake_capture_edges, Attributes.singleton (Closure pname))
location astate
in
(astate, closure_addr_hist)
end
let eval_var location hist var astate = Stack.eval location hist var astate
let eval_access mode location addr_hist access astate =
let+ astate = check_addr_access mode location addr_hist astate in
Memory.eval_edge addr_hist access astate
let eval_access_biad_isl mode location addr_hist access astate =
let map_ok addr_hist access results =
List.map results ~f:(fun result ->
let+ astate = result in
Memory.eval_edge addr_hist access astate )
in
let results = check_and_abduce_addr_access_isl mode location addr_hist astate in
map_ok addr_hist access results
let eval mode location exp0 astate =
let rec eval mode exp astate =
match (exp : Exp.t) with
| Var id ->
Ok (eval_var location (* error in case of missing history? *) [] (Var.of_id id) astate)
| Lvar pvar ->
Ok
(eval_var location
[ValueHistory.VariableAccessed (pvar, location)]
(Var.of_pvar pvar) astate)
| Lfield (exp', field, _) ->
let* astate, addr_hist = eval Read exp' astate in
eval_access mode location addr_hist (FieldAccess field) astate
| Lindex (exp', exp_index) ->
let* astate, addr_hist_index = eval Read exp_index astate in
let* astate, addr_hist = eval Read exp' astate in
eval_access mode location addr_hist (ArrayAccess (StdTyp.void, fst addr_hist_index)) astate
| Closure {name; captured_vars} ->
let+ astate, rev_captured =
List.fold_result captured_vars ~init:(astate, [])
~f:(fun (astate, rev_captured) (capt_exp, captured_as, _, mode) ->
let+ astate, addr_trace = eval Read capt_exp astate in
(astate, (captured_as, addr_trace, mode) :: rev_captured) )
in
Closures.record location name (List.rev rev_captured) astate
| Cast (_, exp') ->
eval mode exp' astate
| Const (Cint i) ->
let v = AbstractValue.Constants.get_int i in
let+ astate =
PulseArithmetic.and_eq_int v i astate
>>| AddressAttributes.invalidate
(v, [ValueHistory.Assignment location])
(ConstantDereference i) location
in
(astate, (v, []))
| UnOp (unop, exp, _typ) ->
let* astate, (addr, hist) = eval Read exp astate in
let unop_addr = AbstractValue.mk_fresh () in
let+ astate = PulseArithmetic.eval_unop unop_addr unop addr astate in
(astate, (unop_addr, hist))
| BinOp (bop, e_lhs, e_rhs) ->
let* astate, (addr_lhs, hist_lhs) = eval Read e_lhs astate in
(* NOTE: keeping track of only [hist_lhs] into the binop is not the best *)
let* astate, (addr_rhs, _hist_rhs) = eval Read e_rhs astate in
let binop_addr = AbstractValue.mk_fresh () in
let+ astate =
PulseArithmetic.eval_binop binop_addr bop (AbstractValueOperand addr_lhs)
(AbstractValueOperand addr_rhs) astate
in
(astate, (binop_addr, hist_lhs))
| Const _ | Sizeof _ | Exn _ ->
Ok (astate, (AbstractValue.mk_fresh (), (* TODO history *) []))
in
eval mode exp0 astate
let eval_to_operand mode location exp astate =
match (exp : Exp.t) with
| Const (Cint i) ->
Ok (astate, PulseArithmetic.LiteralOperand i)
| exp ->
let+ astate, (value, _) = eval mode location exp astate in
(astate, PulseArithmetic.AbstractValueOperand value)
let prune location ~condition astate =
let rec prune_aux ~negated exp astate =
match (exp : Exp.t) with
| BinOp (bop, exp_lhs, exp_rhs) ->
let* astate, lhs_op = eval_to_operand Read location exp_lhs astate in
let* astate, rhs_op = eval_to_operand Read location exp_rhs astate in
PulseArithmetic.prune_binop ~negated bop lhs_op rhs_op astate
| UnOp (LNot, exp', _) ->
prune_aux ~negated:(not negated) exp' astate
| exp ->
prune_aux ~negated (Exp.BinOp (Ne, exp, Exp.zero)) astate
in
prune_aux ~negated:false condition astate
let eval_deref location exp astate =
let* astate, addr_hist = eval Read location exp astate in
let+ astate = check_addr_access Read location addr_hist astate in
Memory.eval_edge addr_hist Dereference astate
let eval_structure_isl mode loc exp astate =
match (exp : Exp.t) with
| Lfield (exp', field, _) ->
let+ astate, addr_hist = eval mode loc exp' astate in
let astates = eval_access_biad_isl mode loc addr_hist (FieldAccess field) astate in
(false, astates)
| Lindex (exp', exp_index) ->
let* astate, addr_hist_index = eval mode loc exp_index astate in
let+ astate, addr_hist = eval mode loc exp' astate in
let astates =
eval_access_biad_isl mode loc addr_hist
(ArrayAccess (StdTyp.void, fst addr_hist_index))
astate
in
(false, astates)
| _ ->
let+ astate, (addr, history) = eval mode loc exp astate in
(true, [Ok (astate, (addr, history))])
let eval_deref_biad_isl location access addr_hist astate =
check_and_abduce_addr_access_isl Read location addr_hist astate
|> List.map ~f:(fun result ->
let+ astate = result in
Memory.eval_edge addr_hist access astate )
let eval_deref_isl location exp astate =
let<*> is_structured, ls_astate_addr_hist = eval_structure_isl Read location exp astate in
let eval_deref_function (astate, addr_hist) =
if is_structured then eval_deref_biad_isl location Dereference addr_hist astate
else [eval_deref location exp astate]
in
List.concat_map ls_astate_addr_hist ~f:(fun result ->
let<*> astate_addr = result in
eval_deref_function astate_addr )
let realloc_pvar tenv pvar typ location astate =
let addr = AbstractValue.mk_fresh () in
let astate =
Stack.add (Var.of_pvar pvar) (addr, [ValueHistory.VariableDeclared (pvar, location)]) astate
in
AbductiveDomain.set_uninitialized tenv (`LocalDecl (pvar, Some addr)) typ location astate
let write_id id new_addr_loc astate = Stack.add (Var.of_id id) new_addr_loc astate
let havoc_id id loc_opt astate =
(* Topl needs to track the return value of a method; even if nondet now, it may be pruned later. *)
if Topl.is_active () || Stack.mem (Var.of_id id) astate then
write_id id (AbstractValue.mk_fresh (), loc_opt) astate
else astate
let write_access location addr_trace_ref access addr_trace_obj astate =
check_addr_access Write location addr_trace_ref astate
>>| Memory.add_edge addr_trace_ref access addr_trace_obj location
let write_access_biad_isl location addr_trace_ref access addr_trace_obj astate =
let astates = check_and_abduce_addr_access_isl Write location addr_trace_ref astate in
List.map astates ~f:(fun result ->
let+ astate = result in
Memory.add_edge addr_trace_ref access addr_trace_obj location astate )
let write_deref location ~ref:addr_trace_ref ~obj:addr_trace_obj astate =
write_access location addr_trace_ref Dereference addr_trace_obj astate
let write_deref_biad_isl location ~ref:(addr_ref, addr_ref_history) access ~obj:addr_trace_obj
astate =
write_access_biad_isl location (addr_ref, addr_ref_history) access addr_trace_obj astate
let write_field location ~ref:addr_trace_ref field ~obj:addr_trace_obj astate =
write_access location addr_trace_ref (FieldAccess field) addr_trace_obj astate
let write_arr_index location ~ref:addr_trace_ref ~index ~obj:addr_trace_obj astate =
write_access location addr_trace_ref (ArrayAccess (StdTyp.void, index)) addr_trace_obj astate
let havoc_field location addr_trace field trace_obj astate =
write_field location ~ref:addr_trace field ~obj:(AbstractValue.mk_fresh (), trace_obj) astate
let allocate procname location addr_trace astate =
AddressAttributes.allocate procname addr_trace location astate
let add_dynamic_type typ address astate = AddressAttributes.add_dynamic_type typ address astate
let remove_allocation_attr address astate = AddressAttributes.remove_allocation_attr address astate
let invalidate location cause addr_trace astate =
check_addr_access NoAccess location addr_trace astate
>>| AddressAttributes.invalidate addr_trace cause location
let invalidate_biad_isl location cause (address, history) astate =
check_and_abduce_addr_access_isl NoAccess location (address, history) ~null_noop:true astate
|> List.map ~f:(fun result ->
let+ astate = result in
AddressAttributes.invalidate (address, history) cause location astate )
let invalidate_access location cause ref_addr_hist access astate =
let astate, (addr_obj, _) = Memory.eval_edge ref_addr_hist access astate in
invalidate location cause (addr_obj, snd ref_addr_hist) astate
let invalidate_array_elements location cause addr_trace astate =
let+ astate = check_addr_access NoAccess location addr_trace astate in
match Memory.find_opt (fst addr_trace) astate with
| None ->
astate
| Some edges ->
Memory.Edges.fold edges ~init:astate ~f:(fun astate (access, dest_addr_trace) ->
match (access : Memory.Access.t) with
| ArrayAccess _ ->
AddressAttributes.invalidate dest_addr_trace cause location astate
| _ ->
astate )
let shallow_copy location addr_hist astate =
let+ astate = check_addr_access Read location addr_hist astate in
let cell =
match AbductiveDomain.find_post_cell_opt (fst addr_hist) astate with
| None ->
(Memory.Edges.empty, Attributes.empty)
| Some cell ->
cell
in
let copy = (AbstractValue.mk_fresh (), [ValueHistory.Assignment location]) in
(AbductiveDomain.set_post_cell copy cell location astate, copy)
let check_address_escape escape_location proc_desc address history astate =
let is_assigned_to_global address astate =
let points_to_address pointer address astate =
Memory.find_edge_opt pointer Dereference astate
|> Option.exists ~f:(fun (pointee, _) -> AbstractValue.equal pointee address)
in
Stack.exists
(fun var (pointer, _) -> Var.is_global var && points_to_address pointer address astate)
astate
in
let check_address_of_cpp_temporary () =
AddressAttributes.find_opt address astate
|> Option.fold_result ~init:() ~f:(fun () attrs ->
IContainer.iter_result ~fold:Attributes.fold attrs ~f:(fun attr ->
match attr with
| Attribute.AddressOfCppTemporary (variable, _)
when not (is_assigned_to_global address astate) ->
(* The returned address corresponds to a C++ temporary. It will have gone out of
scope by now except if it was bound to a global. *)
Error
(ReportableError
{ diagnostic=
Diagnostic.StackVariableAddressEscape
{variable; location= escape_location; history}
; astate })
| _ ->
Ok () ) )
in
let check_address_of_stack_variable () =
let proc_name = Procdesc.get_proc_name proc_desc in
IContainer.iter_result ~fold:(IContainer.fold_of_pervasives_map_fold Stack.fold) astate
~f:(fun (variable, (var_address, _)) ->
if
AbstractValue.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 (
L.d_printfln_escaped "Stack variable address &%a detected at address %a" Var.pp variable
AbstractValue.pp address ;
Error
(ReportableError
{ diagnostic=
Diagnostic.StackVariableAddressEscape
{variable; location= escape_location; history}
; astate }) )
else Ok () )
in
let+ () = check_address_of_cpp_temporary () >>= check_address_of_stack_variable in
astate
let mark_address_of_cpp_temporary history variable address astate =
AddressAttributes.add_one address (AddressOfCppTemporary (variable, history)) astate
let mark_address_of_stack_variable history variable location address astate =
AddressAttributes.add_one address (AddressOfStackVariable (variable, location, history)) astate
let check_memory_leak_unreachable unreachable_addrs location astate =
let check_memory_leak result attributes =
let allocated_not_freed_opt =
Attributes.fold attributes ~init:(None (* allocation trace *), false (* freed *))
~f:(fun acc attr ->
match (attr : Attribute.t) with
| Allocated (procname, trace) ->
(Some (procname, trace), snd acc)
| Invalid (CFree, _) ->
(fst acc, true)
| _ ->
acc )
in
match allocated_not_freed_opt with
| Some (procname, trace), false ->
(* allocated but not freed *)
Error
(ReportableError
{ diagnostic= Diagnostic.MemoryLeak {procname; location; allocation_trace= trace}
; astate })
| _ ->
result
in
List.fold unreachable_addrs ~init:(Ok ()) ~f:(fun res addr ->
match AbductiveDomain.AddressAttributes.find_opt addr astate with
| Some unreachable_attrs ->
check_memory_leak res unreachable_attrs
| None ->
res )
let get_dynamic_type_unreachable_values vars astate =
(* For each unreachable address we find a root variable for it; if there is
more than one, it doesn't matter which *)
let find_var_opt astate addr =
Stack.fold
(fun var (var_addr, _) var_opt ->
if AbstractValue.equal addr var_addr then Some var else var_opt )
astate None
in
let astate' = Stack.remove_vars vars astate in
let _, _, unreachable_addrs = AbductiveDomain.discard_unreachable astate' in
let res =
List.fold unreachable_addrs ~init:[] ~f:(fun res addr ->
(let open IOption.Let_syntax in
let* attrs = AbductiveDomain.AddressAttributes.find_opt addr astate in
let* typ = Attributes.get_dynamic_type attrs in
let+ var = find_var_opt astate addr in
(var, addr, typ) :: res)
|> Option.value ~default:res )
in
List.map ~f:(fun (var, _, typ) -> (var, typ)) res
let remove_vars tenv vars location orig_astate =
let astate =
(* Simplification of [IsInstanceOf(var, typ)] term is necessary here, as a variable can die before
the normalization function is called. This could cause [IsInstanceOf(var, typ)] terms that
reference dead vars to be collected before they are evaluated to detect a contradiction *)
List.fold vars ~init:(AbductiveDomain.simplify_instanceof tenv orig_astate)
~f:(fun astate var ->
match Stack.find_opt var astate with
| Some (address, history) ->
let astate =
if Var.appears_in_source_code var && AbductiveDomain.is_local var astate then
mark_address_of_stack_variable history var location address astate
else astate
in
if Var.is_cpp_temporary var then
mark_address_of_cpp_temporary history var address astate
else astate
| _ ->
astate )
in
let astate' = Stack.remove_vars vars astate in
if phys_equal astate' astate then Ok astate
else
let astate, _, unreachable_addrs = AbductiveDomain.discard_unreachable astate' in
let+ () = check_memory_leak_unreachable unreachable_addrs location orig_astate in
astate
let get_captured_actuals location ~captured_vars ~actual_closure astate =
let* astate, this_value_addr = eval_access Read location actual_closure Dereference astate in
let+ _, astate, captured_vars_with_actuals =
List.fold_result captured_vars ~init:(0, astate, [])
~f:(fun (id, astate, captured) (var, mode) ->
let+ astate, captured_actual =
eval_access Read location this_value_addr
(FieldAccess (Closures.mk_fake_field ~id mode))
astate
in
(id + 1, astate, (var, captured_actual) :: captured) )
in
(astate, captured_vars_with_actuals)