From a480d4bf4f745f8940309666fbc6d8a37c64ae04 Mon Sep 17 00:00:00 2001
From: Andre Maroneze <andre.oliveiramaroneze@cea.fr>
Date: Wed, 4 Mar 2020 19:11:32 +0100
Subject: [PATCH] [Kernel] add option -cpp-extra-args-per-file

---
 doc/userman/user-changes.tex                  |  5 +++
 doc/userman/user-sources.tex                  |  2 ++
 man/frama-c.1                                 |  8 ++++-
 man/frama-c.1.header                          |  2 +-
 man/frama-c.1.md                              |  4 +++
 src/kernel_services/ast_queries/file.ml       | 13 ++++++-
 .../cmdline_parameters/parameter_builder.ml   | 24 +++++++++++++
 .../cmdline_parameters/parameter_sig.mli      | 16 +++++++++
 .../plugin_entry_points/kernel.ml             | 34 +++++++++++++++++++
 .../plugin_entry_points/kernel.mli            |  3 ++
 tests/misc/cpp-extra-args-per-file1.c         | 20 +++++++++++
 tests/misc/cpp-extra-args-per-file2.c         | 15 ++++++++
 .../cpp-extra-args-per-file1.res.oracle       |  2 ++
 13 files changed, 145 insertions(+), 3 deletions(-)
 create mode 100644 tests/misc/cpp-extra-args-per-file1.c
 create mode 100644 tests/misc/cpp-extra-args-per-file2.c
 create mode 100644 tests/misc/oracle/cpp-extra-args-per-file1.res.oracle

diff --git a/doc/userman/user-changes.tex b/doc/userman/user-changes.tex
index e9e0c934891..75f767128b9 100644
--- a/doc/userman/user-changes.tex
+++ b/doc/userman/user-changes.tex
@@ -5,6 +5,11 @@ release. First we list changes of the last release.
 
 \section*{\nextframacversion}
 
+\begin{itemize}
+\item \textbf{Preparing the Sources:} added option
+  \texttt{-cpp-extra-args-per-file}.
+\end{itemize}
+
 \section*{20.0 (Calcium)}
 
 \begin{itemize}
diff --git a/doc/userman/user-sources.tex b/doc/userman/user-sources.tex
index 8fc0e1180af..cba9cf513df 100644
--- a/doc/userman/user-sources.tex
+++ b/doc/userman/user-sources.tex
@@ -59,6 +59,8 @@ allows the user to extend the pre-processing command,
 typically via the inclusion of defines (\texttt{-D} switches) and header
 directories (\texttt{-I} switches), as in
 \texttt{-cpp-extra-args="-DDEBUG -DARCH=ia32 -I./headers"}.
