From f43a8b4bb4daa6783e1f3c8b1c34d1a2d956e5c7 Mon Sep 17 00:00:00 2001
From: Tristan Le Gall <tristan.le-gall@cea.fr>
Date: Fri, 6 Oct 2017 14:02:07 +0200
Subject: [PATCH] [kernel] add option -json-compilation-database

---
 .gitignore                                    |   1 +
 Makefile                                      |  16 ++
 configure.in                                  |  16 ++
 doc/userman/user-changes.tex                  |   2 +
 doc/userman/user-sources.tex                  |   9 ++
 headers/header_spec.txt                       |   3 +
 man/frama-c.1                                 |  19 ++-
 share/Makefile.config.in                      |   7 +
 src/kernel_services/ast_queries/file.ml       |  16 +-
 .../json_compilation_database.ko.ml           |  29 ++++
 .../ast_queries/json_compilation_database.mli |  26 ++++
 .../json_compilation_database.ok.ml           | 141 ++++++++++++++++++
 .../plugin_entry_points/kernel.ml             |  15 ++
 .../plugin_entry_points/kernel.mli            |   3 +
 14 files changed, 296 insertions(+), 7 deletions(-)
 create mode 100644 src/kernel_services/ast_queries/json_compilation_database.ko.ml
 create mode 100644 src/kernel_services/ast_queries/json_compilation_database.mli
 create mode 100644 src/kernel_services/ast_queries/json_compilation_database.ok.ml

diff --git a/.gitignore b/.gitignore
index 48665fcf8dd..e04ec9e2d72 100644
--- a/.gitignore
+++ b/.gitignore
@@ -178,6 +178,7 @@ Makefile.plugin.generated
 /src/kernel_internals/parsing/cparser.ml
 /src/kernel_internals/parsing/cparser.mli
 /src/plugins/value/domains/apron/apron_domain.ml
+/src/kernel_services/ast_queries/json_compilation_database.ml
 
 # generated tar.gz files
 
diff --git a/Makefile b/Makefile
index a8d1211c8fe..12d9f540998 100644
--- a/Makefile
+++ b/Makefile
@@ -530,6 +530,7 @@ KERNEL_CMO=\
 	src/libraries/utils/command.cmo                                 \
 	src/libraries/utils/task.cmo                                    \
 	src/kernel_services/ast_queries/filecheck.cmo                \
+	src/kernel_services/ast_queries/json_compilation_database.cmo   \
 	src/kernel_services/ast_queries/file.cmo                     \
 	src/kernel_internals/typing/translate_lightweight.cmo         \
 	src/kernel_internals/typing/allocates.cmo                     \
@@ -581,6 +582,21 @@ GENERATED += $(addprefix src/kernel_internals/parsing/,\
 		logic_lexer.ml logic_parser.ml \
 		logic_parser.mli logic_preprocess.ml)
 
+
+ifeq ($(HAS_YOJSON),yes)
+src/kernel_services/ast_queries/json_compilation_database.ml: \
+	src/kernel_services/ast_queries/json_compilation_database.ok.ml share/Makefile.config
+	$(CP_IF_DIFF) $< $@
+	$(CHMOD_RO) $@
+else
+src/kernel_services/ast_queries/json_compilation_database.ml: \
+	src/kernel_services/ast_queries/json_compilation_database.ko.ml share/Makefile.config
+	$(CP_IF_DIFF) $< $@
+	$(CHMOD_RO) $@
+endif
+GENERATED += src/kernel_services/ast_queries/json_compilation_database.ml
+
+
 .PHONY: check-logic-parser-wildcard
 check-logic-parser-wildcard:
 	cd src/kernel_internals/parsing && ocaml check_logic_parser.ml
diff --git a/configure.in b/configure.in
index d3f05f4982a..40f7e2abc41 100644
--- a/configure.in
+++ b/configure.in
@@ -301,6 +301,21 @@ fi
 
 AC_CHECK_PROG(OTAGS,otags,otags,)
 
