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.

155 lines
6.1 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 F = Format
module L = Logging
open PulseBasicInterface
open PulseDomainInterface
let debug fmt = L.(debug Analysis Verbose fmt)
(* An impurity analysis that relies on pulse to determine how the state
changes *)
let get_matching_dest_addr_opt ~edges_pre ~edges_post : AbstractValue.t list option =
match
List.fold2 ~init:(Some [])
~f:(fun acc (_, (addr_dest_pre, _)) (_, (addr_dest_post, _)) ->
if AbstractValue.equal addr_dest_pre addr_dest_post then
Option.map acc ~f:(fun acc -> addr_dest_pre :: acc)
else None )
(BaseMemory.Edges.bindings edges_pre)
(BaseMemory.Edges.bindings edges_post)
with
| Unequal_lengths ->
debug "Mismatch in pre and post.\n" ;
None
| Ok x ->
x
let add_invalid_and_modified ~check_empty attrs acc =
let modified =
Attributes.get_written_to attrs
|> Option.value_map ~default:[] ~f:(fun modified -> [ImpurityDomain.WrittenTo modified])
in
let res =
Attributes.get_invalid attrs
|> Option.value_map ~default:modified ~f:(fun invalid ->
ImpurityDomain.Invalid invalid :: modified )
in
if check_empty && List.is_empty res then
L.(die InternalError) "Address is modified without being written to or invalidated."
else res @ acc
(** Given Pulse summary, extract impurity info, i.e. parameters and global variables that are
modified by the function.
TODO: keep track of impure library calls *)
let extract_impurity pdesc pre_post : ImpurityDomain.t =
let pre_heap = (AbductiveDomain.extract_pre pre_post).BaseDomain.heap in
let post_heap = (AbductiveDomain.extract_post pre_post).BaseDomain.heap in
let post_stack = (AbductiveDomain.extract_post pre_post).BaseDomain.stack in
let add_to_modified var addr acc =
let rec aux acc ~addr_to_explore ~visited : ImpurityDomain.trace list =
match addr_to_explore with
| [] ->
acc
| addr :: addr_to_explore -> (
if AbstractValue.Set.mem addr visited then aux acc ~addr_to_explore ~visited
else
let cell_pre_opt = BaseMemory.find_opt addr pre_heap in
let cell_post_opt = BaseMemory.find_opt addr post_heap in
let visited = AbstractValue.Set.add addr visited in
match (cell_pre_opt, cell_post_opt) with
| None, None ->
aux acc ~addr_to_explore ~visited
| Some (_, _pre_attrs), None ->
L.(die InternalError)
"It is unexpected to have an address which has a binding in pre but not in post!"
| None, Some (_edges_post, attrs_post) ->
aux
(add_invalid_and_modified ~check_empty:false attrs_post acc)
~addr_to_explore ~visited
| Some (edges_pre, _), Some (edges_post, attrs_post) -> (
match get_matching_dest_addr_opt ~edges_pre ~edges_post with
| Some addr_list ->
aux
(add_invalid_and_modified attrs_post ~check_empty:false acc)
~addr_to_explore:(addr_list @ addr_to_explore) ~visited
| None ->
aux
(add_invalid_and_modified ~check_empty:true attrs_post acc)
~addr_to_explore ~visited ) )
in
match aux [] ~addr_to_explore:[addr] ~visited:AbstractValue.Set.empty with
| [] ->
acc
| hd :: tl ->
ImpurityDomain.ModifiedVarSet.add {var; trace_list= (hd, tl)} acc
in
let pname = Procdesc.get_proc_name pdesc in
let modified_params =
Procdesc.get_formals pdesc
|> List.fold_left ~init:ImpurityDomain.ModifiedVarSet.empty ~f:(fun acc (name, typ) ->
let var = Var.of_pvar (Pvar.mk name pname) in
match BaseStack.find_opt var post_stack with
| Some (addr, _) when Typ.is_pointer typ -> (
match BaseMemory.find_opt addr pre_heap with
| Some (edges_pre, _) ->
BaseMemory.Edges.fold
(fun _ (addr, _) acc -> add_to_modified var addr acc)
edges_pre acc
| None ->
debug "The address is not materialized in pre-heap." ;
acc )
| _ ->
acc )
in
let modified_globals =
BaseStack.fold
(fun var (addr, _) acc -> if Var.is_global var then add_to_modified var addr acc else acc)
post_stack ImpurityDomain.ModifiedVarSet.empty
in
{modified_globals; modified_params}
let report_errors summary modified_opt =
let pdesc = Summary.get_proc_desc summary in
let proc_name = Procdesc.get_proc_name pdesc in
let pname_loc = Procdesc.get_loc pdesc in
let impure_fun_desc = F.asprintf "Impure function %a" Procname.pp proc_name in
let impure_fun_ltr = Errlog.make_trace_element 0 pname_loc impure_fun_desc [] in
( match modified_opt with
| None ->
Reporting.log_error summary ~loc:pname_loc ~ltr:[impure_fun_ltr] IssueType.impure_function
impure_fun_desc
| Some (ImpurityDomain.{modified_globals; modified_params} as astate) ->
if Purity.should_report proc_name && not (ImpurityDomain.is_pure astate) then
let modified_ltr param_source set acc =
ImpurityDomain.ModifiedVarSet.fold
(ImpurityDomain.add_to_errlog ~nesting:1 param_source)
set acc
in
let ltr =
impure_fun_ltr
:: modified_ltr Formal modified_params (modified_ltr Global modified_globals [])
in
Reporting.log_error summary ~loc:pname_loc ~ltr IssueType.impure_function impure_fun_desc ) ;
summary
let checker ({Callbacks.summary} as callback) : Summary.t =
let pulse_summary = Pulse.checker callback in
pulse_summary.payloads.pulse
|> Option.map ~f:(fun pre_posts ->
List.fold pre_posts ~init:ImpurityDomain.pure ~f:(fun acc pre_post ->
let modified = extract_impurity (Summary.get_proc_desc summary) pre_post in
ImpurityDomain.join acc modified ) )
|> report_errors summary