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.
176 lines
6.0 KiB
176 lines
6.0 KiB
(*
|
|
* Copyright (c) 2014 - present Facebook, Inc.
|
|
* All rights reserved.
|
|
*
|
|
* This source code is licensed under the BSD style license found in the
|
|
* LICENSE file in the root directory of this source tree. An additional grant
|
|
* of patent rights can be found in the PATENTS file in the same directory.
|
|
*)
|
|
|
|
module L = Logging
|
|
module F = Format
|
|
open Utils
|
|
|
|
let checkers_repeated_calls_name = "CHECKERS_REPEATED_CALLS"
|
|
|
|
(* activate the check for repeated calls *)
|
|
let checkers_repeated_calls = Config.from_env_variable checkers_repeated_calls_name
|
|
|
|
|
|
(** Extension for the repeated calls check. *)
|
|
module RepeatedCallsExtension : Eradicate.ExtensionT =
|
|
struct
|
|
module InstrSet =
|
|
Set.Make(struct
|
|
type t = Sil.instr
|
|
let compare i1 i2 = match i1, i2 with
|
|
| Sil.Call (ret1, e1, etl1, loc1, cf1), Sil.Call (ret2, e2, etl2, loc2, cf2) ->
|
|
(* ignore return ids and call flags *)
|
|
let n = Sil.exp_compare e1 e2 in
|
|
if n <> 0 then n else let n = list_compare Sil.exp_typ_compare etl1 etl2 in
|
|
if n <> 0 then n else Sil.call_flags_compare cf1 cf2
|
|
| _ -> Sil.instr_compare i1 i2
|
|
end)
|
|
|
|
type extension = InstrSet.t
|
|
|
|
let empty = InstrSet.empty
|
|
|
|
let join calls1 calls2 =
|
|
InstrSet.inter calls1 calls2
|
|
|
|
let pp fmt calls =
|
|
let pp_call instr = F.fprintf fmt " %a@\n" (Sil.pp_instr pe_text) instr in
|
|
if not (InstrSet.is_empty calls) then
|
|
begin
|
|
F.fprintf fmt "Calls:@\n";
|
|
InstrSet.iter pp_call calls;
|
|
end
|
|
|
|
let get_old_call instr calls =
|
|
try
|
|
Some (InstrSet.find instr calls)
|
|
with Not_found -> None
|
|
|
|
let add_call instr calls =
|
|
if InstrSet.mem instr calls then calls
|
|
else InstrSet.add instr calls
|
|
|
|
type paths =
|
|
| AllPaths (** Check on all paths *)
|
|
| SomePath (** Check if some path exists *)
|
|
|
|
(** Check if the procedure performs an allocation operation.
|
|
If [paths] is AllPaths, check if an allocation happens on all paths.
|
|
If [paths] is SomePath, check if a path with an allocation exists. *)
|
|
let proc_performs_allocation pdesc paths : Sil.location option =
|
|
|
|
let node_allocates node : Sil.location option =
|
|
let found = ref None in
|
|
let proc_is_new pn =
|
|
Procname.equal pn SymExec.ModelBuiltins.__new ||
|
|
Procname.equal pn SymExec.ModelBuiltins.__new_array in
|
|
let do_instr instr =
|
|
match instr with
|
|
| Sil.Call (_, Sil.Const (Sil.Cfun pn), _, loc, _) when proc_is_new pn ->
|
|
found := Some loc
|
|
| _ -> () in
|
|
list_iter do_instr (Cfg.Node.get_instrs node);
|
|
!found in
|
|
|
|
let module DFAllocCheck = Dataflow.MakeDF(struct
|
|
type t = Sil.location option
|
|
let equal = opt_equal Sil.loc_equal
|
|
let _join _paths l1o l2o = (* join with left priority *)
|
|
match l1o, l2o with
|
|
| None, None ->
|
|
None
|
|
| Some loc, None
|
|
| None, Some loc ->
|
|
if _paths = AllPaths then None else Some loc
|
|
| Some loc1, Some loc2 ->
|
|
Some loc1 (* left priority *)
|
|
let join = _join paths
|
|
let do_node node lo1 =
|
|
let lo2 = node_allocates node in
|
|
let lo' = (* use left priority join to implement transfer function *)
|
|
_join SomePath lo1 lo2 in
|
|
[lo'], [lo']
|
|
let proc_throws pn = Dataflow.DontKnow
|
|
end) in
|
|
|
|
if Cfg.Procdesc.is_defined pdesc then
|
|
let transitions = DFAllocCheck.run pdesc None in
|
|
match transitions (Cfg.Procdesc.get_exit_node pdesc) with
|
|
| DFAllocCheck.Transition (loc, _, _) -> loc
|
|
| DFAllocCheck.Dead_state -> None
|
|
else None
|
|
|
|
(** Check repeated calls to the same procedure. *)
|
|
let check_instr get_proc_desc curr_pname curr_pdesc node extension instr normalized_etl =
|
|
|
|
(** Arguments are not temporary variables. *)
|
|
let arguments_not_temp args =
|
|
let filter_arg (e, t) = match e with
|
|
| Sil.Lvar pvar ->
|
|
(* same temporary variable does not imply same value *)
|
|
not (Errdesc.pvar_is_frontend_tmp pvar)
|
|
| _ -> true in
|
|
list_for_all filter_arg args in
|
|
|
|
match instr with
|
|
| Sil.Call (ret_ids, Sil.Const (Sil.Cfun callee_pname), _, loc, call_flags)
|
|
when ret_ids <> [] && arguments_not_temp normalized_etl ->
|
|
let instr_normalized_args = Sil.Call (
|
|
ret_ids,
|
|
Sil.Const (Sil.Cfun callee_pname),
|
|
normalized_etl,
|
|
loc,
|
|
call_flags) in
|
|
let report callee_pdesc =
|
|
match get_old_call instr_normalized_args extension with
|
|
| Some (Sil.Call (_, _, _, loc_old, _)) ->
|
|
begin
|
|
match proc_performs_allocation callee_pdesc AllPaths with
|
|
| Some alloc_loc ->
|
|
let description =
|
|
Printf.sprintf "call to %s seen before on line %d (may allocate at %s:%n)"
|
|
(Procname.to_simplified_string callee_pname)
|
|
loc_old.Sil.line
|
|
(DB.source_file_to_string alloc_loc.Sil.file)
|
|
alloc_loc.Sil.line in
|
|
Checkers.ST.report_error
|
|
curr_pname curr_pdesc checkers_repeated_calls_name loc description
|
|
| None -> ()
|
|
end
|
|
| _ -> () in
|
|
|
|
let () = match get_proc_desc callee_pname with
|
|
| None -> ()
|
|
| Some callee_pdesc -> report callee_pdesc in
|
|
add_call instr_normalized_args extension
|
|
| _ -> extension
|
|
|
|
let ext =
|
|
{
|
|
TypeState.empty = empty;
|
|
check_instr = check_instr;
|
|
join = join;
|
|
pp = pp;
|
|
}
|
|
|
|
let mkpayload typestate = Specs.TypeState None
|
|
end (* CheckRepeatedCalls *)
|
|
|
|
module MainRepeatedCalls =
|
|
Eradicate.Build(RepeatedCallsExtension)
|
|
|
|
let callback_check_repeated_calls all_procs get_proc_desc idenv tenv proc_name proc_desc =
|
|
let checks =
|
|
{
|
|
TypeCheck.eradicate = false;
|
|
check_extension = checkers_repeated_calls;
|
|
check_ret_type = [];
|
|
} in
|
|
MainRepeatedCalls.callback checks all_procs get_proc_desc idenv tenv proc_name proc_desc
|