+You can also add arguments on a per-file basis, using option
+\optiondef{-}{cpp-extra-args-per-file}.
 
 If you have a JSON compilation database file\footnote{%
   \url{http://clang.llvm.org/docs/JSONCompilationDatabase.html}}, you can use
diff --git a/man/frama-c.1 b/man/frama-c.1
index 0d19ee714ab..706d54135e1 100644
--- a/man/frama-c.1
+++ b/man/frama-c.1
@@ -25,7 +25,7 @@
 .\" using pandoc 2.0 or newer. To modify this file, edit the Markdown file
 .\" and run `make man/frama-c.1`.
 
-.TH FRAMA-C 1 2019-07-29
+.TH FRAMA-C 1 2020-03-05
 .SH NAME
 .PP
 frama\-c[.byte] \- a static analyzer for C programs
@@ -262,6 +262,12 @@ definitions) must thus go there instead of \f[B]\-cpp\-command\f[].
 .RS
 .RE
 .TP
+.B \-cpp\-extra\-args\-per\-file \f[I]file1:args1,\&...,filen:argsn\f[]
+like \f[B]\-cpp\-extra\-args\f[], but the arguments only apply to the
+specified files.
+.RS
+.RE
+.TP
 .B [\-no]\-cpp\-frama\-c\-compliant
 indicates that the chosen preprocessor complies to some Frama\-C
 requirements, such as accepting the same set of options as GNU cpp, and
diff --git a/man/frama-c.1.header b/man/frama-c.1.header
index 367a0c8bd4b..ce56197c3e9 100644
--- a/man/frama-c.1.header
+++ b/man/frama-c.1.header
@@ -25,4 +25,4 @@
 .\" using pandoc 2.0 or newer. To modify this file, edit the Markdown file
 .\" and run `make man/frama-c.1`.
 
-.TH FRAMA-C 1 2019-07-29
+.TH FRAMA-C 1 2020-03-05
diff --git a/man/frama-c.1.md b/man/frama-c.1.md
index 7e21eccfa57..c5835e67eb2 100644
--- a/man/frama-c.1.md
+++ b/man/frama-c.1.md
@@ -156,6 +156,10 @@ pre-processed. *args* are used only for the first pass, so that arguments that
 should not be used twice (such as additional include directives or macro
 definitions) must thus go there instead of **-cpp-command**.
 
+-cpp-extra-args-per-file *file1:args1,...,filen:argsn*
+: like **-cpp-extra-args**, but the arguments only apply to the specified
+files.
+
 [-no]-cpp-frama-c-compliant
 : indicates that the chosen preprocessor complies to some Frama-C requirements,
 such as accepting the same set of options as GNU cpp, and accepting
diff --git a/src/kernel_services/ast_queries/file.ml b/src/kernel_services/ast_queries/file.ml
index 99008be9f27..0267c03d381 100644
--- a/src/kernel_services/ast_queries/file.ml
+++ b/src/kernel_services/ast_queries/file.ml
@@ -123,12 +123,22 @@ let from_filename ?cpp f =
     if cmdline <> "" then
       cmdline, cpp_opt_kind ()
     else
-      let flags = Json_compilation_database.get_flags f in
+      let jcdb_flags = Json_compilation_database.get_flags f in
       let cpp, gnu =
         match cpp with
         | None -> get_preprocessor_command ()
         | Some cpp -> cpp, cpp_opt_kind ()
       in
+      let extra_flags =
+        try Kernel.CppExtraArgsPerFile.find f
+        with Not_found -> ""
+      in
+      if jcdb_flags <> [] && extra_flags <> "" then
+        Kernel.warning ~once:true
+          "found flags for file %a both in json compilation database and \
+           -cpp-extra-args-per-file; this is unsupported and non-deterministic \
+           behavior may happen" Filepath.Normalized.pretty f;
+      let flags = extra_flags :: jcdb_flags in
       (if flags = [] then cpp else cpp ^ " " ^ String.concat " " flags), gnu
   in
   if Filename.check_suffix (f:>string) ".i" then begin
@@ -173,6 +183,7 @@ end = struct
         let dependencies =
           [ Kernel.CppCommand.self;
             Kernel.CppExtraArgs.self;
+            Kernel.CppExtraArgsPerFile.self;
             Kernel.JsonCompilationDatabase.self;
             Kernel.Files.self ]
         let name = "Files for preprocessing"
diff --git a/src/kernel_services/cmdline_parameters/parameter_builder.ml b/src/kernel_services/cmdline_parameters/parameter_builder.ml
index fc7cf42a98c..74ab79bec5a 100644
--- a/src/kernel_services/cmdline_parameters/parameter_builder.ml
+++ b/src/kernel_services/cmdline_parameters/parameter_builder.ml
@@ -1418,6 +1418,30 @@ struct
       (V)
       (struct include X let dependencies = [] end)
 
+  module Filepath_map
+      (V: Parameter_sig.Value_datatype with type key = Fc_Filepath.Normalized.t)
+      (X: sig
+         include Parameter_sig.Input_with_arg
+         val existence: Fc_Filepath.existence
+         val default: V.t Datatype.Filepath.Map.t
+       end) =
+    Make_map
+      (struct
+        include Datatype.Filepath
+        let of_string s =
+          try
+            Fc_Filepath.Normalized.of_string ~existence:X.existence s
+          with
+          | Fc_Filepath.No_file ->
+            P.L.abort "file '%s' not found" s
+          | Fc_Filepath.File_exists ->
+            P.L.abort "file '%s' already exists" s
+        let to_string = Fc_Filepath.Normalized.to_pretty_string
+        let of_singleton_string = no_element_of_string
+      end)
+      (V)
+      (struct include X let dependencies = [] end)
+
   module Kernel_function_map
       (V: Parameter_sig.Value_datatype with type key = kernel_function)
       (X: sig
diff --git a/src/kernel_services/cmdline_parameters/parameter_sig.mli b/src/kernel_services/cmdline_parameters/parameter_sig.mli
index 0f0e7ecc920..060ea584248 100644
--- a/src/kernel_services/cmdline_parameters/parameter_sig.mli
+++ b/src/kernel_services/cmdline_parameters/parameter_sig.mli
@@ -482,6 +482,9 @@ module type Multiple_map = sig
   val mem: key -> bool
 end
 
+module type Filepath_map =
+  Map with type key = Datatype.Filepath.t
+
 (* ************************************************************************** *)
 (** {2 All the different kinds of command line options as functors} *)
 (* ************************************************************************** *)
@@ -573,6 +576,19 @@ module type Builder = sig
          (** see [Filepath] module. *)
        end): Filepath_list
 
+  module Filepath_map
+      (V: Value_datatype with type key = Fc_Filepath.Normalized.t)
+      (X: sig
+         include Input_with_arg
+         val default: V.t Datatype.Filepath.Map.t
+         val existence: Fc_Filepath.existence
+         val file_kind: string
+       end):
+    Map
+    with type key = Fc_Filepath.Normalized.t
+     and type value = V.t
+     and type t = V.t Datatype.Filepath.Map.t
+
   (** Parameter is a map where multibindings are **not** allowed. *)
   module Make_map
       (K: String_datatype_with_collections)
diff --git a/src/kernel_services/plugin_entry_points/kernel.ml b/src/kernel_services/plugin_entry_points/kernel.ml
index cce382d5d4e..7ed97572321 100644
--- a/src/kernel_services/plugin_entry_points/kernel.ml
+++ b/src/kernel_services/plugin_entry_points/kernel.ml
@@ -263,6 +263,25 @@ module Filepath_list
       include X
     end)
 