+# yojson
+########
+
+AC_MSG_CHECKING(for Yojson)
+
+YOJSON_PATH=$($OCAMLFIND query yojson 2>/dev/null | tr -d '\r\n')
+if test -f "$YOJSON_PATH/yojson.$OBJ_SUFFIX"; then
+  HAS_YOJSON="yes";
+  AC_MSG_RESULT(found)
+else
+  HAS_YOJSON="no";
+  AC_MSG_RESULT(yojson not found. Kernel option -json-compilation-database won't be available.)
+fi;
+
+
 # apron
 ########
 
@@ -921,6 +936,7 @@ AC_SUBST(VERBOSEMAKE)
 AC_SUBST(DEVELOPMENT)
 AC_SUBST(DOT)
 AC_SUBST(HAS_DOT)
+AC_SUBST(HAS_YOJSON)
 AC_SUBST(HAS_APRON)
 AC_SUBST(HAS_LANDMARKS)
 AC_SUBST(OCAMLBEST)
diff --git a/doc/userman/user-changes.tex b/doc/userman/user-changes.tex
index 791997648b2..b496c22ab9a 100644
--- a/doc/userman/user-changes.tex
+++ b/doc/userman/user-changes.tex
@@ -9,6 +9,8 @@ release. First we list changes of the last release.
 
 \begin{itemize}
 \item \textbf{Customizing Analyzers:} added option \texttt{-warn-special-float}.
+\item \textbf{Preparing the Sources:} added option
+  \texttt{-json-compilation-database}.
 \end{itemize}
 
 \section*{Sulfur-20171101}
diff --git a/doc/userman/user-sources.tex b/doc/userman/user-sources.tex
index c7627ca2306..a0f014ef5d2 100644
--- a/doc/userman/user-sources.tex
+++ b/doc/userman/user-sources.tex
@@ -48,6 +48,15 @@ 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"}.
 
+If you have a JSON compilation database file\footnote{%
+  \url{http://clang.llvm.org/docs/JSONCompilationDatabase.html}}, you can use
+it to retrieve \texttt{-D} and \texttt{-I} flags for each file in the database,
+via option \optiondef{-}{json-compilation-database} <path>, where <path> is
+the path to the JSON file or to a directory containing a
+\texttt{compile\_commands.json} file. With this option set, \FramaC will parse
+the compilation database and, it will include
+\texttt{-D} and \texttt{-I} flags associated to the file.
+
 By default, \acsl annotations are pre-processed (option \optiondef{-}{pp-annot}
 is set by default). Unless a custom pre-processor is specified
 (via \optionuse{-}{cpp-frama-c-compliant}), \FramaC considers that \gcc is
diff --git a/headers/header_spec.txt b/headers/header_spec.txt
index 72a19c41d0d..f40ad1dd860 100644
--- a/headers/header_spec.txt
+++ b/headers/header_spec.txt
@@ -485,6 +485,9 @@ src/kernel_services/ast_queries/file.ml: CEA_LGPL
 src/kernel_services/ast_queries/file.mli: CEA_LGPL
 src/kernel_services/ast_queries/filecheck.ml: CEA_LGPL
 src/kernel_services/ast_queries/filecheck.mli: CEA_LGPL
+src/kernel_services/ast_queries/json_compilation_database.ko.ml: CEA_LGPL_OR_PROPRIETARY
+src/kernel_services/ast_queries/json_compilation_database.mli: CEA_LGPL_OR_PROPRIETARY
+src/kernel_services/ast_queries/json_compilation_database.ok.ml: CEA_LGPL_OR_PROPRIETARY
 src/kernel_services/ast_queries/logic_const.ml: CEA_INRIA_LGPL
 src/kernel_services/ast_queries/logic_const.mli: CEA_INRIA_LGPL
 src/kernel_services/ast_queries/logic_env.ml: CEA_INRIA_LGPL
diff --git a/man/frama-c.1 b/man/frama-c.1
index 5afe72dfc40..eb35d7b51df 100644
--- a/man/frama-c.1
+++ b/man/frama-c.1
@@ -257,9 +257,9 @@ is not false, also adds
 to prevent an inconsistent mix of system and Frama-C header files.
 Defaults to yes.
 .TP
-.BI -implicit-function-declaration\  <action>
+.BI -implicit-function-declaration\  action
 warns or aborts when a function is called before it has been declared.
-\fI<action>\fP can be one of \fBignore\fP, \fBwarn\fP, or
+\fIaction\fP can be one of \fBignore\fP, \fBwarn\fP, or
 \fBerror\fP. Defaults to \fBwarn\fP.
 .TP
 .B -initialized-padding-locals
@@ -283,6 +283,19 @@ Set the name of the journal file (without the
 .I .ml
 extension). Defaults to frama_c_journal.
 .TP
+.BI -json-compilation-database\  path
+use \fIpath\fP as a JSON compilation database
+(see
+.RB < https://clang.llvm.org/docs/JSONCompilationDatabase.html >
+for more information)
+: each file preprocessed by
+Frama-C will include corresponding
+.BR -I\  and\  -D
+flags according to the specifications in \fIpath\fP.
+If \fIpath\fP is a directory, use
+.BR <path>/compile_commands.json .
+Disabled by default.
+.TP
 .B [-no]-keep-comments
 Tries to preserve comments when pretty-printing the source code (defaults to
 no).
@@ -301,7 +314,7 @@ copies log messages from the Frama-C's kernel to \fIfile\fP. \fIkind\fP
 specifies which kinds of messages to be copied (e.g. \fBw\fP for warnings,
 \fBe\fP for errors, etc.). See \fB-kernel-help\fP for more details.
 Can also be set on a per-plugin basis, with option
-.BI - plugin \-log \fP.
+.BI - <plugin> \-log \fP.
 .TP
 .B [-no]-lib-entry
 Indicates that the entry point is called during program execution. This
diff --git a/share/Makefile.config.in b/share/Makefile.config.in
index baa4ffde083..e932db64f96 100644
--- a/share/Makefile.config.in
+++ b/share/Makefile.config.in
@@ -98,6 +98,9 @@ HAS_GTKSOURCEVIEW ?=@HAS_GTKSOURCEVIEW@
 # lablgnomecanvas
 HAS_GNOMECANVAS	?=@HAS_GNOMECANVAS@
 
+# yojson
+HAS_YOJSON	?=@HAS_YOJSON@
+
 # apron
 HAS_APRON	?=@HAS_APRON@
 
@@ -158,6 +161,10 @@ ifeq ($(HAS_LANDMARKS),yes)
 LIBRARY_NAMES += landmarks landmarks.ppx
 endif
 
+ifeq ($(HAS_YOJSON),yes)
+LIBRARY_NAMES += yojson
+endif
+
 ifneq ($(ENABLE_GUI),no)
 LIBRARY_NAMES_GUI = lablgtk2 lablgtk2.gnomecanvas lablgtk2.sourceview2
 else
diff --git a/src/kernel_services/ast_queries/file.ml b/src/kernel_services/ast_queries/file.ml
index a745200cb71..fbb39c31641 100644
--- a/src/kernel_services/ast_queries/file.ml
+++ b/src/kernel_services/ast_queries/file.ml
@@ -113,9 +113,17 @@ let get_preprocessor_command () =
 
 let from_filename ?cpp f =
   let cpp, is_gnu_like =
-    match cpp with
-      | None -> get_preprocessor_command ()
-      | Some s -> s, cpp_opt_kind ()
+    let cmdline = Kernel.CppCommand.get() in
+    if cmdline <> "" then
+      cmdline, cpp_opt_kind ()
+    else
+      let 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
+      (if flags = "" then cpp else cpp ^ " " ^ flags), gnu
   in
   if Filename.check_suffix f ".i" then begin
     NoCPP f
@@ -158,6 +166,7 @@ end = struct
          let dependencies =
            [ Kernel.CppCommand.self;
              Kernel.CppExtraArgs.self;
+             Kernel.JsonCompilationDatabase.self;
              Kernel.Files.self ]
          let name = "Files for preprocessing"
        end)
@@ -648,7 +657,6 @@ let files_to_cabs_cil files =
   if Kernel.UnspecifiedAccess.get () then
     Undefined_sequence.check_sequences merged_file;
   merged_file, cabs_files
-
 (* "Implicit" annotations are those added by the kernel with ACSL name
    'Frama_C_implicit_init'. Currently, this concerns statements that are
    generated to initialize local variables. *)
diff --git a/src/kernel_services/ast_queries/json_compilation_database.ko.ml b/src/kernel_services/ast_queries/json_compilation_database.ko.ml
new file mode 100644
index 00000000000..d9d2df2f841
--- /dev/null
+++ b/src/kernel_services/ast_queries/json_compilation_database.ko.ml
@@ -0,0 +1,29 @@
+(**************************************************************************)
+(*                                                                        *)
+(*  This file is part of Frama-C.                                         *)
+(*                                                                        *)
+(*  Copyright (C) 2007-2017                                               *)
+(*    CEA (Commissariat à l'énergie atomique et aux énergies              *)
+(*         alternatives)                                                  *)
+(*                                                                        *)
+(*  you can redistribute it and/or modify it under the terms of the GNU   *)
+(*  Lesser General Public License as published by the Free Software       *)
+(*  Foundation, version 2.1.                                              *)
+(*                                                                        *)
+(*  It is distributed in the hope that it will be useful,                 *)
+(*  but WITHOUT ANY WARRANTY; without even the implied warranty of        *)
+(*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *)
+(*  GNU Lesser General Public License for more details.                   *)
+(*                                                                        *)
+(*  See the GNU Lesser General Public License version 2.1                 *)
+(*  for more details (enclosed in the file licenses/LGPLv2.1).            *)
+(*                                                                        *)
+(**************************************************************************)
+
+let get_flags _ =
+  if Kernel.JsonCompilationDatabase.get () <> "" then begin
+    Kernel.warning ~once:true "ignoring option %s: Frama-C was not compiled \
+                               with the required libraries (yojson)"
+      Kernel.JsonCompilationDatabase.option_name;
+  end;
+  ""
diff --git a/src/kernel_services/ast_queries/json_compilation_database.mli b/src/kernel_services/ast_queries/json_compilation_database.mli
new file mode 100644
index 00000000000..a919aef6fc4
--- /dev/null
+++ b/src/kernel_services/ast_queries/json_compilation_database.mli
@@ -0,0 +1,26 @@
+(**************************************************************************)
+(*                                                                        *)
+(*  This file is part of Frama-C.                                         *)
+(*                                                                        *)
+(*  Copyright (C) 2007-2017                                               *)
+(*    CEA (Commissariat à l'énergie atomique et aux énergies              *)
+(*         alternatives)                                                  *)
+(*                                                                        *)
+(*  you can redistribute it and/or modify it under the terms of the GNU   *)
+(*  Lesser General Public License as published by the Free Software       *)
+(*  Foundation, version 2.1.                                              *)
+(*                                                                        *)
+(*  It is distributed in the hope that it will be useful,                 *)
+(*  but WITHOUT ANY WARRANTY; without even the implied warranty of        *)
+(*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *)
+(*  GNU Lesser General Public License for more details.                   *)
+(*                                                                        *)
+(*  See the GNU Lesser General Public License version 2.1                 *)
+(*  for more details (enclosed in the file licenses/LGPLv2.1).            *)
+(*                                                                        *)
+(**************************************************************************)
+
+(** [get_flags f] returns the preprocessing flags associated to file [f]
+    in the JSON compilation database (when enabled), or the empty string
+    otherwise. If not empty, the flags always start with a space. *)
+val get_flags : string -> string
diff --git a/src/kernel_services/ast_queries/json_compilation_database.ok.ml b/src/kernel_services/ast_queries/json_compilation_database.ok.ml
new file mode 100644
index 00000000000..1279731b150
--- /dev/null
+++ b/src/kernel_services/ast_queries/json_compilation_database.ok.ml
@@ -0,0 +1,141 @@
+(**************************************************************************)
+(*                                                                        *)
+(*  This file is part of Frama-C.                                         *)
+(*                                                                        *)
+(*  Copyright (C) 2007-2017                                               *)
+(*    CEA (Commissariat à l'énergie atomique et aux énergies              *)
+(*         alternatives)                                                  *)
+(*                                                                        *)
+(*  you can redistribute it and/or modify it under the terms of the GNU   *)
+(*  Lesser General Public License as published by the Free Software       *)
+(*  Foundation, version 2.1.                                              *)
+(*                                                                        *)
+(*  It is distributed in the hope that it will be useful,                 *)
+(*  but WITHOUT ANY WARRANTY; without even the implied warranty of        *)
+(*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *)
+(*  GNU Lesser General Public License for more details.                   *)
+(*                                                                        *)
+(*  See the GNU Lesser General Public License version 2.1                 *)
+(*  for more details (enclosed in the file licenses/LGPLv2.1).            *)
+(*                                                                        *)
+(**************************************************************************)
+
+let dkey = Kernel.register_category "pp:compilation-db"
+
+module Flags =
+  State_builder.Hashtbl
+    (Datatype.String.Hashtbl)
+    (Datatype.String)
+    (struct
+      let name ="JsonCompilationDatabase.Flags"
+      let dependencies = []
+      let size = 32
+    end)
+
+module Init = State_builder.Bool_ref
+    (struct
+      let name ="JsonCompilationDatabase.Init"
+      let dependencies = []
+      let default () = false
+    end)
+
+let parse_file ?(cwd=Sys.getcwd()) (r : Yojson.Basic.json) =
+  let open Yojson.Basic.Util in
+  let filename = r |> member "file" |> to_string in
+  let dirname  = r |> member "directory" |> to_string in
+  let fullname = Filepath.normalize ~base_name:dirname filename in
+
+  (* get the list of arguments *)
+  let string_option_list =
+    try
+      let s = r |> member "command" |> to_string in
+      (* TODO: take into account spaces inside quote blocks *)
+      Str.split (Str.regexp "[ \t]+") s
+    with (* no command args *)
+      _ ->
+      List.map to_string (r |> member "arguments" |> to_list)
+  in
+  (* filtering the list *)
+  let (_, list_includes,list_defines) =
+    List.fold_left
+      (fun (prec,acc_includes,acc_defines) s ->
+         match prec with
+         | "-I" -> ("", s :: acc_includes, acc_defines)
+         | "-D" -> ("", acc_includes, s :: acc_defines)
+         | _ ->
+           let first_two_chars = Str.string_before s 2
+           and other_chars =Str.string_after s 2
+           in
+           begin
+             match first_two_chars with
+             | "-I" when String.length other_chars > 0 ->
+               ("", other_chars :: acc_includes, acc_defines)
+             | "-I" ->
+               ("-I", acc_includes, acc_defines)
+             | "-D" when String.length other_chars > 0 ->
+               ("", acc_includes, other_chars :: acc_defines)
+             | "-D" ->
+               ("-D", acc_includes, acc_defines)
+             | _ ->
+               ("",acc_includes,acc_defines)
+           end
+      )
+      ("", [], [])
+      string_option_list
+  in
+  (* Normalize list_includes path names: first we normalize w.r.t. to the
+     file's directory (following the compilation database logic), then we
+     relativize w.r.t. to Frama-C's PWD if possible (to avoid overly long
+     absolute paths). *)
+  let list_includes =
+    List.map (fun f ->
+        let abs = Filepath.normalize ~base_name:dirname f in
+        Filepath.relativize ~base_name:cwd abs
+      ) list_includes
+  in
+  let arg_includes =
+    if list_includes = [] then ""
+    else " -I" ^ (String.concat " -I" list_includes)
+  in
+  (* The JSON file does not include quotes for macro definitions, so we
+     add them here. It is necessary for function macros, and should not affect
+     constant definitions. *)
+  let arg_defines =
+    String.concat "" (List.map (fun d -> " -D'" ^ d ^ "'") list_defines)
+  in
+  Flags.add fullname (arg_includes ^ arg_defines)
+
+let get_flags f =
+  if Kernel.JsonCompilationDatabase.get () <> "" then begin
+    if not (Init.get ()) then begin
+      let database = Kernel.JsonCompilationDatabase.get () in
+      let path =
+        if Sys.is_directory database then
+          Filename.concat database "compile_commands.json"
+        else database
+      in
+      Kernel.feedback "using compilation database: %s" path;
+      begin
+        try
+          let r_list =
+            Yojson.Basic.from_file path |> Yojson.Basic.Util.to_list
+          in
+          List.iter parse_file r_list;
+        with
+        | Sys_error msg
+        | Yojson.Json_error msg
+        | Yojson.Basic.Util.Type_error (msg, _) ->
+          Kernel.abort "could not parse compilation database@ %s" msg
+      end;
+      Init.set true
+    end;
+    let filepath = Filepath.normalize f in
+    try
+      let flags = Flags.find filepath in
+      Kernel.feedback ~dkey "flags found for '%s': %s" filepath flags;
+      flags
+    with Not_found ->
+      Kernel.feedback ~dkey "no flags found for '%s'" filepath;
+      ""
+  end
+  else ""
diff --git a/src/kernel_services/plugin_entry_points/kernel.ml b/src/kernel_services/plugin_entry_points/kernel.ml
index a9537687505..0c3abff0dd5 100644
--- a/src/kernel_services/plugin_entry_points/kernel.ml
+++ b/src/kernel_services/plugin_entry_points/kernel.ml
@@ -855,6 +855,21 @@ module C11 =
     let module_name = "C11"
   end)
 
+let () = Parameter_customize.set_group parsing
+let () = Parameter_customize.do_not_reset_on_copy ()
+module JsonCompilationDatabase =
+  String
+    (struct
+      let module_name = "JsonCompilationDatabase"
+      let option_name = "-json-compilation-database"
+      let default = ""
+      let arg_name = "path"
+      let help = "when set, preprocessing of each file will include \
+                  corresponding -I and -D flags from the JSON compilation \
+                  database specified by <path>. If <path> is a directory, \
+                  use '<path>/compile_commands.json'. Disabled by default."
+    end)
+
 (* ************************************************************************* *)
 (** {2 Customizing Normalization} *)
 (* ************************************************************************* *)
diff --git a/src/kernel_services/plugin_entry_points/kernel.mli b/src/kernel_services/plugin_entry_points/kernel.mli
index ce6e91dbe3b..9441861c9e8 100644
--- a/src/kernel_services/plugin_entry_points/kernel.mli
+++ b/src/kernel_services/plugin_entry_points/kernel.mli
@@ -260,6 +260,9 @@ module ImplicitFunctionDeclaration: Parameter_sig.String
 module C11: Parameter_sig.Bool
   (** Behavior of option "-c11" *)
 
+module JsonCompilationDatabase: Parameter_sig.String
+  (** Behavior of option "-json-compilation-database" *)
+
 (* ************************************************************************* *)
 (** {3 Customizing cabs2cil options} *)
 (* ************************************************************************* *)
-- 
GitLab