From 596823bd32cc8e21bb9fde8de9a88260b4dca7e1 Mon Sep 17 00:00:00 2001 From: Jules Villard Date: Mon, 6 Jun 2016 07:36:18 -0700 Subject: [PATCH] support `clang -c file1.c file2.c` Summary: Use the output of `clang -###` to drive which commands to run. Attach the plugin to all commands starting with `-cc1`. Benefits: - support for compiling multiple files in one clang command, eg `infer -- clang -c file1.c file2.c` - support for compile commands that do not target a `.o` file, eg `infer -- clang -S hello.c` - support for `-cc1` compile commands - more generally, run all commands that clang would run, and attach plugin in all compilation cases Reviewed By: martinoluca Differential Revision: D3366912 fbshipit-source-id: 98d5e3b --- .../attach_plugin_and_run_clang_frontend.sh | 177 +++++++++++++++ .../lib/clang_wrappers/clang_general_wrapper | 205 ------------------ .../clang_wrappers/clang_general_wrapper++ | 1 - infer/lib/clang_wrappers/clang_wrapper++ | 1 - .../filter_args_and_run_fcp_clang | 1 + .../filter_args_and_run_fcp_clang++ | 1 + ...apper => filter_args_and_run_fcp_clang.sh} | 0 .../hijack_and_normalize_clang_command | 1 + .../hijack_and_normalize_clang_command++ | 1 + .../hijack_and_normalize_clang_command.sh | 66 ++++++ infer/lib/wrappers/clang | 7 +- infer/lib/xcode_wrappers/clang | 6 +- .../build_systems/build_integration_tests.py | 30 +++ .../tests/build_systems/codetoanalyze/hello.c | 1 + .../build_systems/codetoanalyze/hello2.c | 15 ++ .../expected_outputs/cc1_report.json | 7 + .../expected_outputs/multiclang_report.json | 12 + 17 files changed, 317 insertions(+), 215 deletions(-) create mode 100755 infer/lib/clang_wrappers/attach_plugin_and_run_clang_frontend.sh delete mode 100755 infer/lib/clang_wrappers/clang_general_wrapper delete mode 120000 infer/lib/clang_wrappers/clang_general_wrapper++ delete mode 120000 infer/lib/clang_wrappers/clang_wrapper++ create mode 120000 infer/lib/clang_wrappers/filter_args_and_run_fcp_clang create mode 120000 infer/lib/clang_wrappers/filter_args_and_run_fcp_clang++ rename infer/lib/clang_wrappers/{clang_wrapper => filter_args_and_run_fcp_clang.sh} (100%) create mode 120000 infer/lib/clang_wrappers/hijack_and_normalize_clang_command create mode 120000 infer/lib/clang_wrappers/hijack_and_normalize_clang_command++ create mode 100755 infer/lib/clang_wrappers/hijack_and_normalize_clang_command.sh create mode 120000 infer/tests/build_systems/codetoanalyze/hello.c create mode 100644 infer/tests/build_systems/codetoanalyze/hello2.c create mode 100644 infer/tests/build_systems/expected_outputs/cc1_report.json create mode 100644 infer/tests/build_systems/expected_outputs/multiclang_report.json diff --git a/infer/lib/clang_wrappers/attach_plugin_and_run_clang_frontend.sh b/infer/lib/clang_wrappers/attach_plugin_and_run_clang_frontend.sh new file mode 100755 index 000000000..1fcbc91db --- /dev/null +++ b/infer/lib/clang_wrappers/attach_plugin_and_run_clang_frontend.sh @@ -0,0 +1,177 @@ +#!/bin/bash + +# 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 the arguments a `clang -cc1 ...` command, attaches a plugin to +# the clang command, then run our own clang with all the arguments +# (passing through filter_args_and_run_fcp_clang.sh) and pipe the +# output to InferClang. + +#### Configuration #### +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +BIN_DIR="${SCRIPT_DIR}/../../bin" +ETC_DIR="${SCRIPT_DIR}/../../etc" + +# path to the wrapped clang compiler to invoke +CLANG_COMPILER="${SCRIPT_DIR}/filter_args_and_run_fcp_clang" +# extension of the file containing the clang cmd intercepted +CMD_FILE_EXT=".sh" +# extension of the file containing the output of the Infer Clang frontend +INFERCLANG_LOG_FILE_EXT=".astlog" +# path of the plugin to load in clang +PLUGIN_PATH="${SCRIPT_DIR}/../../../facebook-clang-plugins/libtooling/build/FacebookClangPlugin.dylib" +# name of the plugin to use +PLUGIN_NAME="BiniouASTExporter" +# output directory of the plugin +RESULTS_DIR="$FCP_RESULTS_DIR" +# this skips the creation of .o files +SYNTAX_ONLY="$FCP_RUN_SYNTAX_ONLY" +# extra arguments to pass during the execution of the infer frontend +INFER_FRONTEND_ARGS=($FCP_INFER_FRONTEND_ARGS) +# this fails the execution of clang if the frontend fails +REPORT_FRONTEND_FAILURE="$FCP_REPORT_FRONTEND_FAILURE" +# enable debug mode (to get more data saved to disk for future inspections) +DEBUG_MODE="$FCP_DEBUG_MODE" +# specify where is located Apple's clang +APPLE_CLANG="$FCP_APPLE_CLANG" +# whether to amend include search path with C++ model headers +INFER_CXX_MODELS="$FCP_INFER_CXX_MODELS" + +# invariants that this script expects +if [ -z "$RESULTS_DIR" ]; then + echo '$FCP_RESULTS_DIR with the name of the output directory not provided.' 1>&2 + exit 1 +fi +if [ "$1" != "-cc1" ]; then + echo "$0 expects to be run with -cc1" 1>&2 + exit 1 +fi + +# we know the first argument is "-cc1" +shift + + +# Functions +function get_option_argument { + # retrieves the value passed to an argument of a clang command + OPT="$1" + shift + while [ -n "$1" ] && [ "$1" != "$OPT" ]; do shift; done + echo "$2" +} + +function has_flag { + # return if the given flag is part of the given command or not + local FLAG="$1" + shift + while [ -n "$1" ] && [ "$1" != "$FLAG" ]; do shift; done + [ -n "$1" ]; +} + +# Main +INPUT_ARGUMENTS=("$@") + +# -cc1 has to be the first argument or clang will think it runs in driver mode +CLANG_CMD=("${CLANG_COMPILER}${XX}" "-cc1") + +# It's important to place this option before other -isystem options. +if [ -n "$INFER_CXX_MODELS" ]; then + CLANG_CMD+=("-isystem" "${SCRIPT_DIR}/../../models/cpp/include") +fi + +# (t7400979) this is a workaround to avoid that clang crashes when the -fmodules flag +# and the YojsonASTExporter plugin are used. Since the -plugin argument disables +# the generation of .o files, we invoke apple clang again to generate the expected +# artifacts. This will keep xcodebuild plus all the sub-steps happy. +if [ -n "$APPLE_CLANG" ]; then + ADD_PLUGIN_FLAG="-plugin" +else + ADD_PLUGIN_FLAG="-add-plugin" +fi + +CLANG_CMD+=( + "-load" + "${PLUGIN_PATH}" + "$ADD_PLUGIN_FLAG" + "${PLUGIN_NAME}" + "-plugin-arg-${PLUGIN_NAME}" + "-" + "-plugin-arg-${PLUGIN_NAME}" + "PREPEND_CURRENT_DIR=1") + +if [ -n "$SYNTAX_ONLY" ]; then + CLANG_CMD+=("-fsyntax-only") +fi + +if [ -n "$LLVM_MODE" ]; then + CLANG_CMD+=("-o" "-" "-g" "-S" "-emit-llvm") +fi + +# add the remaining arguments +CLANG_CMD+=("$@") + +# the source file is at the end of the command, match it with the wanted extensions +SOURCE_FILENAME="${INPUT_ARGUMENTS[${#INPUT_ARGUMENTS[@]} - 1]}" + +if ! [[ "$SOURCE_FILENAME" = /* ]]; then + SOURCE_FILENAME="$(pwd)/$SOURCE_FILENAME" +fi + +if [ -n "$LLVM_MODE" ]; then + INFER_FRONTEND_CMD=( + "${BIN_DIR}/InferLLVM" + "-c" "$SOURCE_FILENAME" + "-results_dir" "$RESULTS_DIR" + "${INFER_FRONTEND_ARGS[@]}") + INFER_FRONTEND_LOG_FILE="/dev/stdout" +else + LANGUAGE=$(get_option_argument "-x" "${INPUT_ARGUMENTS[@]}") + if [ -n "$LANGUAGE" ]; then INFER_FRONTEND_ARGS+=("-x" "$LANGUAGE"); fi + if has_flag "-fobjc-arc" "${INPUT_ARGUMENTS[@]}"; then + INFER_FRONTEND_ARGS+=("-fobjc-arc"); + fi + + INFER_FRONTEND_CMD=( + "${BIN_DIR}/InferClang" + "-c" "$SOURCE_FILENAME" + "-results_dir" "$RESULTS_DIR" + "${INFER_FRONTEND_ARGS[@]}") + + if [ -n "$DEBUG_MODE" ]; then + OBJECT_FILENAME="$(get_option_argument "-o" "${INPUT_ARGUMENTS[@]}")" + # Emit the clang command with the extra args piped to InferClang + echo "${CLANG_CMD[@]} " \ + "| tee ${OBJECT_FILENAME}.biniou " \ + "| ${INFER_FRONTEND_CMD[@]}" \ + > "${OBJECT_FILENAME}${CMD_FILE_EXT}" + echo "bdump -x -d ${ETC_DIR}/clang_ast.dict -w '!!DUMMY!!' ${OBJECT_FILENAME}.biniou " \ + "> ${OBJECT_FILENAME}.bdump" \ + >> "${OBJECT_FILENAME}${CMD_FILE_EXT}" + # Emit the InferClang cmd used to run the frontend + INFER_FRONTEND_LOG_FILE="${OBJECT_FILENAME}${INFERCLANG_LOG_FILE_EXT}" + echo "${INFER_FRONTEND_CMD[@]}" > "$INFER_FRONTEND_LOG_FILE" + else + INFER_FRONTEND_LOG_FILE="/dev/null" + fi +fi + +# run clang and pipe its output to InferClang/InferLLVM, or flush it in case the latter crashes +"${CLANG_CMD[@]}" | \ + ("${INFER_FRONTEND_CMD[@]}" || \ + { EC=$?; cat > /dev/null; exit $EC; }) \ + >> "$INFER_FRONTEND_LOG_FILE" 2>&1 +STATUSES=("${PIPESTATUS[@]}") +STATUS="${STATUSES[0]}" +INFER_STATUS="${STATUSES[1]}" + +# if clang fails, then fail, otherwise, fail with the frontend's exitcode if required +if [ "$STATUS" == 0 ] && [ -n "$REPORT_FRONTEND_FAILURE" ]; then + STATUS="$INFER_STATUS" +fi + +exit $STATUS diff --git a/infer/lib/clang_wrappers/clang_general_wrapper b/infer/lib/clang_wrappers/clang_general_wrapper deleted file mode 100755 index f1d6e5412..000000000 --- a/infer/lib/clang_wrappers/clang_general_wrapper +++ /dev/null @@ -1,205 +0,0 @@ -#!/bin/bash -# Clang wrapper to inject the execution of a plugin and execute the infer frontend - -# Initialization -PARENT=$(dirname "$0") -SCRIPT_DIR=$(cd "$PARENT" && pwd) -SCRIPT_DIR="${SCRIPT_DIR%/}" -BIN_DIR="${SCRIPT_DIR}/../../bin" -ETC_DIR="${SCRIPT_DIR}/../../etc" - -#### Configuration #### -# path to the wrapped clang compiler to invoke -CLANG_COMPILER="${SCRIPT_DIR}/clang_wrapper" -# extension of the file containing the clang cmd intercepted -CMD_FILE_EXT=".sh" -# extenion of the file containing the output of the Infer Clang frontend -INFERCLANG_LOG_FILE_EXT=".astlog" -# path of the plugin to load in clang -CLANG_PLUGIN_REL_PATH="facebook-clang-plugins/libtooling/build/FacebookClangPlugin.dylib" -PLUGIN_PATH="${SCRIPT_DIR}/../../../${CLANG_PLUGIN_REL_PATH}" -# name of the plugin to use -PLUGIN_NAME="BiniouASTExporter" -# output directory of the plugin -RESULTS_DIR="${FCP_RESULTS_DIR}" -# this forces the wrapper to invoke get_standard_commandline_args to get -# a more precise clang command with all the arguments in the right place (slow) -USE_STD_CLANG_CMD="${FCP_USE_STD_CLANG_CMD}" -# this skips the creation of .o files -SYNTAX_ONLY="${FCP_RUN_SYNTAX_ONLY}" -# extra arguments to pass during the execution of the infer frontend -INFER_FRONTEND_ARGS=($FCP_INFER_FRONTEND_ARGS) -# this fails the execution of clang if the frontend fails -REPORT_FRONTEND_FAILURE="${FCP_REPORT_FRONTEND_FAILURE}" -# enable debug mode (to get more data saved to disk for future inspections) -DEBUG_MODE="${FCP_DEBUG_MODE}" -# specify where is located Apple's clang -APPLE_CLANG="${FCP_APPLE_CLANG}" -# whether to amend include search path with C++ model headers -INFER_CXX_MODELS="${FCP_INFER_CXX_MODELS}" - -if [ -z "$RESULTS_DIR" ]; then - echo '$FCP_RESULTS_DIR with the name of the output directory not provided.' > /dev/stderr - exit 1 -fi - -if [ "${0%++}" != "$0" ]; then XX="++"; else XX=""; fi - -EXTRA_INCLUDE_PATH=() -if [ -n "$INFER_CXX_MODELS" ]; then - EXTRA_INCLUDE_PATH+=("-isystem") - EXTRA_INCLUDE_PATH+=("${SCRIPT_DIR}/../../models/cpp/include") -fi - -CLANG_CMD=("${CLANG_COMPILER}${XX}" "${EXTRA_INCLUDE_PATH[@]}" "$@") -CWD=$(pwd) -CWD="${CWD%/}" -[ ! -d "$RESULTS_DIR" ] && mkdir -p "$RESULTS_DIR" - -# Functions -function get_option_argument { - # retrieves the value passed to an argument of a clang command - OPT="$1" - shift - while [ -n "$1" ] && [ "$1" != "$OPT" ]; do shift; done - echo "$2" -} - -function has_flag { - # return if the given flag is part of the given command or not - local FLAG="$1" - shift - while [ -n "$1" ] && [ "$1" != "$FLAG" ]; do shift; done - [ -n "$1" ]; echo "$?" -} - -# Main -INPUT_ARGUMENTS=("$@") -if [ -n "$USE_STD_CLANG_CMD" ]; then - # this will run clang with the -### argument to get the command in a more - # standard format. - # Slow since it spawns clang as a separate process - STD_CMD="$($CLANG_COMPILER$XX -### "$@" 2>&1 | grep '^[[:space:]]\"' -m 1)" - # use sed to split all the arguments, and remove their surrounding double quotes - SED_CMD=$(echo "$STD_CMD" | sed -e 's/" "/\'$'\n/g' -e 's/^[[:space:]]*"//' -e 's/"[[:space:]]*$//') - IFS=$'\n' - # create an array of arguments using newline as separator - INPUT_ARGUMENTS=($SED_CMD) - unset IFS -fi - -OBJECT_FILENAME="$(get_option_argument "-o" "${INPUT_ARGUMENTS[@]}")" - -if echo "$OBJECT_FILENAME" | grep -q "\.o$" -then - # get the source file name - if [ -n "$USE_STD_CLANG_CMD" ]; then - # the source file is at the end of the command, match it with the wanted extensions - SOURCE_FILE=$(echo ${INPUT_ARGUMENTS[${#INPUT_ARGUMENTS[@]} - 1]}) - else - # in this case we search for the argument after -c, match it with the wanted extensions - SOURCE_FILE=$(get_option_argument "-c" "${INPUT_ARGUMENTS[@]}") - fi - - if [ -f "$SOURCE_FILE" ] - then - # (t7400979) this is a workaround to avoid that clang crashes when the -fmodules flag - # and the YojsonASTExporter plugin are used. Since the -plugin argument disables - # the generation of .o files, we invoke apple clang again to generate the expected - # artifacts. This will keep xcodebuild plus all the sub-steps happy. - if [ -n "$APPLE_CLANG" ]; then - ADD_PLUGIN_FLAG="-plugin" - else - ADD_PLUGIN_FLAG="-add-plugin" - fi - if [ -z "$LLVM_MODE" ]; then - ATTACH_PLUGIN="1" - fi - IFS=$'\n' - if [ -n "$ATTACH_PLUGIN" ]; then - EXTRA_ARGS=("-Xclang" "-load" - "-Xclang" "${PLUGIN_PATH}" - "-Xclang" "$ADD_PLUGIN_FLAG" - "-Xclang" "${PLUGIN_NAME}" - "-Xclang" "-plugin-arg-${PLUGIN_NAME}" - "-Xclang" "-" - "-Xclang" "-plugin-arg-${PLUGIN_NAME}" - "-Xclang" "PREPEND_CURRENT_DIR=1") - fi - if [ -n "$SYNTAX_ONLY" ]; then - EXTRA_ARGS+=("-fsyntax-only") - fi - unset IFS - - if [ -n "$LLVM_MODE" ]; then - EXTRA_ARGS+=("-o" "-" "-g" "-S" "-emit-llvm") - fi - - # using always the original clang command for several reasons: - # - to avoid handling the presence/absence of -Xclang if the standard command is used - # - to emit the same command that was captured by this wrapper - # - to invoke the linker, whenever is needed - CLANG_CMD+=("${EXTRA_ARGS[@]}") - fi -fi - -if [ -n "$ATTACH_PLUGIN" ] || [ -n "$LLVM_MODE" ]; then - [[ "$SOURCE_FILE" = /* ]] || { SOURCE_FILE="${CWD}/$SOURCE_FILE"; } - - if [ -n "$LLVM_MODE" ]; then - INFER_FRONTEND_CMD=( - "${BIN_DIR}/InferLLVM" - "-c" "$SOURCE_FILE" - "-results_dir" "$RESULTS_DIR" - "${INFER_FRONTEND_ARGS[@]}") - INFER_FRONTEND_LOG_FILE="/dev/stdout" - else - FOBJC_ARC_FLAG=$(has_flag "-fobjc-arc" "${INPUT_ARGUMENTS[@]}") - LANGUAGE=$(get_option_argument "-x" "${INPUT_ARGUMENTS[@]}") - if [ -n "$LANGUAGE" ]; then INFER_FRONTEND_ARGS+=("-x" "$LANGUAGE"); fi - if [ "$FOBJC_ARC_FLAG" == 0 ]; then INFER_FRONTEND_ARGS+=("-fobjc-arc"); fi - - INFER_FRONTEND_CMD=( - "${BIN_DIR}/InferClang" - "-c" "$SOURCE_FILE" - "-results_dir" "$RESULTS_DIR" - "${INFER_FRONTEND_ARGS[@]}") - - if [ -n "$DEBUG_MODE" ]; then - # Emit the clang command with the extra args piped to InferClang - echo "${CLANG_CMD[@]} " \ - "| tee ${OBJECT_FILENAME}.biniou " \ - "| ${INFER_FRONTEND_CMD[@]}" \ - > "${OBJECT_FILENAME}${CMD_FILE_EXT}" - echo "bdump -x -d ${ETC_DIR}/clang_ast.dict -w '!!DUMMY!!' ${OBJECT_FILENAME}.biniou " \ - "> ${OBJECT_FILENAME}.bdump" \ - >> "${OBJECT_FILENAME}${CMD_FILE_EXT}" - # Emit the InferClang cmd used to run the frontend - INFER_FRONTEND_LOG_FILE="${OBJECT_FILENAME}${INFERCLANG_LOG_FILE_EXT}" - echo "${INFER_FRONTEND_CMD[@]}" > "$INFER_FRONTEND_LOG_FILE" - else - INFER_FRONTEND_LOG_FILE="/dev/null" - fi - fi - - # run clang and pipe its output to InferClang/InferLLVM, or flush it in case the latter crashes - "${CLANG_CMD[@]}" | ("${INFER_FRONTEND_CMD[@]}" || { EC=$?; cat > /dev/null; exit $EC; }) >> "$INFER_FRONTEND_LOG_FILE" 2>&1 - STATUSES=("${PIPESTATUS[@]}") - STATUS="${STATUSES[0]}" - INFER_STATUS="${STATUSES[1]}" - - # if clang fails, then fail, otherwise, fail with the frontend's exitcode if required - if [ "$STATUS" == 0 ] && [ -n "$REPORT_FRONTEND_FAILURE" ]; then - STATUS="$INFER_STATUS" - fi -else - "${CLANG_CMD[@]}" - STATUS=$? -fi - -# run apple clang if required (and if any) -if [ -n "$APPLE_CLANG" ]; then - "${APPLE_CLANG}$XX" "$@" || exit $? -fi - -exit $STATUS diff --git a/infer/lib/clang_wrappers/clang_general_wrapper++ b/infer/lib/clang_wrappers/clang_general_wrapper++ deleted file mode 120000 index 67b0ba784..000000000 --- a/infer/lib/clang_wrappers/clang_general_wrapper++ +++ /dev/null @@ -1 +0,0 @@ -clang_general_wrapper \ No newline at end of file diff --git a/infer/lib/clang_wrappers/clang_wrapper++ b/infer/lib/clang_wrappers/clang_wrapper++ deleted file mode 120000 index 4fc0dde1b..000000000 --- a/infer/lib/clang_wrappers/clang_wrapper++ +++ /dev/null @@ -1 +0,0 @@ -clang_wrapper \ No newline at end of file diff --git a/infer/lib/clang_wrappers/filter_args_and_run_fcp_clang b/infer/lib/clang_wrappers/filter_args_and_run_fcp_clang new file mode 120000 index 000000000..12a508574 --- /dev/null +++ b/infer/lib/clang_wrappers/filter_args_and_run_fcp_clang @@ -0,0 +1 @@ +filter_args_and_run_fcp_clang.sh \ No newline at end of file diff --git a/infer/lib/clang_wrappers/filter_args_and_run_fcp_clang++ b/infer/lib/clang_wrappers/filter_args_and_run_fcp_clang++ new file mode 120000 index 000000000..12a508574 --- /dev/null +++ b/infer/lib/clang_wrappers/filter_args_and_run_fcp_clang++ @@ -0,0 +1 @@ +filter_args_and_run_fcp_clang.sh \ No newline at end of file diff --git a/infer/lib/clang_wrappers/clang_wrapper b/infer/lib/clang_wrappers/filter_args_and_run_fcp_clang.sh similarity index 100% rename from infer/lib/clang_wrappers/clang_wrapper rename to infer/lib/clang_wrappers/filter_args_and_run_fcp_clang.sh diff --git a/infer/lib/clang_wrappers/hijack_and_normalize_clang_command b/infer/lib/clang_wrappers/hijack_and_normalize_clang_command new file mode 120000 index 000000000..d2862a31c --- /dev/null +++ b/infer/lib/clang_wrappers/hijack_and_normalize_clang_command @@ -0,0 +1 @@ +hijack_and_normalize_clang_command.sh \ No newline at end of file diff --git a/infer/lib/clang_wrappers/hijack_and_normalize_clang_command++ b/infer/lib/clang_wrappers/hijack_and_normalize_clang_command++ new file mode 120000 index 000000000..d2862a31c --- /dev/null +++ b/infer/lib/clang_wrappers/hijack_and_normalize_clang_command++ @@ -0,0 +1 @@ +hijack_and_normalize_clang_command.sh \ No newline at end of file diff --git a/infer/lib/clang_wrappers/hijack_and_normalize_clang_command.sh b/infer/lib/clang_wrappers/hijack_and_normalize_clang_command.sh new file mode 100755 index 000000000..6d26511ec --- /dev/null +++ b/infer/lib/clang_wrappers/hijack_and_normalize_clang_command.sh @@ -0,0 +1,66 @@ +#!/bin/bash + +# 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 the more-or-less raw arguments passed to clang as arguments, +# this normalizes them via `clang -###` if needed to call the script +# that actually attaches the plugin on each source file. Unless we +# don't want to attach the plugin, in which case just run the original +# command. + +#### Configuration #### +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +# script to run our own clang +CLANG_COMPILER="${SCRIPT_DIR}/filter_args_and_run_fcp_clang" +# script to attach the plugin to clang -cc1 commands and run InferClang +CLANG_CC1_CAPTURE="${SCRIPT_DIR}/attach_plugin_and_run_clang_frontend.sh" +# path to Apple's clang +APPLE_CLANG="$FCP_APPLE_CLANG" + +# Main +if [ "${0%++}" != "$0" ]; then XX="++"; fi + +# Normalize clang command if not -cc1 already. -cc1 is always the first argument if present. +if [ "$1" = "-cc1" ]; then + "$CLANG_CC1_CAPTURE" "$@" + STATUS=$? +else + # Run `clang -###` to get one compile command per source file. + # Slow since it spawns clang as a separate process + # + # Generate a command containing all the commands in the output of `clang -###`. These are + # the lines that start with ' "/absolute/path/to/binary"'. + # + # In that command, replace /absolute/path/to/clang with our own wrapper, but only for the + # core compiler commands (those that start with "-cc1"). This means we'll capture all + # compilation commands (one per source file), without interfering with non-compiler commands + # (as they run with absolute paths, so they won't get captured again further down the line). + # + # Fail on errors: if we detect an error in the output of `clang -###`, we add the line + # `echo ; exit 1` to the generated command. This is because `clang -###` pretty much + # never fails, but warns of failures on stderr instead. + CC_COMMAND=$("$CLANG_COMPILER$XX" -### "$@" 2>&1 | \ + # only keep lines that are commands or errors + grep -e '^\([[:space:]]\"\|clang: error:\)' | \ + # replace -cc1 commands with our clang wrapper + sed -e "s#^[[:space:]]\"\([^\"]*\)\" \"-cc1\" \(.*\)\$# \"$CLANG_CC1_CAPTURE\" \"-cc1\" \2#g" | \ + # replace error messages by failures + sed -e 's#^\(^clang: error:.*$\)#echo "\1"; exit 1#g' | \ + # add trailing ; to each line + sed -e 's/$/;/g') + eval $CC_COMMAND + STATUS=$? +fi + +# run Apple clang if required (and if any) +if [ -n "$APPLE_CLANG" ]; then + "$APPLE_CLANG$XX" "$@" || exit $? +fi + +exit $STATUS diff --git a/infer/lib/wrappers/clang b/infer/lib/wrappers/clang index e0a92ba06..6aebd5179 100755 --- a/infer/lib/wrappers/clang +++ b/infer/lib/wrappers/clang @@ -10,9 +10,9 @@ if [ -z "$INFER_RESULTS_DIR" ]; then fi # invoke the right compiler looking at the final plusplus (e.g. gcc/g++ clang/clang++) -if [ "${0%++}" != "$0" ]; then XX="++"; else XX=""; fi -FRONTEND_COMMAND=("$SCRIPT_DIR/../clang_wrappers/clang_general_wrapper$XX" "$@") -HOST_COMPILER_COMMAND=("$SCRIPT_DIR/../clang_wrappers/clang_wrapper$XX" "$@") +if [ "${0%++}" != "$0" ]; then XX="++"; fi +FRONTEND_COMMAND=("$SCRIPT_DIR/../clang_wrappers/hijack_and_normalize_clang_command$XX" "$@") +HOST_COMPILER_COMMAND=("$SCRIPT_DIR/../clang_wrappers/filter_args_and_run_fcp_clang$XX" "$@") if [ -n "$INFER_COMPILER_WRAPPER_IN_RECURSION" ]; then if [ -z "$INFER_LISTENER" ]; then @@ -21,7 +21,6 @@ if [ -n "$INFER_COMPILER_WRAPPER_IN_RECURSION" ]; then else export INFER_COMPILER_WRAPPER_IN_RECURSION="Y" export FCP_RESULTS_DIR="$INFER_RESULTS_DIR"; - export FCP_USE_STD_CLANG_CMD="1"; "${FRONTEND_COMMAND[@]}" fi diff --git a/infer/lib/xcode_wrappers/clang b/infer/lib/xcode_wrappers/clang index 7b0536a62..bfef4ed44 100755 --- a/infer/lib/xcode_wrappers/clang +++ b/infer/lib/xcode_wrappers/clang @@ -5,9 +5,8 @@ CLANG_WRAPPERS_PATH="${SCRIPT_PATH}/../clang_wrappers" if [ "${0%++}" != "$0" ]; then XX="++"; else XX=""; fi -export FCP_CLANG_COMPILER="${CLANG_WRAPPERS_PATH%/}/clang_wrapper$XX"; +export FCP_CLANG_COMPILER="${CLANG_WRAPPERS_PATH%/}/filter_args_and_run_fcp_clang$XX"; export FCP_RESULTS_DIR="${INFER_RESULTS_DIR}"; -export FCP_USE_STD_CLANG_CMD="1"; if [ -z "$INFER_RESULTS_DIR" ]; then # this redirects to the compiler without adding any FCP flag @@ -17,5 +16,4 @@ if [ -z "$INFER_RESULTS_DIR" ]; then exit $? fi - -"${CLANG_WRAPPERS_PATH%/}/clang_general_wrapper$XX" "$@" -fno-cxx-modules +"${CLANG_WRAPPERS_PATH%/}/hijack_and_normalize_clang_command$XX" "$@" -fno-cxx-modules diff --git a/infer/tests/build_systems/build_integration_tests.py b/infer/tests/build_systems/build_integration_tests.py index beba5d0d5..ea03f37c4 100755 --- a/infer/tests/build_systems/build_integration_tests.py +++ b/infer/tests/build_systems/build_integration_tests.py @@ -40,6 +40,9 @@ from inferlib import config, issues, utils ROOT_DIR = os.path.join(SCRIPT_DIR, os.pardir, os.pardir, os.pardir) +CLANG_BIN = os.path.join(ROOT_DIR, + 'facebook-clang-plugins', 'clang', 'bin', 'clang') + REPORT_JSON = 'report.json' INFER_EXECUTABLE = 'infer' @@ -58,11 +61,13 @@ EXPECTED_OUTPUTS_DIR = os.path.join(SCRIPT_DIR, 'expected_outputs') ALL_TESTS = [ 'ant', 'buck', + 'cc1', 'cmake', 'gradle', 'javac', 'locale', 'make', + 'multiclang', 'unknown_ext', 'utf8_in_pwd', 'waf', @@ -401,6 +406,31 @@ class BuildIntegrationTest(unittest.TestCase): CODETOANALYZE_DIR, [['clang', '-x', 'c', '-c', 'hello.unknown_ext']]) + def test_clang_multiple_source_files(self): + test('multiclang', 'clang multiple source files', + CODETOANALYZE_DIR, + [['clang', '-c', 'hello.c', 'hello2.c']]) + + def test_clang_cc1(self): + def preprocess(): + hashhashhash = subprocess.check_output( + [CLANG_BIN, '-###', '-c', 'hello.c'], + # `clang -### -c hello.c` prints on stderr + stderr=subprocess.STDOUT) + # pick the line containing the compilation command, which + # should be the only one to include "-cc1" + cc1_line = filter(lambda s: s.find('"-cc1"') != -1, + hashhashhash.splitlines())[0] + # [cc1_line] usually looks like ' "/foo/clang" "bar" "baz"'. + # return [['clang', 'bar', 'baz']] + cmd = [s.strip('"') for s in cc1_line.strip().split('" "')] + cmd[0] = 'clang' + return [cmd] + test('cc1', 'clang -cc1', + CODETOANALYZE_DIR, + [], + preprocess=preprocess) + if __name__ == '__main__': # hackish capturing of the arguments after '--' diff --git a/infer/tests/build_systems/codetoanalyze/hello.c b/infer/tests/build_systems/codetoanalyze/hello.c new file mode 120000 index 000000000..2227f671f --- /dev/null +++ b/infer/tests/build_systems/codetoanalyze/hello.c @@ -0,0 +1 @@ +../../../../examples/hello.c \ No newline at end of file diff --git a/infer/tests/build_systems/codetoanalyze/hello2.c b/infer/tests/build_systems/codetoanalyze/hello2.c new file mode 100644 index 000000000..6910ad214 --- /dev/null +++ b/infer/tests/build_systems/codetoanalyze/hello2.c @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2015 - 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. + */ + +#include + +void test2() { + int* s = NULL; + *s = 42; +} diff --git a/infer/tests/build_systems/expected_outputs/cc1_report.json b/infer/tests/build_systems/expected_outputs/cc1_report.json new file mode 100644 index 000000000..a6ed10eff --- /dev/null +++ b/infer/tests/build_systems/expected_outputs/cc1_report.json @@ -0,0 +1,7 @@ +[ + { + "bug_type": "NULL_DEREFERENCE", + "file": "hello.c", + "procedure": "test" + } +] \ No newline at end of file diff --git a/infer/tests/build_systems/expected_outputs/multiclang_report.json b/infer/tests/build_systems/expected_outputs/multiclang_report.json new file mode 100644 index 000000000..2fd39e4b3 --- /dev/null +++ b/infer/tests/build_systems/expected_outputs/multiclang_report.json @@ -0,0 +1,12 @@ +[ + { + "bug_type": "NULL_DEREFERENCE", + "file": "hello.c", + "procedure": "test" + }, + { + "bug_type": "NULL_DEREFERENCE", + "file": "hello2.c", + "procedure": "test2" + } +] \ No newline at end of file