(*
* 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 .
* )
open ! IStd
module L = Logging
type t = { exec : string ; argv : string list ; orig_argv : string list ; quoting_style : ClangQuotes . style }
let fcp_dir =
Config . bin_dir ^/ Filename . parent_dir_name ^/ Filename . parent_dir_name
^/ " facebook-clang-plugins "
(* * path of the plugin to load in clang *)
let plugin_path = fcp_dir ^/ " libtooling " ^/ " build " ^/ " FacebookClangPlugin.dylib "
(* * name of the plugin to use *)
let plugin_name = " BiniouASTExporter "
let value_of_argv_option argv opt_name =
List . fold
~ f : ( fun ( prev_arg , result ) arg ->
let result' =
if Option . is_some result then result
else if String . equal opt_name prev_arg then Some arg
else None
in
( arg , result' ) )
~ init : ( " " , None ) argv
| > snd
let value_of_option { orig_argv } = value_of_argv_option orig_argv
let has_flag { orig_argv } flag = List . exists ~ f : ( String . equal flag ) orig_argv
let can_attach_ast_exporter cmd =
let is_supported_language cmd =
match value_of_option cmd " -x " with
| None ->
L . external_warning " malformed -cc1 command has no \" -x \" flag! " ;
false
| Some lang when String . is_prefix ~ prefix : " assembler " lang ->
false
| Some _ ->
true
in
(* -Eonly is -cc1 flag that gets produced by 'clang -M -### ...' *)
let is_preprocessor_only cmd = has_flag cmd " -E " | | has_flag cmd " -Eonly " in
has_flag cmd " -cc1 " && is_supported_language cmd && not ( is_preprocessor_only cmd )
let argv_cons a b = a :: b
let argv_do_if cond action x = if cond then action x else x
let file_arg_cmd_sanitizer cmd =
let file = ClangQuotes . mk_arg_file " clang_command_ " cmd . quoting_style cmd . argv in
{ cmd with argv = [ Format . sprintf " @%s " file ] }
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 )
? ( blacklisted_flags = [] ) ? ( blacklisted_flags_with_arg = [] ) ? ( post_args = [] ) args =
let rec aux ( prev , res_rev ) args =
match args with
| [] ->
(* return non-reversed list *)
List . rev_append res_rev post_args
| 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
| arg :: tl ->
let res_rev' = replace_option_arg prev arg :: res_rev in
aux ( arg , res_rev' ) tl
in
aux ( " " , [] ) args
(* Work around various path or library issues occurring when one tries to substitute Apple's version
of clang with a different version . Also mitigate version discrepancies in clang'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
in
let args_defines = if Config . bufferoverrun then [ " -D__INFER_BUFFEROVERRUN " ] else [] in
let post_args_rev =
[] | > List . rev_append [ " -include " ; Config . lib_dir ^/ " clang_wrappers " ^/ " global_defines.h " ]
| > List . rev_append args_defines
| > (* Never error on warnings. Clang is often more strict than Apple's version. These arguments
are appended at the end to override previous opposite settings . How it's done : suppress
all the warnings , since there are no warnings , compiler can't elevate them to error
level . * )
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
in
file_arg_cmd_sanitizer { cmd with argv = clang_arguments }
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
{ exec = prog ; orig_argv = sanitized_args ; argv = sanitized_args ; quoting_style }
let command_to_run cmd =
let mk_cmd normalizer =
let { exec ; argv ; quoting_style } = normalizer cmd in
Printf . sprintf " '%s' %s " exec
( List . map ~ f : ( ClangQuotes . quote quoting_style ) argv | > String . concat ~ sep : " " )
in
if can_attach_ast_exporter cmd then mk_cmd clang_cc1_cmd_sanitizer
else if String . is_prefix ~ prefix : " clang " ( Filename . basename cmd . exec ) then
(* `clang` supports argument files and the commands can be longer than the maximum length of the
command line , so put arguments in a file * )
mk_cmd file_arg_cmd_sanitizer
else (* other commands such as `ld` do not support argument files *)
mk_cmd ( fun x -> x )
let with_exec exec args = { args with exec }
let with_plugin_args args =
if Config . cxx_infer_headers && Config . biabduction && Config . bufferoverrun then
L . ( die UserError )
" The biabduction and bufferoverrun analyses have conflicting header models for C++. Either disable infer's custom C++ headers (--no-cxx-infer-headers), the biabduction analysis (--no-biabduction), or the bufferoverrun analysis (--no-bufferoverrun). " ;
let plugin_arg_flag = " -plugin-arg- " ^ plugin_name in
let args_before_rev =
[]
| > (* -cc1 has to be the first argument or clang will think it runs in driver mode *)
argv_cons " -cc1 "
| > (* It's important to place this option before other -isystem options. *)
argv_do_if
Config . ( cxx_infer_headers && ( biabduction | | bufferoverrun | | siof ) )
( List . rev_append [ " -isystem " ; Config . cpp_extra_include_dir ] )
| > List . rev_append
[ " -load "
; plugin_path
; (* ( 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 has_flag args " -fmodules " then " -plugin " else " -add-plugin " )
; plugin_name
; plugin_arg_flag
; " - "
; plugin_arg_flag
; " PREPEND_CURRENT_DIR=1 "
; plugin_arg_flag
; " MAX_STRING_SIZE= " ^ string_of_int CFrontend_config . biniou_buffer_size ]
in
(* add -O0 option to avoid compiler obfuscation of AST *)
let args_after_rev =
[] | > argv_cons " -O0 " | > argv_do_if Config . fcp_syntax_only ( argv_cons " -fsyntax-only " )
in
{ args with argv = List . rev_append args_before_rev ( args . argv @ List . rev args_after_rev ) }
let prepend_arg arg clang_args = { clang_args with argv = arg :: clang_args . argv }
let prepend_args args clang_args = { clang_args with argv = args @ clang_args . argv }
let append_args args clang_args = { clang_args with argv = clang_args . argv @ args }
let get_orig_argv { exec ; orig_argv } = Array . of_list ( exec :: orig_argv )