#!/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-plugins/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}" # specify where is located Apple's clang APPLE_CLANG="${FCP_APPLE_CLANG}" 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 # (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 ATTACH_PLUGIN="1" IFS=$'\n' 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") if [ -n "$SYNTAX_ONLY" ]; then EXTRA_ARGS+=("-fsyntax-only") fi if [ -z "$DEBUG_MODE" ]; then # uglify json EXTRA_ARGS+=( "-Xclang" "-plugin-arg-${PLUGIN_NAME}" "-Xclang" "PRETTIFY_JSON=0") 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 piped to InferClang echo "${CLANG_CMD[@]} | tee ${OBJECT_FILENAME}.yjson | ${INFERCLANG_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 # 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 # run apple clang if required (and if any) if [ -n "$APPLE_CLANG" ]; then "${APPLE_CLANG}$XX" "$@" || exit $? fi exit $STATUS