+module Filepath_map
+    (X: sig
+       include Input_with_arg
+       val existence: Filepath.existence
+       val file_kind: string
+     end) =
+  P.Filepath_map
+    (struct
+      include Datatype.String
+      type key = Filepath.Normalized.t
+      let of_string ~key:_ ~prev:_ s = s
+      let to_string ~key:_ s = s
+    end)
+    (struct
+      let () = Parameter_customize.set_module_name X.module_name
+      include X
+      let default = Datatype.Filepath.Map.empty
+    end)
+
 module Kernel_function_set(X: Input_with_arg) =
   P.Kernel_function_set
     (struct
@@ -924,6 +943,21 @@ module CppExtraArgs =
                   preprocessing the C code but not while preprocessing annotations"
     end)
 
+let () = Parameter_customize.set_group parsing
+let () = Parameter_customize.do_not_reset_on_copy ()
+module CppExtraArgsPerFile =
+  Filepath_map
+    (struct
+      let module_name = "CppExtraArgsPerFile"
+      let option_name = "-cpp-extra-args-per-file"
+      let arg_name = "file:flags"
+      let existence = Filepath.Must_exist
+      let file_kind = "source"
+      let help =
+        "when set, adds preprocessing arguments for each specified file. \
+         To add arguments for all files, use -cpp-extra-args."
+    end)
+
 let () = Parameter_customize.set_group parsing
 let () = Parameter_customize.do_not_reset_on_copy ()
 module CppGnuLike =
diff --git a/src/kernel_services/plugin_entry_points/kernel.mli b/src/kernel_services/plugin_entry_points/kernel.mli
index bbfdc8097ff..8a371085fbd 100644
--- a/src/kernel_services/plugin_entry_points/kernel.mli
+++ b/src/kernel_services/plugin_entry_points/kernel.mli
@@ -378,6 +378,9 @@ module CppCommand: Parameter_sig.String
 module CppExtraArgs: Parameter_sig.String_list
 (** Behavior of option "-cpp-extra-args" *)
 
+module CppExtraArgsPerFile: Parameter_sig.Filepath_map with type value = string
+(** Behavior of option "-cpp-extra-args-per-file" *)
+
 module CppGnuLike: Parameter_sig.Bool
 (** Behavior of option "-cpp-frama-c-compliant" *)
 
diff --git a/tests/misc/cpp-extra-args-per-file1.c b/tests/misc/cpp-extra-args-per-file1.c
new file mode 100644
index 00000000000..49f13a4f541
--- /dev/null
+++ b/tests/misc/cpp-extra-args-per-file1.c
@@ -0,0 +1,20 @@
+/* run.config
+   OPT: -no-autoload-plugins -cpp-extra-args="-DGLOBAL" -cpp-extra-args-per-file @PTEST_DIR@/cpp-extra-args-per-file1.c:'-DFILE1 -DMACRO_WITH_QUOTES="\"hello world"\"',@PTEST_DIR@/cpp-extra-args-per-file2.c:"-DFILE2" -then @PTEST_DIR@/cpp-extra-args-per-file2.c
+ */
+
+#ifndef GLOBAL
+#error GLOBAL must be defined
+#endif
+
+#ifndef FILE1
+#error FILE1 must be defined
+#endif
+
+#ifdef FILE2
+#error FILE2 must NOT be defined
+#endif
+
+int main() {
+  char *a = MACRO_WITH_QUOTES;
+  return 0;
+}
diff --git a/tests/misc/cpp-extra-args-per-file2.c b/tests/misc/cpp-extra-args-per-file2.c
new file mode 100644
index 00000000000..baa99d93c02
--- /dev/null
+++ b/tests/misc/cpp-extra-args-per-file2.c
@@ -0,0 +1,15 @@
+/* run.config
+   DONTRUN: main test is cpp-extra-args-per-file1.c
+ */
+
+#ifndef GLOBAL
+#error GLOBAL must be defined
+#endif
+
+#ifndef FILE2
+#error FILE2 must be defined
+#endif
+
+#ifdef FILE1
+#error FILE1 must NOT be defined
+#endif
diff --git a/tests/misc/oracle/cpp-extra-args-per-file1.res.oracle b/tests/misc/oracle/cpp-extra-args-per-file1.res.oracle
new file mode 100644
index 00000000000..d32c7d2d184
--- /dev/null
+++ b/tests/misc/oracle/cpp-extra-args-per-file1.res.oracle
@@ -0,0 +1,2 @@
+[kernel] Parsing tests/misc/cpp-extra-args-per-file1.c (with preprocessing)
+[kernel] Parsing tests/misc/cpp-extra-args-per-file2.c (with preprocessing)
-- 
GitLab