(* * Copyright (c) 2009 - 2013 Monoidics ltd. * 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 (** Support for localisation *) module F = Format module MF = MarkupFormatter module Tags = struct type t = (string * string) list [@@deriving compare] let accessed_line = "accessed_line" (* line where value was last accessed *) let alloc_function = "alloc_function" (* allocation function used *) let alloc_call = "alloc_call" (* call in the current procedure which triggers the allocation *) let alloc_line = "alloc_line" (* line of alloc_call *) let array_index = "array_index" (* index of the array *) let array_size = "array_size" (* size of the array *) let assigned_line = "assigned_line" (* line where value was last assigned *) let bucket = "bucket" (* bucket to classify likelihood of real bug *) let call_procedure = "call_procedure" (* name of the procedure called *) let call_line = "call_line" (* line of call_procedure *) let dealloc_function = "dealloc_function" (* deallocation function used *) let dealloc_call = "dealloc_call" (* call in the current procedure which triggers the deallocation *) let dealloc_line = "dealloc_line" (* line of dealloc_call *) let dereferenced_line = "dereferenced_line" (* line where value was dereferenced *) let escape_to = "escape_to" (* expression wher a value escapes to *) let line = "line" (* line of the error *) let type1 = "type1" (* 1st Java type *) let type2 = "type2" (* 2nd Java type *) let value = "value" (* string describing a C value, e.g. "x.date" *) let parameter_not_null_checked = "parameter_not_null_checked" (* describes a NPE that comes from parameter not nullable *) let field_not_null_checked = "field_not_null_checked" (* describes a NPE that comes from field not nullable *) let nullable_src = "nullable_src" (* @Nullable-annoted field/param/retval that causes a warning *) let weak_captured_var_src = "weak_captured_var_src" (* Weak variable captured in a block that causes a warning *) let double_lock = "double_lock" let empty_vector_access = "empty_vector_access" let create () = ref [] let add tags tag value = List.Assoc.add ~equal:String.equal tags tag value let update tags tag value = tags := add !tags tag value let get tags tag = List.Assoc.find ~equal:String.equal tags tag let tag_value_records_of_tags tags = List.map ~f:(fun (tag, value) -> {Jsonbug_t.tag; value}) tags let tags_of_tag_value_records (tag_value_records: Jsonbug_t.tag_value_record list) = List.map ~f:(fun {Jsonbug_t.tag; value} -> (tag, value)) tag_value_records let lines_of_tags (tags: t) = let line_tags = String.Set.of_list [dereferenced_line; call_line; assigned_line; alloc_line; accessed_line; dealloc_line] in List.filter_map ~f:(fun (tag, value) -> if String.Set.mem line_tags tag then Some (int_of_string value) else None ) tags end type error_desc = {descriptions: string list; advice: string option; tags: Tags.t; dotty: string option} [@@deriving compare] (** empty error description *) let no_desc : error_desc = {descriptions= []; advice= None; tags= []; dotty= None} (** verbatim desc from a string, not to be used for user-visible descs *) let verbatim_desc s = {no_desc with descriptions= [s]} let custom_desc_with_advice description advice tags = {no_desc with descriptions= [description]; advice= Some advice; tags} (** pretty print an error description *) let pp_error_desc fmt err_desc = let pp_item fmt s = F.fprintf fmt "%s" s in Pp.seq pp_item fmt err_desc.descriptions let error_desc_get_dotty err_desc = err_desc.dotty module BucketLevel = struct (** highest likelihood *) let b1 = "B1" let b2 = "B2" let b3 = "B3" let b4 = "B4" (** lowest likelihood *) let b5 = "B5" end (** takes in input a tag to extract from the given error_desc and returns its value *) let error_desc_extract_tag_value err_desc tag_to_extract = let find_value tag v = match v with t, _ when String.equal t tag -> true | _ -> false in match List.find ~f:(find_value tag_to_extract) err_desc.tags with Some (_, s) -> s | None -> "" (** get the bucket value of an error_desc, if any *) let error_desc_get_bucket err_desc = Tags.get err_desc.tags Tags.bucket (** set the bucket value of an error_desc *) let error_desc_set_bucket err_desc bucket = let tags = Tags.add err_desc.tags Tags.bucket bucket in let descriptions = if Config.show_buckets then Printf.sprintf "[%s]" bucket :: err_desc.descriptions else err_desc.descriptions in {err_desc with descriptions; tags} let error_desc_is_reportable_bucket err_desc = let issue_bucket = error_desc_get_bucket err_desc in let high_buckets = BucketLevel.([b1; b2]) in Option.value_map issue_bucket ~default:false ~f:(fun b -> List.mem ~equal:String.equal high_buckets b ) (** get the value tag, if any *) let get_value_line_tag tags = try let value = snd (List.find_exn ~f:(fun (tag, _) -> String.equal tag Tags.value) tags) in let line = snd (List.find_exn ~f:(fun (tag, _) -> String.equal tag Tags.line) tags) in Some [value; line] with Not_found -> None (** extract from desc a value on which to apply polymorphic hash and equality *) let desc_get_comparable err_desc = match get_value_line_tag err_desc.tags with Some sl' -> sl' | None -> err_desc.descriptions (** hash function for error_desc *) let error_desc_hash desc = Hashtbl.hash (desc_get_comparable desc) (** equality for error_desc *) let error_desc_equal desc1 desc2 = [%compare.equal : string list] (desc_get_comparable desc1) (desc_get_comparable desc2) let line_tag_ tags tag loc = let line_str = string_of_int loc.Location.line in Tags.update tags tag line_str ; let s = "line " ^ line_str in if loc.Location.col <> -1 then let col_str = string_of_int loc.Location.col in s ^ ", column " ^ col_str else s let at_line_tag tags tag loc = "at " ^ line_tag_ tags tag loc let line_ tags loc = line_tag_ tags Tags.line loc let at_line tags loc = at_line_tag tags Tags.line loc let call_to tags proc_name = let proc_name_str = Typ.Procname.to_simplified_string proc_name in Tags.update tags Tags.call_procedure proc_name_str ; "call to " ^ MF.monospaced_to_string proc_name_str let call_to_at_line tags proc_name loc = call_to tags proc_name ^ " " ^ at_line_tag tags Tags.call_line loc let by_call_to tags proc_name = "by " ^ call_to tags proc_name let by_call_to_ra tags ra = "by " ^ call_to_at_line tags ra.PredSymb.ra_pname ra.PredSymb.ra_loc let add_by_call_to_opt problem_str tags proc_name_opt = match proc_name_opt with | Some proc_name -> problem_str ^ " " ^ by_call_to tags proc_name | None -> problem_str let rec format_typ typ = match typ.Typ.desc with | Typ.Tptr (t, _) when Config.curr_language_is Config.Java -> format_typ t | Typ.Tstruct name -> Typ.Name.name name | _ -> Typ.to_string typ let format_field f = if Config.curr_language_is Config.Java then Typ.Fieldname.Java.get_field f else Typ.Fieldname.to_string f let format_method pname = match pname with | Typ.Procname.Java pname_java -> Typ.Procname.Java.get_method pname_java | _ -> Typ.Procname.to_string pname let mem_dyn_allocated = "memory dynamically allocated" let lock_acquired = "lock acquired" let released = "released" let reachable = "reachable" (** dereference strings used to explain a dereference action in an error message *) type deref_str = { tags: (string * string) list ref (** tags for the error description *) ; value_pre: string option (** string printed before the value being dereferenced *) ; value_post: string option (** string printed after the value being dereferenced *) ; problem_str: string (** description of the problem *) } let pointer_or_object () = if Config.curr_language_is Config.Java then "object" else "pointer" let deref_str_null_ proc_name_opt problem_str_ tags = let problem_str = add_by_call_to_opt problem_str_ tags proc_name_opt in {tags; value_pre= Some (pointer_or_object ()); value_post= None; problem_str} (** dereference strings for null dereference *) let deref_str_null proc_name_opt = let problem_str = "could be null and is dereferenced" in deref_str_null_ proc_name_opt problem_str (Tags.create ()) let access_str_empty proc_name_opt = let problem_str = "could be empty and is accessed" in deref_str_null_ proc_name_opt problem_str (Tags.create ()) (** dereference strings for null dereference due to Nullable annotation *) let deref_str_nullable proc_name_opt nullable_obj_str = let tags = Tags.create () in Tags.update tags Tags.nullable_src nullable_obj_str ; (* to be completed once we know if the deref'd expression is directly or transitively @Nullable*) let problem_str = "" in deref_str_null_ proc_name_opt problem_str tags (** dereference strings for null dereference due to weak captured variable in block *) let deref_str_weak_variable_in_block proc_name_opt nullable_obj_str = let tags = Tags.create () in Tags.update tags Tags.weak_captured_var_src nullable_obj_str ; let problem_str = "" in deref_str_null_ proc_name_opt problem_str tags (** dereference strings for nonterminal nil arguments in c/objc variadic methods *) let deref_str_nil_argument_in_variadic_method pn total_args arg_number = let tags = Tags.create () in let function_method, nil_null = if Typ.Procname.is_c_method pn then ("method", "nil") else ("function", "null") in let problem_str = Printf.sprintf "could be %s which results in a call to %s with %d arguments instead of %d (%s indicates that the last argument of this variadic %s has been reached)" nil_null (Typ.Procname.to_simplified_string pn) arg_number (total_args - 1) nil_null function_method in deref_str_null_ None problem_str tags (** dereference strings for an undefined value coming from the given procedure *) let deref_str_undef (proc_name, loc) = let tags = Tags.create () in let proc_name_str = Typ.Procname.to_simplified_string proc_name in Tags.update tags Tags.call_procedure proc_name_str ; { tags ; value_pre= Some (pointer_or_object ()) ; value_post= None ; problem_str= "could be assigned by a call to skip function " ^ proc_name_str ^ at_line_tag tags Tags.call_line loc ^ " and is dereferenced or freed" } (** dereference strings for a freed pointer dereference *) let deref_str_freed ra = let tags = Tags.create () in let freed_or_closed_by_call = let freed_or_closed = match ra.PredSymb.ra_res with | PredSymb.Rmemory _ -> "freed" | PredSymb.Rfile -> "closed" | PredSymb.Rignore -> "freed" | PredSymb.Rlock -> "locked" in freed_or_closed ^ " " ^ by_call_to_ra tags ra in { tags ; value_pre= Some (pointer_or_object ()) ; value_post= None ; problem_str= "was " ^ freed_or_closed_by_call ^ " and is dereferenced or freed" } (** dereference strings for a dangling pointer dereference *) let deref_str_dangling dangling_kind_opt = let dangling_kind_prefix = match dangling_kind_opt with | Some PredSymb.DAuninit -> "uninitialized " | Some PredSymb.DAaddr_stack_var -> "deallocated stack " | Some PredSymb.DAminusone -> "-1 " | None -> "" in { tags= Tags.create () ; value_pre= Some (dangling_kind_prefix ^ pointer_or_object ()) ; value_post= None ; problem_str= "could be dangling and is dereferenced or freed" } (** dereference strings for a pointer size mismatch *) let deref_str_pointer_size_mismatch typ_from_instr typ_of_object = let str_from_typ typ = let pp f = Typ.pp_full Pp.text f typ in F.asprintf "%t" pp in { tags= Tags.create () ; value_pre= Some (pointer_or_object ()) ; value_post= Some ("of type " ^ str_from_typ typ_from_instr) ; problem_str= "could be used to access an object of smaller type " ^ str_from_typ typ_of_object } (** dereference strings for an array out of bound access *) let deref_str_array_bound size_opt index_opt = let tags = Tags.create () in let size_str_opt = match size_opt with | Some n -> let n_str = IntLit.to_string n in Tags.update tags Tags.array_size n_str ; Some ("of size " ^ n_str) | None -> None in let index_str = match index_opt with | Some n -> let n_str = IntLit.to_string n in Tags.update tags Tags.array_index n_str ; "index " ^ n_str | None -> "an index" in { tags ; value_pre= Some "array" ; value_post= size_str_opt ; problem_str= "could be accessed with " ^ index_str ^ " out of bounds" } (** Java unchecked exceptions errors *) let java_unchecked_exn_desc proc_name exn_name pre_str : error_desc = { no_desc with descriptions= [ MF.monospaced_to_string (Typ.Procname.to_string proc_name) ; "can throw " ^ MF.monospaced_to_string (Typ.Name.name exn_name) ; "whenever " ^ pre_str ] } let desc_context_leak pname context_typ fieldname leak_path : error_desc = let fld_str = Typ.Fieldname.to_string fieldname in let leak_root = "Static field " ^ fld_str ^ " |->\n" in let leak_path_entry_to_str acc entry = let entry_str = match entry with | Some fld, _ -> Typ.Fieldname.to_string fld | None, typ -> Typ.to_string typ in (* intentionally omit space; [typ_to_string] adds an extra space *) acc ^ entry_str ^ " |->\n" in let context_str = Typ.to_string context_typ in let path_str = let path_prefix = if List.is_empty leak_path then "Leaked " else List.fold ~f:leak_path_entry_to_str ~init:"" leak_path ^ "Leaked " in path_prefix ^ context_str in let preamble = let pname_str = match pname with | Typ.Procname.Java pname_java -> MF.monospaced_to_string (Printf.sprintf "%s.%s" (Typ.Procname.Java.get_class_name pname_java) (Typ.Procname.Java.get_method pname_java)) | _ -> "" in "Context " ^ context_str ^ " may leak during method " ^ pname_str ^ ":\n" in {no_desc with descriptions= [preamble ^ MF.code_to_string (leak_root ^ path_str)]} let desc_double_lock pname_opt object_str loc = let mutex_str = Format.sprintf "Mutex %s" object_str in let tags = Tags.create () in let msg = "could be locked and is locked again" in let msg = add_by_call_to_opt msg tags pname_opt in Tags.update tags Tags.double_lock object_str ; let descriptions = [mutex_str; msg; at_line tags loc] in {no_desc with descriptions; tags= !tags} let desc_unsafe_guarded_by_access accessed_fld guarded_by_str loc = let line_info = at_line (Tags.create ()) loc in let accessed_fld_str = Typ.Fieldname.to_string accessed_fld in let annot_str = Printf.sprintf "@GuardedBy(\"%s\")" guarded_by_str in let syncronized_str = MF.monospaced_to_string (Printf.sprintf "synchronized(%s)" guarded_by_str) in let msg = Format.asprintf "The field %a is annotated with %a, but the lock %a is not held during the access to the field %s. Since the current method is non-private, it can be called from outside the current class without synchronization. Consider wrapping the access in a %s block or making the method private." MF.pp_monospaced accessed_fld_str MF.pp_monospaced annot_str MF.pp_monospaced guarded_by_str line_info syncronized_str in {no_desc with descriptions= [msg]} let desc_fragment_retains_view fragment_typ fieldname fld_typ pname : error_desc = (* TODO: try advice *) let problem = Printf.sprintf "Fragment %s does not nullify View field %s (type %s) in %s." (format_typ fragment_typ) (format_field fieldname) (format_typ fld_typ) (format_method pname) in let consequences = "If this Fragment is placed on the back stack, a reference to this (probably dead) View will be retained." in let advice = "In general, it is a good idea to initialize View's in onCreateView, then nullify them in onDestroyView." in {no_desc with descriptions= [problem; consequences; advice]} let desc_custom_error loc : error_desc = {no_desc with descriptions= ["detected"; at_line (Tags.create ()) loc]} (** type of access *) type access = | Last_assigned of int * bool (* line, null_case_flag *) | Last_accessed of int * bool (* line, is_nullable flag *) | Initialized_automatically | Returned_from_call of int let nullable_annotation_name proc_name = match Config.nullable_annotation with | Some name -> name | None when Typ.Procname.is_java proc_name -> "@Nullable" | None (* default Clang annotation name *) -> "_Nullable" let access_desc tags access_opt = match access_opt with | None -> [] | Some Last_accessed (n, _) -> let line_str = string_of_int n in Tags.update tags Tags.accessed_line line_str ; ["last accessed on line " ^ line_str] | Some Last_assigned (n, _) -> let line_str = string_of_int n in Tags.update tags Tags.assigned_line line_str ; ["last assigned on line " ^ line_str] | Some Returned_from_call _ -> [] | Some Initialized_automatically -> ["initialized automatically"] let dereference_string proc_name deref_str value_str access_opt loc = let tags = deref_str.tags in Tags.update tags Tags.value value_str ; let is_call_access = match access_opt with Some Returned_from_call _ -> true | _ -> false in let value_desc = String.concat ~sep:"" [ (match deref_str.value_pre with Some s -> s ^ " " | _ -> "") ; (if is_call_access then "returned by " else "") ; MF.monospaced_to_string value_str ; (match deref_str.value_post with Some s -> " " ^ MF.monospaced_to_string s | _ -> "") ] in let problem_desc = let problem_str = let annotation_name = nullable_annotation_name proc_name in match (Tags.get !tags Tags.nullable_src, Tags.get !tags Tags.weak_captured_var_src) with | Some nullable_src, _ -> if String.equal nullable_src value_str then "is annotated with " ^ annotation_name ^ " and is dereferenced without a null check" else "is indirectly marked " ^ annotation_name ^ " (source: " ^ MF.monospaced_to_string nullable_src ^ ") and is dereferenced without a null check" | None, Some weak_var_str -> if String.equal weak_var_str value_str then "is a weak pointer captured in the block and is dereferenced without a null check" else "is equal to the variable " ^ MF.monospaced_to_string weak_var_str ^ ", a weak pointer captured in the block, and is dereferenced without a null check" | None, None -> deref_str.problem_str in [problem_str ^ " " ^ at_line tags loc] in let access_desc = access_desc tags access_opt in {no_desc with descriptions= value_desc :: access_desc @ problem_desc; tags= !tags} let parameter_field_not_null_checked_desc (desc: error_desc) exp = let parameter_not_nullable_desc var = let var_s = Pvar.to_string var in let param_not_null_desc = "Parameter " ^ MF.monospaced_to_string var_s ^ " is not checked for null, there could be a null pointer dereference:" in { desc with descriptions= param_not_null_desc :: desc.descriptions ; tags= (Tags.parameter_not_null_checked, var_s) :: desc.tags } in let field_not_nullable_desc exp = let rec exp_to_string exp = match exp with | Exp.Lfield (exp', field, _) -> exp_to_string exp' ^ " -> " ^ Typ.Fieldname.to_string field | Exp.Lvar pvar -> Mangled.to_string (Pvar.get_name pvar) | _ -> "" in let var_s = exp_to_string exp in let field_not_null_desc = "Instance variable " ^ MF.monospaced_to_string var_s ^ " is not checked for null, there could be a null pointer dereference:" in { desc with descriptions= field_not_null_desc :: desc.descriptions ; tags= (Tags.field_not_null_checked, var_s) :: desc.tags } in match exp with | Exp.Lvar var -> parameter_not_nullable_desc var | Exp.Lfield _ -> field_not_nullable_desc exp | _ -> desc let has_tag (desc: error_desc) tag = List.exists ~f:(fun (tag', _) -> String.equal tag tag') desc.tags let is_parameter_not_null_checked_desc desc = has_tag desc Tags.parameter_not_null_checked let is_field_not_null_checked_desc desc = has_tag desc Tags.field_not_null_checked let is_double_lock_desc desc = has_tag desc Tags.double_lock let desc_allocation_mismatch alloc dealloc = let tags = Tags.create () in let using is_alloc (primitive_pname, called_pname, loc) = let tag_fun, tag_call, tag_line = if is_alloc then (Tags.alloc_function, Tags.alloc_call, Tags.alloc_line) else (Tags.dealloc_function, Tags.dealloc_call, Tags.dealloc_line) in Tags.update tags tag_fun (Typ.Procname.to_simplified_string primitive_pname) ; Tags.update tags tag_call (Typ.Procname.to_simplified_string called_pname) ; Tags.update tags tag_line (string_of_int loc.Location.line) ; let by_call = if Typ.Procname.equal primitive_pname called_pname then "" else " by call to " ^ MF.monospaced_to_string (Typ.Procname.to_simplified_string called_pname) in "using " ^ MF.monospaced_to_string (Typ.Procname.to_simplified_string primitive_pname) ^ by_call ^ " " ^ at_line (Tags.create ()) (* ignore the tag *) loc in let description = Format.sprintf "%s %s is deallocated %s" mem_dyn_allocated (using true alloc) (using false dealloc) in {no_desc with descriptions= [description]; tags= !tags} let desc_condition_always_true_false i cond_str_opt loc = let tags = Tags.create () in let value = match cond_str_opt with None -> "" | Some s -> s in let tt_ff = if IntLit.iszero i then "false" else "true" in Tags.update tags Tags.value value ; let description = Format.sprintf "Boolean condition %s is always %s %s" (if String.equal value "" then "" else " " ^ MF.monospaced_to_string value) tt_ff (at_line tags loc) in {no_desc with descriptions= [description]; tags= !tags} let desc_unreachable_code_after loc = let tags = Tags.create () in let description = "Unreachable code after statement " ^ at_line tags loc in {no_desc with descriptions= [description]} let desc_deallocate_stack_variable var_str proc_name loc = let tags = Tags.create () in Tags.update tags Tags.value var_str ; let description = Format.asprintf "Stack variable %a is freed by a %s" MF.pp_monospaced var_str (call_to_at_line tags proc_name loc) in {no_desc with descriptions= [description]; tags= !tags} let desc_deallocate_static_memory const_str proc_name loc = let tags = Tags.create () in Tags.update tags Tags.value const_str ; let description = Format.asprintf "Constant string %a is freed by a %s" MF.pp_monospaced const_str (call_to_at_line tags proc_name loc) in {no_desc with descriptions= [description]; tags= !tags} let desc_class_cast_exception pname_opt typ_str1 typ_str2 exp_str_opt loc = let tags = Tags.create () in Tags.update tags Tags.type1 typ_str1 ; Tags.update tags Tags.type2 typ_str2 ; let in_expression = match exp_str_opt with | Some exp_str -> Tags.update tags Tags.value exp_str ; " in expression " ^ MF.monospaced_to_string exp_str ^ " " | None -> " " in let at_line' () = match pname_opt with | Some proc_name -> "in " ^ call_to_at_line tags proc_name loc | None -> at_line tags loc in let description = Format.asprintf "%a cannot be cast to %a %s %s" MF.pp_monospaced typ_str1 MF.pp_monospaced typ_str2 in_expression (at_line' ()) in {no_desc with descriptions= [description]; tags= !tags} let desc_divide_by_zero expr_str loc = let tags = Tags.create () in Tags.update tags Tags.value expr_str ; let description = Format.asprintf "Expression %a could be zero %s" MF.pp_monospaced expr_str (at_line tags loc) in {no_desc with descriptions= [description]; tags= !tags} let desc_empty_vector_access pname_opt object_str loc = let vector_str = Format.asprintf "Vector %a" MF.pp_monospaced object_str in let desc = access_str_empty pname_opt in let tags = desc.tags in Tags.update tags Tags.empty_vector_access object_str ; let descriptions = [vector_str; desc.problem_str; at_line tags loc] in {no_desc with descriptions; tags= !tags} let is_empty_vector_access_desc desc = has_tag desc Tags.empty_vector_access let desc_frontend_warning desc sugg_opt loc = let tags = Tags.create () in let sugg = match sugg_opt with Some sugg -> sugg | None -> "" in (* If the description ends in a period, we remove it because the sentence continues with "at line ..." *) let desc = match String.chop_suffix ~suffix:"." desc with Some desc -> desc | None -> desc in let description = Format.sprintf "%s %s. %s" desc (at_line tags loc) sugg in {no_desc with descriptions= [description]; tags= !tags} let desc_leak hpred_type_opt value_str_opt resource_opt resource_action_opt loc bucket_opt = let tags = Tags.create () in let () = match bucket_opt with Some bucket -> Tags.update tags Tags.bucket bucket | None -> () in let xxx_allocated_to = let value_str, to_, on_ = match value_str_opt with | None -> ("", "", "") | Some s -> Tags.update tags Tags.value s ; (MF.monospaced_to_string s, " to ", " on ") in let typ_str = match hpred_type_opt with | Some Exp.Sizeof {typ= {desc= Tstruct name}} when Typ.Name.is_class name -> " of type " ^ MF.monospaced_to_string (Typ.Name.name name) ^ " " | _ -> " " in let desc_str = match resource_opt with | Some PredSymb.Rmemory _ -> mem_dyn_allocated ^ to_ ^ value_str | Some PredSymb.Rfile -> "resource" ^ typ_str ^ "acquired" ^ to_ ^ value_str | Some PredSymb.Rlock -> lock_acquired ^ on_ ^ value_str | Some PredSymb.Rignore | None -> if is_none value_str_opt then "memory" else value_str in if String.equal desc_str "" then [] else [desc_str] in let by_call_to = match resource_action_opt with Some ra -> [by_call_to_ra tags ra] | None -> [] in let is_not_rxxx_after = let rxxx = match resource_opt with | Some PredSymb.Rmemory _ -> reachable | Some PredSymb.Rfile | Some PredSymb.Rlock -> released | Some PredSymb.Rignore | None -> reachable in ["is not " ^ rxxx ^ " after " ^ line_ tags loc] in let bucket_str = match bucket_opt with Some bucket when Config.show_buckets -> bucket | _ -> "" in { no_desc with descriptions= bucket_str :: xxx_allocated_to @ by_call_to @ is_not_rxxx_after; tags= !tags } let desc_buffer_overrun desc = verbatim_desc desc (** kind of precondition not met *) type pnm_kind = Pnm_bounds | Pnm_dangling let desc_precondition_not_met kind proc_name loc = let tags = Tags.create () in let kind_str = match kind with | None -> [] | Some Pnm_bounds -> ["possible array out of bounds"] | Some Pnm_dangling -> ["possible dangling pointer dereference"] in {no_desc with descriptions= kind_str @ ["in " ^ call_to_at_line tags proc_name loc]; tags= !tags} let desc_null_test_after_dereference expr_str line loc = let tags = Tags.create () in Tags.update tags Tags.dereferenced_line (string_of_int line) ; Tags.update tags Tags.value expr_str ; let description = Format.asprintf "Pointer %a was dereferenced at line %d and is tested for null %s" MF.pp_monospaced expr_str line (at_line tags loc) in {no_desc with descriptions= [description]; tags= !tags} let desc_retain_cycle cycle_str loc cycle_dotty = Logging.d_strln "Proposition with retain cycle:" ; let tags = Tags.create () in let desc = Format.sprintf "Retain cycle %s involving the following objects:%s" (at_line tags loc) cycle_str in {no_desc with descriptions= [desc]; tags= !tags; dotty= cycle_dotty} let registered_observer_being_deallocated_str obj_str = "Object " ^ obj_str ^ " is registered in a notification center but not being removed before deallocation" let desc_registered_observer_being_deallocated pvar loc = let tags = Tags.create () in let obj_str = MF.monospaced_to_string (Pvar.to_string pvar) in { no_desc with descriptions= [ registered_observer_being_deallocated_str obj_str ^ at_line tags loc ^ ". Being still registered as observer of the notification " ^ "center, the deallocated object " ^ obj_str ^ " may be notified in the future." ] ; tags= !tags } let desc_unary_minus_applied_to_unsigned_expression expr_str_opt typ_str loc = let tags = Tags.create () in let expression = match expr_str_opt with | Some s -> Tags.update tags Tags.value s ; "expression " ^ s | None -> "an expression" in let description = Format.asprintf "A unary minus is applied to %a of type %s %s" MF.pp_monospaced expression typ_str (at_line tags loc) in {no_desc with descriptions= [description]; tags= !tags} let desc_skip_function proc_name = let tags = Tags.create () in let proc_name_str = Typ.Procname.to_string proc_name in Tags.update tags Tags.value proc_name_str ; {no_desc with descriptions= [proc_name_str]; tags= !tags} let desc_inherently_dangerous_function proc_name = let proc_name_str = Typ.Procname.to_string proc_name in let tags = Tags.create () in Tags.update tags Tags.value proc_name_str ; {no_desc with descriptions= [MF.monospaced_to_string proc_name_str]; tags= !tags} let desc_stack_variable_address_escape pvar addr_dexp_str loc = let expr_str = Pvar.to_string pvar in let tags = Tags.create () in Tags.update tags Tags.value expr_str ; let escape_to_str = match addr_dexp_str with | Some s -> Tags.update tags Tags.escape_to s ; "to " ^ s ^ " " | None -> "" in let variable_str = if Pvar.is_frontend_tmp pvar then "temporary" else Format.asprintf "stack variable %a" MF.pp_monospaced expr_str in let description = Format.asprintf "Address of %s escapes %s%s" variable_str escape_to_str (at_line tags loc) in {no_desc with descriptions= [description]; tags= !tags} let desc_uninitialized_dangling_pointer_deref deref expr_str loc = let tags = Tags.create () in Tags.update tags Tags.value expr_str ; let prefix = match deref.value_pre with Some s -> s | _ -> "" in let description = Format.asprintf "%s %a %s %s" prefix MF.pp_monospaced expr_str deref.problem_str (at_line tags loc) in {no_desc with descriptions= [description]; tags= !tags}