(* * 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. *) module L = Logging module F = Format open Utils type printf_signature = { unique_id: string; format_pos: int; fixed_pos: int list; vararg_pos: int option } let printf_signature_to_string (printf: printf_signature): string = Printf.sprintf "{%s; %d [%s] %s}" printf.unique_id printf.format_pos (String.concat "," (list_map string_of_int printf.fixed_pos)) (match printf.vararg_pos with | Some i -> string_of_int i | _ -> "-") let printf_like_functions = ref [ { unique_id = "java.io.PrintStream.printf(java.lang.String,java.lang.Object[]):java.io.PrintStream"; format_pos = 1; fixed_pos = []; vararg_pos = Some 2 }; { unique_id = "java.io.PrintStream.printf(java.lang.Locale,java.lang.String,java.lang.Object[]):java.io.PrintStream"; format_pos = 2; fixed_pos = []; vararg_pos = Some 3 }; { unique_id = "java.lang.String(java.lang.String,java.lang.Object[]):java.lang.String"; format_pos = 1; fixed_pos = []; vararg_pos = Some 2 }; { unique_id = "java.lang.String(java.lang.Locale,java.lang.String,java.lang.Object[]):java.lang.String"; format_pos = 2; fixed_pos = []; vararg_pos = Some 3 }; ] let add_printf_like_function plf = printf_like_functions := plf :: !printf_like_functions let printf_like_function (proc_name: Procname.t): printf_signature option = try Some ( list_find (fun printf -> string_equal printf.unique_id (Procname.to_unique_id proc_name)) !printf_like_functions) with Not_found -> None let default_format_type_name (format_type: string): string = match format_type with | "d" | "i" | "u" | "x" | "X" | "o" -> "java.lang.Integer" | "a" | "A" | "f" | "F" | "g" | "G" | "e" | "E" -> "java.lang.Double" | "c" -> "java.lang.Character" | "b" -> "java.lang.Boolean" | "s" -> "java.lang.String" | "h" | "H" -> "java.lang.Object" | _ -> "unknown" let format_type_matches_given_type (format_type: string) (given_type: string): bool = match format_type with | "d" | "i" | "u" | "x" | "X" | "o" -> list_mem string_equal given_type ["java.lang.Integer"; "java.lang.Long"; "java.lang.Short"; "java.lang.Byte"] | "a" | "A" | "f" | "F" | "g" | "G" | "e" | "E" -> list_mem string_equal given_type ["java.lang.Double"; "java.lang.Float"] | "c" -> string_equal given_type "java.lang.Character" | "b" | "h" | "H" | "s" -> true (* accepts pretty much anything, even null *) | _ -> false (* The format string and the nvar for the fixed arguments and the nvar of the varargs array *) let format_arguments (printf: printf_signature) (args: (Sil.exp * Sil.typ) list): (string option * (Sil.exp list) * (Sil.exp option)) = let format_string = match list_nth args printf.format_pos with | Sil.Const (Sil.Cstr fmt), _ -> Some fmt | _ -> None in let fixed_nvars = list_map (fun i -> fst (list_nth args i)) printf.fixed_pos in let varargs_nvar = match printf.vararg_pos with | Some pos -> Some (fst (list_nth args pos)) | None -> None in format_string, fixed_nvars, varargs_nvar (* Extract type names from format string *) let rec format_string_type_names (fmt_string: string) (start: int): string list = try let fmt_re = Str.regexp "%[0-9]*\\.?[0-9]*[A-mo-z]" in (* matches '%2.1d' etc. *) let _ = Str.search_forward fmt_re fmt_string start in let fmt_match = Str.matched_string fmt_string in let fmt_type = String.sub fmt_match ((String.length fmt_match) - 1) 1 in fmt_type:: format_string_type_names fmt_string (Str.match_end ()) with Not_found -> [] let printf_args_name = "CHECKERS_PRINTF_ARGS" let callback_printf_args (all_procs : Procname.t list) (get_proc_desc: Procname.t -> Cfg.Procdesc.t option) (idenv: Idenv.t) (tenv: Sil.tenv) (proc_name: Procname.t) (proc_desc : Cfg.Procdesc.t) : unit = (* Check if format string lines up with arguments *) let rec check_type_names instr_loc n_arg instr_proc_name fmt_type_names arg_type_names = let instr_name = Procname.to_simplified_string instr_proc_name in let instr_line = Sil.loc_to_string instr_loc in match (fmt_type_names, arg_type_names) with | ft:: fs, gt:: gs -> if not (format_type_matches_given_type ft gt) then let description = Printf.sprintf "%s at line %s: parameter %d is expected to be of type %s but %s was given." instr_name instr_line n_arg (default_format_type_name ft) gt in Checkers.ST.report_error proc_name proc_desc printf_args_name instr_loc description else check_type_names instr_loc (n_arg + 1) instr_proc_name fs gs | [], [] -> () | _ -> let description = Printf.sprintf "format string arguments don't mach provided arguments in %s at line %s" instr_name instr_line in Checkers.ST.report_error proc_name proc_desc printf_args_name instr_loc description in (* Get the array ivar for a given nvar *) let rec array_ivar instrs nvar = match instrs, nvar with | Sil.Letderef (id, Sil.Lvar iv, _, _):: _, Sil.Var nid when Ident.equal id nid -> iv | i:: is, _ -> array_ivar is nvar | _ -> raise Not_found in let rec fixed_nvar_type_name instrs nvar = match nvar with | Sil.Var nid -> ( match instrs with | Sil.Letderef (id, Sil.Lvar iv, t, _):: _ when Ident.equal id nid -> PatternMatch.get_type_name t | i:: is -> fixed_nvar_type_name is nvar | _ -> raise Not_found) | Sil.Const c -> PatternMatch.java_get_const_type_name c | _ -> raise (Failure "Could not resolve fixed type name") in let do_instr (node: Cfg.Node.t) (instr: Sil.instr): unit = match instr with | Sil.Call (_, Sil.Const (Sil.Cfun pn), args, cl, _) -> ( match printf_like_function pn with | Some printf -> ( try let fmt, fixed_nvars, array_nvar = format_arguments printf args in let instrs = Cfg.Node.get_instrs node in let fixed_nvar_type_names = list_map (fixed_nvar_type_name instrs) fixed_nvars in let vararg_ivar_type_names = match array_nvar with | Some nvar -> ( let ivar = array_ivar instrs nvar in PatternMatch.get_vararg_type_names node ivar) | None -> [] in match fmt with | Some fmt -> check_type_names cl (printf.format_pos + 1) pn (format_string_type_names fmt 0) (fixed_nvar_type_names@ vararg_ivar_type_names) | None -> Checkers.ST.report_error proc_name proc_desc printf_args_name cl "Format string must be string literal" with e -> L.stderr "%s Exception when analyzing %s: %s@." printf_args_name (Procname.to_string proc_name) (Printexc.to_string e)) | None -> ()) | _ -> () in Cfg.Procdesc.iter_instrs do_instr proc_desc