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.

610 lines
22 KiB

(*
* Copyright (c) 2013 - 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.
*)
open! IStd
(** Module for user-defined checkers. *)
module L = Logging
module F = Format
let verbose = ref true
(** Convenience functions for checkers to print information *)
module PP = struct
(** Print a range of lines of the source file in [loc], including [nbefore] lines before loc
and [nafter] lines after [loc] *)
let pp_loc_range linereader nbefore nafter fmt loc =
let printline n =
match Printer.LineReader.from_loc linereader { loc with Location.line = n } with
| Some s -> F.fprintf fmt "%s%s@\n" (if n = loc.Location.line then "-->" else " ") s
| _ -> () in
F.fprintf fmt "%a:%d@\n" SourceFile.pp loc.Location.file loc.Location.line;
for n = loc.Location.line - nbefore to loc.Location.line + nafter do printline n done
end (* PP *)
(** State that persists in the .specs files. *)
module ST = struct
let add summary key value =
ProcAttributes.proc_flags_add summary.Specs.attributes.ProcAttributes.proc_flags key value
let pname_add proc_name key value =
let summary = Specs.get_summary_unsafe "ST.pname_add" proc_name in
add summary key value
let files_open = ref Procname.Set.empty
let pname_find proc_name key =
if Procname.Set.mem proc_name !files_open then
let summary = Specs.get_summary_unsafe "ST.pname_find" proc_name in
ProcAttributes.proc_flags_find summary.Specs.attributes.ProcAttributes.proc_flags key
else begin
match Specs.get_summary proc_name with
| None -> raise Not_found
| Some summary ->
begin
files_open := Procname.Set.add proc_name !files_open;
ProcAttributes.proc_flags_find summary.Specs.attributes.ProcAttributes.proc_flags key
end
end
let store_summary proc_name =
Option.iter
~f:(fun summary ->
let summary' =
{ summary with
Specs.timestamp = summary.Specs.timestamp + 1 } in
try Specs.store_summary proc_name summary' with Sys_error s -> L.err "%s@." s)
(Specs.get_summary proc_name)
let report_error tenv
proc_name
proc_desc
kind
loc
?(advice = None)
?(field_name = None)
?(origin_loc = None)
?(exception_kind = fun k d -> Exceptions.Checkers (k, d))
?(always_report = false)
description =
let lookup = Tenv.lookup tenv in
let localized_description = Localise.custom_desc_with_advice
description
(Option.value ~default:"" advice)
[("always_report", string_of_bool always_report)] in
let exn = exception_kind kind localized_description in
let proc_attributes = Specs.pdesc_resolve_attributes proc_desc in
(* Errors can be suppressed with annotations. An error of kind CHECKER_ERROR_NAME can be
suppressed with the following annotations:
- @android.annotation.SuppressLint("checker-error-name")
- @some.PrefixErrorName
where the kind matching is case - insensitive and ignores '-' and '_' characters. *)
let suppressed =
let annotation_matches (a: Annot.t) =
let normalize str =
Str.global_replace (Str.regexp "[_-]") "" (String.lowercase str) in
let drop_prefix str =
Str.replace_first (Str.regexp "^[A-Za-z]+_") "" str in
let normalized_equal s1 s2 =
String.equal (normalize s1) (normalize s2) in
let is_parameter_suppressed =
IList.mem String.equal a.class_name [Annotations.suppressLint] &&
IList.mem normalized_equal kind a.parameters in
let is_annotation_suppressed =
String.is_suffix ~suffix:(normalize (drop_prefix kind)) (normalize a.class_name) in
is_parameter_suppressed || is_annotation_suppressed in
let is_method_suppressed =
Annotations.ma_has_annotation_with
proc_attributes.ProcAttributes.method_annotation
annotation_matches in
let is_field_suppressed =
match field_name, PatternMatch.get_this_type proc_attributes with
| Some field_name, Some t -> begin
match StructTyp.get_field_type_and_annotation ~lookup field_name t with
| Some (_, ia) -> Annotations.ia_has_annotation_with ia annotation_matches
| None -> false
end
| _ -> false in
let is_class_suppressed =
match PatternMatch.get_this_type proc_attributes with
| Some t -> begin
match (PatternMatch.type_get_annotation tenv t) with
| Some ia -> Annotations.ia_has_annotation_with ia annotation_matches
| None -> false
end
| None -> false in
is_method_suppressed || is_field_suppressed || is_class_suppressed in
let trace =
let origin_elements =
match origin_loc with
| Some oloc -> [Errlog.make_trace_element 0 oloc "origin" []]
| None -> [] in
origin_elements @ [Errlog.make_trace_element 0 loc description []]
in
if not suppressed then
begin
if !verbose then
begin
L.stdout "%s: %a: %s@."
kind
SourceFile.pp loc.Location.file
(Procname.to_string proc_name);
L.stdout "%s@." description
end;
Reporting.log_error proc_name ~loc ~ltr:trace exn
end
end
let report_calls_and_accesses tenv callback proc_desc instr =
let proc_name = Procdesc.get_proc_name proc_desc in
let callee = Procname.to_string proc_name in
match PatternMatch.get_java_field_access_signature instr with
| Some (bt, fn, ft) ->
ST.report_error tenv
proc_name
proc_desc
(callback ^ "_CALLBACK")
(Procdesc.get_loc proc_desc)
(Format.sprintf "field access %s.%s:%s in %s@." bt fn ft callee)
| None ->
match PatternMatch.get_java_method_call_formal_signature instr with
| Some (bt, fn, _, rt) ->
ST.report_error tenv
proc_name
proc_desc
(callback ^ "_CALLBACK")
(Procdesc.get_loc proc_desc)
(Format.sprintf "method call %s.%s(%s):%s in %s@." bt fn "..." rt callee)
| None -> ()
(** Report all field accesses and method calls of a procedure. *)
let callback_check_access { Callbacks.tenv; proc_desc } =
Procdesc.iter_instrs
(fun _ instr -> report_calls_and_accesses tenv "PROC" proc_desc instr)
proc_desc
(** Report all field accesses and method calls of a class. *)
let callback_check_cluster_access exe_env all_procs get_proc_desc _ =
IList.iter (fun proc_name ->
match get_proc_desc proc_name with
| Some proc_desc ->
let tenv = Exe_env.get_tenv exe_env proc_name in
Procdesc.iter_instrs
(fun _ instr -> report_calls_and_accesses tenv "CLUSTER" proc_desc instr)
proc_desc
| _ ->
()
) all_procs
(** Looks for writeToParcel methods and checks whether read is in reverse *)
let callback_check_write_to_parcel_java
pname_java ({ Callbacks.tenv; proc_desc; idenv; get_proc_desc } as args) =
let verbose = ref false in
let is_write_to_parcel this_expr this_type =
let method_match () =
Procname.java_get_method pname_java = "writeToParcel" in
let expr_match () = Exp.is_this this_expr in
let type_match () =
let class_name =
Typename.TN_csu (Csu.Class Csu.Java, Mangled.from_string "android.os.Parcelable") in
match this_type with
| Typ.Tptr (Tstruct name, _) | Tstruct name ->
PatternMatch.is_immediate_subtype tenv name class_name
| _ -> false in
method_match () && expr_match () && type_match () in
let is_parcel_constructor proc_name =
Procname.is_constructor proc_name &&
PatternMatch.has_formal_method_argument_type_names
proc_desc pname_java ["android.os.Parcel"] in
let parcel_constructors tenv typ =
match typ with
| Typ.Tptr (Tstruct name, _) -> (
match Tenv.lookup tenv name with
| Some { methods } -> IList.filter is_parcel_constructor methods
| None -> []
)
| _ -> [] in
let check r_desc w_desc =
let is_serialization_node node =
match Procdesc.Node.get_callees node with
| [] -> false
| [Procname.Java pname_java] ->
let class_name = Procname.java_get_class_name pname_java in
let method_name = Procname.java_get_method pname_java in
(try
class_name = "android.os.Parcel" &&
(String.sub method_name ~pos:0 ~len:5 = "write"
||
String.sub method_name ~pos:0 ~len:4 = "read")
with Invalid_argument _ -> false)
| _ -> assert false in
let is_inverse rc_ wc_ = match rc_, wc_ with
| Procname.Java rc, Procname.Java wc ->
let rn = Procname.java_get_method rc in
let wn = Procname.java_get_method wc in
let postfix_length = String.length wn - 5 in (* covers writeList <-> readArrayList etc. *)
(try
String.sub rn ~pos:(String.length rn - postfix_length) ~len:postfix_length =
String.sub wn ~pos:5 ~len:postfix_length
with Invalid_argument _ -> false)
| _ ->
false in
let node_to_call_desc node =
match Procdesc.Node.get_callees node with
| [desc] -> desc
| _ -> assert false in
let r_call_descs =
IList.map node_to_call_desc
(IList.filter is_serialization_node
(Procdesc.get_sliced_slope r_desc is_serialization_node)) in
let w_call_descs =
IList.map node_to_call_desc
(IList.filter is_serialization_node
(Procdesc.get_sliced_slope w_desc is_serialization_node)) in
let rec check_match = function
| rc:: rcs, wc:: wcs ->
if not (is_inverse rc wc) then
L.stdout "Serialization missmatch in %a for %a and %a@."
Procname.pp args.Callbacks.proc_name
Procname.pp rc
Procname.pp wc
else
check_match (rcs, wcs)
| rc:: _, [] ->
L.stdout "Missing write in %a: for %a@."
Procname.pp args.Callbacks.proc_name Procname.pp rc
| _, wc:: _ ->
L.stdout "Missing read in %a: for %a@."
Procname.pp args.Callbacks.proc_name Procname.pp wc
| _ ->
() in
check_match (r_call_descs, w_call_descs) in
let do_instr _ instr = match instr with
| Sil.Call (_, Exp.Const (Const.Cfun _), (_this_exp, this_type):: _, _, _) ->
let this_exp = Idenv.expand_expr idenv _this_exp in
if is_write_to_parcel this_exp this_type then begin
if !verbose then
L.stdout "Serialization check for %a@."
Procname.pp args.Callbacks.proc_name;
try
match parcel_constructors tenv this_type with
| x :: _ ->
(match get_proc_desc x with
| Some x_proc_desc -> check x_proc_desc proc_desc
| None -> raise Not_found)
| _ ->
L.stdout "No parcel constructor found for %a@."
Procname.pp args.Callbacks.proc_name
with Not_found ->
if !verbose then L.stdout "Methods not available@."
end
| _ -> () in
Procdesc.iter_instrs do_instr proc_desc
(** Looks for writeToParcel methods and checks whether read is in reverse *)
let callback_check_write_to_parcel ({ Callbacks.proc_name } as args) =
match proc_name with
| Procname.Java pname_java ->
callback_check_write_to_parcel_java pname_java args
| _ ->
()
(** Monitor calls to Preconditions.checkNotNull and detect inconsistent uses. *)
let callback_monitor_nullcheck { Callbacks.proc_desc; idenv; proc_name } =
let verbose = ref false in
let class_formal_names = lazy (
let formals = Procdesc.get_formals proc_desc in
let class_formals =
let is_class_type (p, typ) =
match typ with
| Typ.Tptr _ when Mangled.to_string p = "this" ->
false (* no need to null check 'this' *)
| Typ.Tstruct _ -> true
| Typ.Tptr (Typ.Tstruct _, _) -> true
| _ -> false in
IList.filter is_class_type formals in
IList.map fst class_formals) in
let equal_formal_param exp formal_name = match exp with
| Exp.Lvar pvar ->
let name = Pvar.get_name pvar in
Mangled.equal name formal_name
| _ -> false in
let is_formal_param exp =
IList.exists (equal_formal_param exp) (Lazy.force class_formal_names) in
let is_nullcheck pn = match pn with
| Procname.Java pn_java ->
PatternMatch.java_proc_name_with_class_method
pn_java "com.google.common.base.Preconditions" "checkNotNull"
| _ ->
false in
let checks_to_formals = ref Exp.Set.empty in
let handle_check_of_formal e =
let repeated = Exp.Set.mem e !checks_to_formals in
if repeated && !verbose then L.stdout "Repeated Null Check of Formal: %a@." Exp.pp e
else begin
checks_to_formals := Exp.Set.add e !checks_to_formals;
if !verbose then L.stdout "Null Check of Formal: %a@." Exp.pp e
end in
let summary_checks_of_formals () =
let formal_names = Lazy.force class_formal_names in
let nchecks = Exp.Set.cardinal !checks_to_formals in
let nformals = IList.length formal_names in
if (nchecks > 0 && nchecks < nformals) then
begin
let was_not_found formal_name =
not (Exp.Set.exists (fun exp -> equal_formal_param exp formal_name) !checks_to_formals) in
let missing = IList.filter was_not_found formal_names in
let loc = Procdesc.get_loc proc_desc in
let pp_file_loc fmt () =
F.fprintf fmt "%a:%d" SourceFile.pp loc.Location.file loc.Location.line in
L.stdout "Null Checks of Formal Parameters: ";
L.stdout "%d out of %d parameters checked (missing checks on: %a)[%a]@."
nchecks nformals (Pp.seq Mangled.pp) missing pp_file_loc ();
let linereader = Printer.LineReader.create () in
L.stdout "%a@." (PP.pp_loc_range linereader 10 10) loc
end in
let do_instr _ instr = match instr with
| Sil.Call (_, Exp.Const (Const.Cfun pn), (_arg1, _):: _, _, _) when is_nullcheck pn ->
let arg1 = Idenv.expand_expr idenv _arg1 in
if is_formal_param arg1 then handle_check_of_formal arg1;
if !verbose then
(match proc_name with
| Procname.Java pname_java ->
L.stdout "call in %s %s: %a with first arg: %a@."
(Procname.java_get_class_name pname_java)
(Procname.java_get_method pname_java)
(Sil.pp_instr Pp.text) instr
Exp.pp arg1
| _ ->
())
| _ -> () in
Procdesc.iter_instrs do_instr proc_desc;
summary_checks_of_formals ()
(** Test persistent state. *)
let callback_test_state { Callbacks.proc_name } =
ST.pname_add proc_name "somekey" "somevalue"
(** Check the uses of VisibleForTesting *)
let callback_checkVisibleForTesting { Callbacks.proc_desc } =
if Annotations.pdesc_has_annot proc_desc Annotations.visibleForTesting then
begin
let loc = Procdesc.get_loc proc_desc in
let linereader = Printer.LineReader.create () in
L.stdout "%a@." (PP.pp_loc_range linereader 10 10) loc
end
(** Check for readValue and readValueAs json deserialization *)
let callback_find_deserialization { Callbacks.proc_desc; get_proc_desc; idenv; proc_name } =
let verbose = true in
let ret_const_key = "return_const" in
let reverse_find_instr f node =
(* this is not really sound but for the moment a sufficient approximation *)
let has_instr node =
try ignore(IList.find f (Procdesc.Node.get_instrs node)); true
with Not_found -> false in
let preds =
Procdesc.Node.get_generated_slope
node
(fun n -> Procdesc.Node.get_sliced_preds n has_instr) in
let instrs =
IList.flatten
(IList.map (fun n -> IList.rev (Procdesc.Node.get_instrs n)) preds) in
try
Some (IList.find f instrs)
with Not_found -> None in
let get_return_const proc_name' =
try
ST.pname_find proc_name' ret_const_key
with Not_found ->
match get_proc_desc proc_name' with
Some proc_desc' ->
let is_return_instr = function
| Sil.Store (Exp.Lvar p, _, _, _)
when Pvar.equal p (Procdesc.get_ret_var proc_desc') -> true
| _ -> false in
(match reverse_find_instr is_return_instr (Procdesc.get_exit_node proc_desc') with
| Some (Sil.Store (_, _, Exp.Const (Const.Cclass n), _)) -> Ident.name_to_string n
| _ -> "<" ^ (Procname.to_string proc_name') ^ ">")
| None -> "?" in
let get_actual_arguments node instr = match instr with
| Sil.Call (_, Exp.Const (Const.Cfun _), _:: args, _, _) ->
(try
let find_const exp =
let expanded = Idenv.expand_expr idenv exp in
match expanded with
| Exp.Const (Const.Cclass n) -> Ident.name_to_string n
| Exp.Lvar _ -> (
let is_call_instr set call = match set, call with
| Sil.Store (_, _, Exp.Var (i1), _), Sil.Call (Some (i2, _), _, _, _, _)
when Ident.equal i1 i2 -> true
| _ -> false in
let is_set_instr = function
| Sil.Store (e1, _, _, _) when Exp.equal expanded e1 -> true
| _ -> false in
match reverse_find_instr is_set_instr node with
(* Look for ivar := tmp *)
| Some s -> (
match reverse_find_instr (is_call_instr s) node with
(* Look for tmp := foo() *)
| Some (Sil.Call (_, Exp.Const (Const.Cfun pn), _, _, _)) ->
get_return_const pn
| _ -> "?")
| _ -> "?")
| _ -> "?" in
let arg_name (exp, _) = find_const exp in
Some (IList.map arg_name args)
with _ -> None)
| _ -> None in
let process_result instr result =
if verbose then (
let linereader = Printer.LineReader.create () in
L.stdout "%a@." (PP.pp_loc_range linereader 2 2) (Sil.instr_get_loc instr);
);
match result with
| str when (Str.string_match (Str.regexp "<\\(.*\\)>") str 0) -> (
let missing_proc_name = Str.matched_group 1 str in
L.stdout "Deserialization of %s requires 2nd phase: " str;
L.stdout "missing: %s@." missing_proc_name)
| "?" -> L.stdout "Unable to resolve deserialization\n\n@."
| _ -> L.stdout "Deserialization of %s\n\n@." result in
let do_instr node instr =
match PatternMatch.get_java_method_call_formal_signature instr with
| Some (_, "readValue", _, _) -> (
match get_actual_arguments node instr with
| Some [_; cl] -> process_result instr cl
| _ -> process_result instr "?")
| Some (_, "readValueAs", _, _) -> (
match get_actual_arguments node instr with
| Some [cl] -> process_result instr cl
| _ -> process_result instr "?")
| _ -> () in
let store_return () =
let ret_const = get_return_const proc_name in
ST.pname_add proc_name ret_const_key ret_const in
store_return ();
Procdesc.iter_instrs do_instr proc_desc
(** Check field accesses. *)
let callback_check_field_access { Callbacks.proc_desc } =
let rec do_exp is_read = function
| Exp.Var _ -> ()
| Exp.UnOp (_, e, _) ->
do_exp is_read e
| Exp.BinOp (_, e1, e2) ->
do_exp is_read e1;
do_exp is_read e2
| Exp.Exn _ -> ()
| Exp.Closure _ -> ()
| Exp.Const _ -> ()
| Exp.Cast (_, e) ->
do_exp is_read e
| Exp.Lvar _ -> ()
| Exp.Lfield (e, fn, _) ->
if not (Ident.java_fieldname_is_outer_instance fn) then
L.stdout "field %s %s@." (Ident.fieldname_to_string fn) (if is_read then "reading" else "writing");
do_exp is_read e
| Exp.Lindex (e1, e2) ->
do_exp is_read e1;
do_exp is_read e2
| Exp.Sizeof _ -> () in
let do_read_exp = do_exp true in
let do_write_exp = do_exp false in
let do_instr _ = function
| Sil.Load (_, e, _, _) ->
do_read_exp e
| Sil.Store (e1, _, e2, _) ->
do_write_exp e1;
do_read_exp e2
| Sil.Prune (e, _, _, _) ->
do_read_exp e
| Sil.Call (_, e, etl, _, _) ->
do_read_exp e;
IList.iter (fun (e, _) -> do_read_exp e) etl
| Sil.Nullify _
| Sil.Abstract _
| Sil.Remove_temps _
| Sil.Declare_locals _ ->
() in
Procdesc.iter_instrs do_instr proc_desc
(** Print c method calls. *)
let callback_print_c_method_calls { Callbacks.tenv; proc_desc; proc_name } =
let do_instr node = function
| Sil.Call (_, Exp.Const (Const.Cfun pn), (e, _):: _, loc, _)
when Procname.is_c_method pn ->
let receiver = match Errdesc.exp_rv_dexp tenv node e with
| Some de -> DecompiledExp.to_string de
| None -> "?" in
let description =
Printf.sprintf "['%s' %s]" receiver (Procname.to_string pn) in
ST.report_error tenv
proc_name
proc_desc
"CHECKERS_PRINT_OBJC_METHOD_CALLS"
loc
description
| Sil.Call (_, Exp.Const (Const.Cfun pn), _, loc, _) ->
let description =
Printf.sprintf "call to %s" (Procname.to_string pn) in
ST.report_error tenv
proc_name
proc_desc
"CHECKERS_PRINT_C_CALL"
loc
description
| _ -> () in
Procdesc.iter_instrs do_instr proc_desc
(** Print access to globals. *)
let callback_print_access_to_globals { Callbacks.tenv; proc_desc; proc_name } =
let do_pvar is_read pvar loc =
let description =
Printf.sprintf "%s of global %s"
(if is_read then "read" else "write")
(Pvar.to_string pvar) in
ST.report_error tenv
proc_name
proc_desc
"CHECKERS_ACCESS_GLOBAL"
loc
description in
let rec get_global_var = function
| Exp.Lvar pvar when Pvar.is_global pvar ->
Some pvar
| Exp.Lfield (e, _, _) ->
get_global_var e
| _ ->
None in
let do_instr _ = function
| Sil.Load (_, e, _, loc) when get_global_var e <> None ->
Option.iter ~f:(fun pvar -> do_pvar true pvar loc) (get_global_var e)
| Sil.Store (e, _, _, loc) when get_global_var e <> None ->
Option.iter ~f:(fun pvar -> do_pvar false pvar loc) (get_global_var e)
| _ -> () in
Procdesc.iter_instrs do_instr proc_desc