|
|
|
#!/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"
|
|
|
|
|
|
|
|
#### 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-plugin/libtooling/build/FacebookClangPlugin.dylib"
|
|
|
|
PLUGIN_PATH="${SCRIPT_DIR}/../../../../${CLANG_PLUGIN_REL_PATH}"
|
|
|
|
# name of the plugin to use
|
|
|
|
PLUGIN_NAME="YojsonASTExporter"
|
|
|
|
# output directory of the plugin
|
|
|
|
RESULTS_DIR="${FCP_RESULTS_DIR}"
|
|
|
|
# space-separated list of source file extensions to compile, where the plugin is needed
|
|
|
|
# e.g. EXTENSIONS="c cpp m mm" (*note* no dots needed)
|
|
|
|
EXTENSIONS="${FCP_EXTENSIONS}"
|
|
|
|
# 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}"
|
|
|
|
|
|
|
|
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
|
|
|
|
CLANG_CMD=("${CLANG_COMPILER}${XX}" "$@")
|
|
|
|
CWD=$(pwd)
|
|
|
|
CWD="${CWD%/}"
|
|
|
|
[ ! -d "$RESULTS_DIR" ] && mkdir -p "$RESULTS_DIR"
|
|
|
|
|
|
|
|
# If no extensions provided, will use default ones (c, h, cc, cpp, m, mm)
|
|
|
|
if [ -z "$EXTENSIONS" ]; then
|
|
|
|
EXTENSIONS="c h cc cpp m mm"
|
|
|
|
fi
|
|
|
|
|
|
|
|
# regular expression for grep to look for specific extensions
|
|
|
|
# (for example c,h,m files)
|
|
|
|
EXTENSIONS_REGEX="\.($(echo $EXTENSIONS | tr ' ' '|'))$"
|
|
|
|
|
|
|
|
# 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]} \
|
|
|
|
| grep -i -E "$EXTENSIONS_REGEX")
|
|
|
|
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[@]}" \
|
|
|
|
| grep -i -E "$EXTENSIONS_REGEX")
|
|
|
|
fi
|
|
|
|
|
|
|
|
if [ -n "$SOURCE_FILE" ]
|
|
|
|
then
|
|
|
|
ATTACH_PLUGIN="1"
|
|
|
|
IFS=$'\n'
|
|
|
|
EXTRA_ARGS=("-Xclang" "-load"
|
|
|
|
"-Xclang" "${PLUGIN_PATH}"
|
|
|
|
"-Xclang" "-add-plugin"
|
|
|
|
"-Xclang" "${PLUGIN_NAME}")
|
|
|
|
if [ -n "$SYNTAX_ONLY" ]; then
|
|
|
|
EXTRA_ARGS+=("-fsyntax-only")
|
|
|
|
fi
|
|
|
|
unset IFS
|
|
|
|
|
|
|
|
# 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" ]; then
|
|
|
|
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
|
|
|
|
|
|
|
|
[[ "$SOURCE_FILE" = /* ]] || { SOURCE_FILE="${CWD}/$SOURCE_FILE"; }
|
|
|
|
INFERCLANG_CMD=(
|
|
|
|
"${BIN_DIR}/InferClang"
|
|
|
|
"-c" "$SOURCE_FILE"
|
|
|
|
"-results_dir" "$RESULTS_DIR"
|
|
|
|
"${INFER_FRONTEND_ARGS[@]}")
|
|
|
|
|
|
|
|
INFERCLANG_LOG_FILE="/dev/null"
|
|
|
|
|
|
|
|
if [ -n "$DEBUG_MODE" ]; then
|
|
|
|
# Emit the clang command with the extra args
|
|
|
|
echo "${CLANG_CMD[@]}" > "${OBJECT_FILENAME}${CMD_FILE_EXT}"
|
|
|
|
# Emit the InferClang cmd used to run the frontend
|
|
|
|
INFERCLANG_LOG_FILE="${OBJECT_FILENAME}${INFERCLANG_LOG_FILE_EXT}"
|
|
|
|
echo "${INFERCLANG_CMD[@]}" > "$INFERCLANG_LOG_FILE"
|
|
|
|
fi
|
|
|
|
|
|
|
|
export CLANG_FRONTEND_PLUGIN__PREPEND_CURRENT_DIR="1"
|
|
|
|
# run clang and pipe its output to InferClang, or flush it in case the latter crashes
|
|
|
|
"${CLANG_CMD[@]}" | ("${INFERCLANG_CMD[@]}" || { EC=$?; cat > /dev/null; exit $EC; }) >> "$INFERCLANG_LOG_FILE" 2>&1
|
|
|
|
STATUSES=("${PIPESTATUS[@]}")
|
|
|
|
STATUS="${STATUSES[0]}"
|
|
|
|
INFERCLANG_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="$INFERCLANG_STATUS"
|
|
|
|
fi
|
|
|
|
else
|
|
|
|
"${CLANG_CMD[@]}"
|
|
|
|
STATUS=$?
|
|
|
|
fi
|
|
|
|
|
|
|
|
exit $STATUS
|