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.
145 lines
6.0 KiB
145 lines
6.0 KiB
/* Copyright (c) 2016 - 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.
|
|
*/
|
|
|
|
/** Given a clang command, normalize it via `clang -###` if needed to get a clear view of what work
|
|
is being done and which source files are being compiled, if any, then replace compilation
|
|
commands by our own clang with our plugin attached for each source file. */
|
|
open! Utils;
|
|
|
|
|
|
/** Given a list of arguments for clang [args], return a list of new commands to run according to
|
|
the results of `clang -### [args]`. Assembly commands (eg, clang -cc1as ...) are filtered out,
|
|
although the type cannot reflect that fact. */
|
|
let normalize (args: array string) :list ClangCommand.t =>
|
|
switch (ClangCommand.mk ClangQuotes.SingleQuotes args) {
|
|
| CC1 args =>
|
|
Logging.out "InferClang got toplevel -cc1 command@\n";
|
|
[ClangCommand.CC1 args]
|
|
| NonCCCommand args =>
|
|
let args' = ClangCommand.append_args ["-fno-cxx-modules"] args;
|
|
let clang_hashhashhash =
|
|
Printf.sprintf
|
|
"%s 2>&1" (ClangCommand.prepend_arg "-###" args' |> ClangCommand.command_to_run);
|
|
Logging.out "clang -### invocation: %s@\n" clang_hashhashhash;
|
|
let normalized_commands = ref [];
|
|
let one_line line =>
|
|
if (string_is_prefix " \"" line) {
|
|
/* massage line to remove edge-cases for splitting */
|
|
"\"" ^ line ^ " \"" |>
|
|
/* split by whitespace */
|
|
Str.split (Str.regexp_string "\" \"") |> Array.of_list |>
|
|
ClangCommand.mk ClangQuotes.EscapedDoubleQuotes
|
|
} else if (
|
|
Str.string_match (Str.regexp "clang[^ :]*: warning: ") line 0
|
|
) {
|
|
ClangCommand.ClangWarning line
|
|
} else {
|
|
ClangCommand.ClangError line
|
|
};
|
|
let commands_or_errors =
|
|
/* commands generated by `clang -### ...` start with ' "/absolute/path/to/binary"' */
|
|
Str.regexp " \"/\\|clang[^ :]*: \\(error\\|warning\\): ";
|
|
let consume_input i =>
|
|
try (
|
|
while true {
|
|
let line = input_line i;
|
|
/* keep only commands and errors */
|
|
if (Str.string_match commands_or_errors line 0) {
|
|
normalized_commands := [one_line line, ...!normalized_commands]
|
|
}
|
|
}
|
|
) {
|
|
| End_of_file => ()
|
|
};
|
|
/* collect stdout and stderr output together (in reverse order) */
|
|
with_process_in clang_hashhashhash consume_input |> ignore;
|
|
normalized_commands := IList.rev !normalized_commands;
|
|
/* Discard assembly commands. This may make the list of commands empty, in which case we'll run
|
|
the original clang command. We could be smarter about this and try to execute the assembly
|
|
commands with our own clang. */
|
|
IList.filter
|
|
(
|
|
fun
|
|
| ClangCommand.Assembly asm_cmd => {
|
|
Logging.out "Skipping assembly command %s@\n" (ClangCommand.command_to_run asm_cmd);
|
|
false
|
|
}
|
|
| _ => true
|
|
)
|
|
!normalized_commands
|
|
| Assembly _ =>
|
|
/* discard assembly commands -- see above */
|
|
Logging.out "InferClang got toplevel assembly command@\n";
|
|
[]
|
|
| ClangError _
|
|
| ClangWarning _ =>
|
|
/* we cannot possibly get this from the command-line... */
|
|
assert false
|
|
};
|
|
|
|
let execute_clang_command (clang_cmd: ClangCommand.t) => {
|
|
/* reset logging, otherwise we might print into the logs of the previous file that was compiled */
|
|
Logging.set_log_file_identifier None;
|
|
switch clang_cmd {
|
|
| CC1 args =>
|
|
/* this command compiles some code; replace the invocation of clang with our own clang and
|
|
plugin */
|
|
Logging.out "Capturing -cc1 command: %s@\n" (ClangCommand.command_to_run args);
|
|
Capture.capture args
|
|
| ClangError error =>
|
|
/* An error in the output of `clang -### ...`. Outputs the error and fail. This is because
|
|
`clang -###` pretty much never fails, but warns of failures on stderr instead. */
|
|
Logging.err "%s" error;
|
|
exit 1
|
|
| ClangWarning warning => Logging.err "%s@\n" warning
|
|
| Assembly args =>
|
|
/* We shouldn't get any assembly command at this point */
|
|
(if Config.debug_mode {failwithf} else {Logging.err})
|
|
"WARNING: unexpected assembly command: %s@\n" (ClangCommand.command_to_run args)
|
|
| NonCCCommand args =>
|
|
/* Non-compilation (eg, linking) command. Run the command as-is. It will not get captured
|
|
further since `clang -### ...` will only output commands that invoke binaries using their
|
|
absolute paths. */
|
|
let argv = ClangCommand.get_orig_argv args;
|
|
Logging.out "Executing raw command: %s@\n" (String.concat " " (Array.to_list argv));
|
|
Process.create_process_and_wait argv
|
|
}
|
|
};
|
|
|
|
let exe args xx_suffix => {
|
|
let commands = normalize args;
|
|
/* xcodebuild projects may require the object files to be generated by the Apple compiler, eg to
|
|
generate precompiled headers compatible with Apple's clang. */
|
|
let should_run_original_command =
|
|
switch (Sys.getenv "FCP_APPLE_CLANG") {
|
|
| bin =>
|
|
let bin_xx = bin ^ xx_suffix;
|
|
Logging.out "Will run Apple clang %s" bin_xx;
|
|
args.(0) = bin_xx;
|
|
true
|
|
| exception Not_found => false
|
|
};
|
|
IList.iter execute_clang_command commands;
|
|
if (commands == [] || should_run_original_command) {
|
|
if (commands == []) {
|
|
/* No command to execute after -###, let's execute the original command
|
|
instead.
|
|
|
|
In particular, this can happen when
|
|
- there are only assembly commands to execute, which we skip, or
|
|
- the user tries to run `infer -- clang -c file_that_does_not_exist.c`. In this case, this
|
|
will fail with the appropriate error message from clang instead of silently analyzing 0
|
|
files. */
|
|
Logging.out
|
|
"WARNING: `clang -### <args>` returned an empty set of commands to run and no error. Will run the original command directly:@\n %s@\n"
|
|
(String.concat " " @@ Array.to_list args)
|
|
};
|
|
Process.create_process_and_wait args
|
|
}
|
|
};
|