diff --git a/infer/src/base/CommandLineOption.ml b/infer/src/base/CommandLineOption.ml index b204b45b1..544ff3e22 100644 --- a/infer/src/base/CommandLineOption.ml +++ b/infer/src/base/CommandLineOption.ml @@ -771,18 +771,6 @@ let mk_subcommand command ?on_unknown_arg:(on_unknown = `Reject) ~name ?deprecat subcommand_actions := (name, switch) :: !subcommand_actions -(* drop well-balanced first and last characters in [s] that satisfy the [drop] predicate; for - instance, [lrstrip ~drop:(function | 'a' | 'x' -> true | _ -> false) "xaabax"] returns "ab" *) -let rec lrstrip ~drop s = - let n = String.length s in - if n < 2 then s - else - let first = String.unsafe_get s 0 in - if Char.equal first (String.unsafe_get s (n - 1)) && drop first then - lrstrip ~drop (String.slice s 1 (n - 1)) - else s - - let args_from_argfile arg = let abs_fname = let fname = String.slice arg 1 (String.length arg) in @@ -790,7 +778,7 @@ let args_from_argfile arg = in match In_channel.read_lines abs_fname with | lines -> - let strip = lrstrip ~drop:(function '"' | '\'' -> true | _ -> false) in + let strip = Utils.strip_balanced_once ~drop:(function '"' | '\'' -> true | _ -> false) in List.map ~f:strip lines | exception e -> raise (Arg.Bad ("Error reading argument file '" ^ abs_fname ^ "': " ^ Exn.to_string e)) diff --git a/infer/src/base/Utils.ml b/infer/src/base/Utils.ml index 1add6c341..32415dff4 100644 --- a/infer/src/base/Utils.ml +++ b/infer/src/base/Utils.ml @@ -364,3 +364,15 @@ let better_hash x = Marshal.to_string x [Marshal.No_sharing] |> Caml.Digest.stri let unlink_file_on_exit temp_file = "Cleaning temporary file " ^ temp_file |> Epilogues.register ~f:(fun () -> try Unix.unlink temp_file with _ -> ()) + + +(** drop at most one layer of well-balanced first and last characters satisfying [drop] from the + string; for instance, [strip_balanced ~drop:(function | 'a' | 'x' -> true | _ -> false) "xaabax"] + returns "aaba" *) +let strip_balanced_once ~drop s = + let n = String.length s in + if n < 2 then s + else + let first = String.unsafe_get s 0 in + if Char.equal first (String.unsafe_get s (n - 1)) && drop first then String.slice s 1 (n - 1) + else s diff --git a/infer/src/base/Utils.mli b/infer/src/base/Utils.mli index 63acced41..93e5d2e38 100644 --- a/infer/src/base/Utils.mli +++ b/infer/src/base/Utils.mli @@ -110,3 +110,8 @@ val better_hash : 'a -> Caml.Digest.t val unlink_file_on_exit : string -> unit (** delete [temporary] file on exit *) + +val strip_balanced_once : drop:(char -> bool) -> string -> string +(** drop at most one layer of well-balanced first and last characters satisfying [drop] from the + string; for instance, [strip_balanced ~drop:(function | 'a' | 'x' -> true | _ -> false) "xaabax"] + returns "aaba" *) diff --git a/infer/src/clang/ClangCommand.ml b/infer/src/clang/ClangCommand.ml index 6e26b1eeb..99900cf45 100644 --- a/infer/src/clang/ClangCommand.ml +++ b/infer/src/clang/ClangCommand.ml @@ -12,6 +12,9 @@ module L = Logging type t = {exec: string; argv: string list; orig_argv: string list; quoting_style: ClangQuotes.style} +(** bad for every clang invocation *) +let clang_blacklisted_flags = ["-fembed-bitcode-marker"; "-fno-canonical-system-headers"] + let fcp_dir = Config.bin_dir ^/ Filename.parent_dir_name ^/ Filename.parent_dir_name ^/ "facebook-clang-plugins" @@ -67,22 +70,54 @@ let file_arg_cmd_sanitizer cmd = let include_override_regex = Option.map ~f:Str.regexp Config.clang_include_to_override_regex -let filter_and_replace_unsupported_args ?(replace_option_arg= fun _ s -> s) +(** Filter arguments from [args], looking into argfiles too. [replace_options_arg prev arg] returns + [arg'], where [arg'] is the new version of [arg] given the preceding arguments (in reverse order) [prev]. *) +let filter_and_replace_unsupported_args ?(replace_options_arg= fun _ s -> s) ?(blacklisted_flags= []) ?(blacklisted_flags_with_arg= []) ?(post_args= []) args = - let rec aux (prev, res_rev) args = + (* [prev] is the previously seen argument, [res_rev] is the reversed result, [changed] is true if + some change has been performed *) + let rec aux (prev_is_blacklisted_with_arg, res_rev, changed) args = match args with | [] -> - (* return non-reversed list *) - List.rev_append res_rev post_args + (prev_is_blacklisted_with_arg, res_rev, changed) + | _ :: tl when prev_is_blacklisted_with_arg -> + (* in the unlikely event that a blacklisted flag with arg sits as the last option in some + arg file, we need to remove its argument now *) + aux (false, res_rev, true) tl + | at_argfile :: tl when String.is_prefix at_argfile ~prefix:"@" + -> ( + let argfile = String.slice at_argfile 1 (String.length at_argfile) in + match In_channel.read_lines argfile with + | lines -> + (* poor parsing of arguments with some stripping supported; hope that tools generating + argfiles more or less put one argument per line *) + let strip s = + String.strip s + |> Utils.strip_balanced_once ~drop:(function '"' | '\'' -> true | _ -> false) + in + let last_in_file_is_blacklisted, rev_res_with_file_args, changed_file = + List.map ~f:strip lines |> aux (prev_is_blacklisted_with_arg, res_rev, false) + in + if changed_file then aux (last_in_file_is_blacklisted, rev_res_with_file_args, true) tl + else + (* keep the same argfile if we haven't needed to change anything in it *) + aux (last_in_file_is_blacklisted, at_argfile :: res_rev, changed) tl + | exception e -> + L.external_warning "Error reading argument file '%s': %s@\n" at_argfile + (Exn.to_string e) ; + aux (false, at_argfile :: res_rev, changed) tl ) | flag :: tl when List.mem ~equal:String.equal blacklisted_flags flag -> - aux (flag, res_rev) tl - | flag1 :: flag2 :: tl when List.mem ~equal:String.equal blacklisted_flags_with_arg flag1 -> - aux (flag2, res_rev) tl + aux (false, res_rev, true) tl + | flag :: tl when List.mem ~equal:String.equal blacklisted_flags_with_arg flag -> + (* remove the flag and its arg separately in case we are at the end of an argfile *) + aux (true, res_rev, true) tl | arg :: tl -> - let res_rev' = replace_option_arg prev arg :: res_rev in - aux (arg, res_rev') tl + let arg' = replace_options_arg res_rev arg in + aux (false, arg' :: res_rev, changed || not (phys_equal arg arg')) tl in - aux ("", []) args + match aux (false, [], false) args with _, res_rev, _ -> + (* return non-reversed list *) + List.rev_append res_rev post_args (* Work around various path or library issues occurring when one tries to substitute Apple's version @@ -90,26 +125,29 @@ let filter_and_replace_unsupported_args ?(replace_option_arg= fun _ s -> s) fatal warnings. *) let clang_cc1_cmd_sanitizer cmd = (* command line options not supported by the opensource compiler or the plugins *) - let blacklisted_flags = ["-fembed-bitcode-marker"; "-fno-canonical-system-headers"] in let blacklisted_flags_with_arg = ["-mllvm"] in - let replace_option_arg option arg = - if String.equal option "-arch" && String.equal arg "armv7k" then "armv7" - (* replace armv7k arch with armv7 *) - else if String.is_suffix arg ~suffix:"dep.tmp" then ( - (* compilation-database Buck integration produces path to `dep.tmp` file that doesn't exist. Create it *) - Unix.mkdir_p (Filename.dirname arg) ; - arg ) - else if String.equal option "-dependency-file" - && Option.is_some Config.buck_compilation_database - (* In compilation database mode, dependency files are not assumed to exist *) - then "/dev/null" - else if String.equal option "-isystem" then - match include_override_regex with - | Some regexp when Str.string_match regexp arg 0 -> - fcp_dir ^/ "clang" ^/ "install" ^/ "lib" ^/ "clang" ^/ "5.0.0" ^/ "include" - | _ -> - arg - else arg + let replace_options_arg options arg = + match options with + | option :: _ -> + if String.equal option "-arch" && String.equal arg "armv7k" then "armv7" + (* replace armv7k arch with armv7 *) + else if String.is_suffix arg ~suffix:"dep.tmp" then ( + (* compilation-database Buck integration produces path to `dep.tmp` file that doesn't exist. Create it *) + Unix.mkdir_p (Filename.dirname arg) ; + arg ) + else if String.equal option "-dependency-file" + && Option.is_some Config.buck_compilation_database + (* In compilation database mode, dependency files are not assumed to exist *) + then "/dev/null" + else if String.equal option "-isystem" then + match include_override_regex with + | Some regexp when Str.string_match regexp arg 0 -> + fcp_dir ^/ "clang" ^/ "install" ^/ "lib" ^/ "clang" ^/ "5.0.0" ^/ "include" + | _ -> + arg + else arg + | [] -> + arg in let args_defines = if Config.bufferoverrun && not Config.biabduction then ["-D__INFER_BUFFEROVERRUN"] else [] @@ -124,8 +162,8 @@ let clang_cc1_cmd_sanitizer cmd = argv_cons "-Wno-everything" in let clang_arguments = - filter_and_replace_unsupported_args ~blacklisted_flags ~blacklisted_flags_with_arg - ~replace_option_arg ~post_args:(List.rev post_args_rev) cmd.argv + filter_and_replace_unsupported_args ~blacklisted_flags:clang_blacklisted_flags + ~blacklisted_flags_with_arg ~replace_options_arg ~post_args:(List.rev post_args_rev) cmd.argv in file_arg_cmd_sanitizer {cmd with argv= clang_arguments} @@ -133,7 +171,10 @@ let clang_cc1_cmd_sanitizer cmd = let mk quoting_style ~prog ~args = (* Some arguments break the compiler so they need to be removed even before the normalization step *) let blacklisted_flags_with_arg = ["-index-store-path"] in - let sanitized_args = filter_and_replace_unsupported_args ~blacklisted_flags_with_arg args in + let sanitized_args = + filter_and_replace_unsupported_args ~blacklisted_flags:clang_blacklisted_flags + ~blacklisted_flags_with_arg args + in {exec= prog; orig_argv= sanitized_args; argv= sanitized_args; quoting_style}