Skip to content

Commit 15da483

Browse files
authored
Merge pull request #448 from goblint/compilation-database
Add Compilation Database support
2 parents 77048c0 + 3b602fa commit 15da483

File tree

9 files changed

+214
-67
lines changed

9 files changed

+214
-67
lines changed

src/analyses/contain.ml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ struct
7979
Hashtbl.replace htbl cn xs
8080
in (*read CXX.json; FIXME: use mangled names including namespaces*)
8181
let json=
82-
match List.filter (fun x -> Str.string_match (Str.regexp ".*CXX\\.json$") x 0) !Goblintutil.jsonFiles with
82+
match List.filter (fun x -> Filename.check_suffix x "CXX.json") !Goblintutil.arg_files with
8383
| [] -> failwith "Containment analysis needs a CXX.json file."
8484
| f :: _ ->
8585
begin
@@ -100,7 +100,7 @@ struct
100100
end
101101
in (*read in SAFE.json, suppress warnings for safe funs/vars*)
102102
json;
103-
match List.filter (fun x -> Str.string_match (Str.regexp ".*SAFE\\.json$") x 0) !Goblintutil.jsonFiles with
103+
match List.filter (fun x -> Filename.check_suffix x "SAFE.json") !Goblintutil.arg_files with
104104
| [] -> ()
105105
| f :: _ ->
106106
try

src/incremental/makefileUtil.ml

Lines changed: 34 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,6 @@ let exec_command ?path (command: string) =
2727
Sys.chdir current_dir;
2828
(exit_code, output)
2929

30-
let string_of_process_status = function
31-
| WEXITED n -> "terminated with exit code " ^ string_of_int n
32-
| WSIGNALED n -> "was killed by signal " ^ string_of_int n
33-
| WSTOPPED n -> "was stopped by signal " ^ string_of_int n
3430

3531
(* BFS for a file with a given suffix in a directory or any subdirectoy *)
3632
let find_file_by_suffix (dir: string) (file_name_suffix: string) =
@@ -47,23 +43,49 @@ let find_file_by_suffix (dir: string) (file_name_suffix: string) =
4743
in
4844
search dir (list_files dir)
4945

46+
(* Delete all *_comb.c files in the directory *)
47+
let remove_comb_files path =
48+
try
49+
while true do
50+
let comb = find_file_by_suffix path comb_suffix in
51+
if GobConfig.get_bool "dbg.verbose" then print_endline ("deleting " ^ comb);
52+
Sys.remove comb;
53+
done
54+
with Failure e -> ()
55+
5056
let run_cilly (path: string) =
5157
if Sys.file_exists path && Sys.is_directory path then (
5258
(* We need to `make clean` if `make` was run manually, otherwise it would say there is nothing to do and cilly would not be run and no combined C file would be created. *)
5359
let _ = exec_command ~path "make clean" in
54-
(try
55-
while true do
56-
let comb = find_file_by_suffix path comb_suffix in
57-
if GobConfig.get_bool "dbg.verbose" then print_endline ("deleting " ^ comb);
58-
Sys.remove comb;
59-
done
60-
with Failure e -> ()); (* Deleted all *_comb.c files in the directory *)
60+
remove_comb_files path;
6161
(* Combine source files with make using cilly as compiler *)
6262
let gcc_path = GobConfig.get_string "exp.gcc_path" in
6363
let (exit_code, output) = exec_command ~path ("make CC=\"cilly --gcc=" ^ gcc_path ^ " --merge --keepmerged\" " ^
6464
"LD=\"cilly --gcc=" ^ gcc_path ^ " --merge --keepmerged\"") in
6565
print_string output;
6666
(* fail if make failed *)
6767
if exit_code <> WEXITED 0 then
68-
failwith ("Failed combining files. Make " ^ (string_of_process_status exit_code) ^ ".")
68+
failwith ("Failed combining files. Make was " ^ (GobUnix.string_of_process_status exit_code) ^ ".")
6969
)
70+
71+
let generate_and_combine makefile =
72+
let path = Filename.dirname makefile in
73+
(* make sure the Makefile exists or try to generate it *)
74+
if not (Sys.file_exists makefile) then (
75+
print_endline ("Given " ^ makefile ^ " does not exist! Try to generate it.");
76+
let configure = ("configure", "./configure", Filename.concat path "configure") in
77+
let autogen = ("autogen", "sh autogen.sh && ./configure", Filename.concat path "autogen.sh") in
78+
let exception MakefileNotGenerated in
79+
let generate_makefile_with (name, command, file) = if Sys.file_exists file then (
80+
print_endline ("Trying to run " ^ name ^ " to generate Makefile");
81+
let exit_code, output = exec_command ~path command in
82+
print_endline (command ^ GobUnix.string_of_process_status exit_code ^ ". Output: " ^ output);
83+
if not (Sys.file_exists makefile) then raise MakefileNotGenerated
84+
); raise MakefileNotGenerated in
85+
try generate_makefile_with configure
86+
with MakefileNotGenerated ->
87+
try generate_makefile_with autogen
88+
with MakefileNotGenerated -> failwith ("Could neither find given " ^ makefile ^ " nor generate it - abort!");
89+
);
90+
run_cilly path;
91+
find_file_by_suffix path comb_suffix

src/maingoblint.ml

Lines changed: 46 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -119,18 +119,14 @@ let option_spec_list =
119119
; "--osekids" , Arg.Set_string OilUtil.osek_ids, ""
120120
]
121121

122-
(** List of C files to consider. *)
123-
let cFileNames = ref []
124122

125-
(** Parse arguments and fill [cFileNames] and [jsonFiles]. Print help if needed. *)
123+
(** Parse arguments and fill [arg_files]. Print help if needed. *)
126124
let parse_arguments () =
127-
let jsonRegex = Str.regexp ".+\\.json$" in
128-
let recordFile fname =
129-
if Str.string_match jsonRegex fname 0
130-
then Goblintutil.jsonFiles := fname :: !Goblintutil.jsonFiles
131-
else cFileNames := fname :: !cFileNames
125+
let anon_arg filename =
126+
arg_files := filename :: !arg_files
132127
in
133-
Arg.parse option_spec_list recordFile "Look up options using 'goblint --help'.";
128+
Arg.parse option_spec_list anon_arg "Look up options using 'goblint --help'.";
129+
arg_files := List.rev !arg_files; (* reverse files to get given order *)
134130
if !writeconffile <> "" then (GobConfig.write_file !writeconffile; raise Exit)
135131

136132
(** Initialize some globals in other modules. *)
@@ -151,15 +147,15 @@ let handle_flags () =
151147
set_string "outfile" ""
152148

153149
(** Use gcc to preprocess a file. Returns the path to the preprocessed file. *)
154-
let preprocess_one_file cppflags fname =
150+
let basic_preprocess ~all_cppflags fname =
155151
(* The actual filename of the preprocessed sourcefile *)
156152
let nname = Filename.concat !Goblintutil.tempDirName (Filename.basename fname) in
157153
if Sys.file_exists (get_string "tempDir") then
158154
nname
159155
else
160156
(* Preprocess using cpp. *)
161157
(* ?? what is __BLOCKS__? is it ok to just undef? this? http://en.wikipedia.org/wiki/Blocks_(C_language_extension) *)
162-
let command = Config.cpp ^ " --undef __BLOCKS__ " ^ cppflags ^ " \"" ^ fname ^ "\" -o \"" ^ nname ^ "\"" in
158+
let command = Config.cpp ^ " --undef __BLOCKS__ " ^ String.join " " (List.map Filename.quote all_cppflags) ^ " \"" ^ fname ^ "\" -o \"" ^ nname ^ "\"" in
163159
if get_bool "dbg.verbose" then print_endline command;
164160

165161
(* if something goes wrong, we need to clean up and exit *)
@@ -173,7 +169,7 @@ let preprocess_one_file cppflags fname =
173169
(** Preprocess all files. Return list of preprocessed files and the temp directory name. *)
174170
let preprocess_files () =
175171
(* Preprocessor flags *)
176-
let cppflags = ref (get_string "cppflags") in
172+
let cppflags = ref (get_string_list "cppflags") in
177173

178174
(* the base include directory *)
179175
let custom_include_dirs =
@@ -213,37 +209,6 @@ let preprocess_files () =
213209

214210
include_dirs := custom_include_dirs @ !include_dirs;
215211

216-
(* reverse the files again *)
217-
cFileNames := List.rev !cFileNames;
218-
219-
(* If the first file given is a Makefile, we use it to combine files *)
220-
if !cFileNames <> [] then (
221-
let firstFile = List.first !cFileNames in
222-
if Filename.basename firstFile = "Makefile" then (
223-
let makefile = firstFile in
224-
let path = Filename.dirname makefile in
225-
(* make sure the Makefile exists or try to generate it *)
226-
if not (Sys.file_exists makefile) then (
227-
print_endline ("Given " ^ makefile ^ " does not exist!");
228-
let configure = Filename.concat path "configure" in
229-
if Sys.file_exists configure then (
230-
print_endline ("Trying to run " ^ configure ^ " to generate Makefile");
231-
let exit_code, output = MakefileUtil.exec_command ~path "./configure" in
232-
print_endline (configure ^ MakefileUtil.string_of_process_status exit_code ^ ". Output: " ^ output);
233-
if not (Sys.file_exists makefile) then failwith ("Running " ^ configure ^ " did not generate a Makefile - abort!")
234-
) else failwith ("Could neither find given " ^ makefile ^ " nor " ^ configure ^ " - abort!")
235-
);
236-
let _ = MakefileUtil.run_cilly path in
237-
let file = MakefileUtil.(find_file_by_suffix path comb_suffix) in
238-
cFileNames := file :: (List.drop 1 !cFileNames);
239-
);
240-
);
241-
242-
cFileNames := find_custom_include "stdlib.c" :: !cFileNames;
243-
244-
if get_bool "ana.sv-comp.functions" then
245-
cFileNames := find_custom_include "sv-comp.c" :: !cFileNames;
246-
247212
(* If we analyze a kernel module, some special includes are needed. *)
248213
if get_bool "kernel" then (
249214
let kernel_roots = [
@@ -261,7 +226,7 @@ let preprocess_files () =
261226

262227
let preconf = find_custom_include "linux/goblint_preconf.h" in
263228
let autoconf = Filename.concat kernel_dir "linux/kconfig.h" in
264-
cppflags := "-D__KERNEL__ -U__i386__ -D__x86_64__ " ^ !cppflags;
229+
cppflags := "-D__KERNEL__" :: "-U__i386__" :: "-D__x86_64__" :: !cppflags;
265230
include_files := preconf :: autoconf :: !include_files;
266231
(* These are not just random permutations of directories, but based on USERINCLUDE from the
267232
* Linux kernel Makefile (in the root directory of the kernel distribution). *)
@@ -271,17 +236,48 @@ let preprocess_files () =
271236
]
272237
);
273238

274-
let includes =
275-
String.join " " (List.map (fun include_dir -> "-I " ^ include_dir) !include_dirs) ^
276-
" " ^
277-
String.join " " (List.map (fun include_file -> "-include " ^ include_file) !include_files)
239+
let include_args =
240+
List.flatten (List.map (fun include_dir -> ["-I"; include_dir]) !include_dirs) @
241+
List.flatten (List.map (fun include_file -> ["-include"; include_file]) !include_files)
278242
in
279243

280-
let cppflags = !cppflags ^ " " ^ includes in
244+
let all_cppflags = !cppflags @ include_args in
281245

282246
(* preprocess all the files *)
283247
if get_bool "dbg.verbose" then print_endline "Preprocessing files.";
284-
List.rev_map (preprocess_one_file cppflags) !cFileNames
248+
249+
let rec preprocess_arg_file = function
250+
| filename when Filename.basename filename = "Makefile" ->
251+
let comb_file = MakefileUtil.generate_and_combine filename in
252+
[basic_preprocess ~all_cppflags comb_file]
253+
254+
| filename when Filename.basename filename = CompilationDatabase.basename ->
255+
CompilationDatabase.load_and_preprocess ~all_cppflags filename
256+
257+
| filename when Sys.is_directory filename ->
258+
let dir_files = Sys.readdir filename in
259+
if Array.mem CompilationDatabase.basename dir_files then (* prefer compilation database to Makefile in case both exist, because compilation database is more robust *)
260+
preprocess_arg_file (Filename.concat filename CompilationDatabase.basename)
261+
else if Array.mem "Makefile" dir_files then
262+
preprocess_arg_file (Filename.concat filename "Makefile")
263+
else
264+
[] (* don't recurse for anything else *)
265+
266+
| filename when Filename.extension filename = ".json" ->
267+
[] (* ignore other JSON files for contain analysis *)
268+
269+
| filename ->
270+
[basic_preprocess ~all_cppflags filename]
271+
in
272+
273+
let extra_arg_files = ref [] in
274+
275+
extra_arg_files := find_custom_include "stdlib.c" :: !extra_arg_files;
276+
277+
if get_bool "ana.sv-comp.functions" then
278+
extra_arg_files := find_custom_include "sv-comp.c" :: !extra_arg_files;
279+
280+
List.flatten (List.map preprocess_arg_file (!extra_arg_files @ !arg_files))
285281

286282
(** Possibly merge all postprocessed files *)
287283
let merge_preprocessed cpp_file_names =

src/util/compilationDatabase.ml

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
open Prelude
2+
3+
let basename = "compile_commands.json"
4+
5+
type command_object = {
6+
directory: string;
7+
file: string;
8+
command: string option [@default None];
9+
arguments: string list option [@default None];
10+
output: string option [@default None];
11+
} [@@deriving yojson]
12+
13+
type t = command_object list [@@deriving yojson]
14+
15+
let parse_file filename =
16+
Result.get_ok (of_yojson (Yojson.Safe.from_file filename))
17+
18+
let command_o_regexp = Str.regexp "-o +[^ ]+"
19+
let command_program_regexp = Str.regexp "^ *\\([^ ]+\\)"
20+
21+
let system ~cwd command =
22+
let old_cwd = Sys.getcwd () in
23+
Fun.protect ~finally:(fun () ->
24+
Sys.chdir old_cwd
25+
) (fun () ->
26+
Sys.chdir cwd;
27+
match Unix.system command with
28+
| WEXITED 0 -> ()
29+
| process_status -> failwith (GobUnix.string_of_process_status process_status)
30+
)
31+
32+
let load_and_preprocess ~all_cppflags filename =
33+
let database_dir = Filename.dirname (GobFilename.absolute filename) in (* absolute before dirname to avoid . *)
34+
(* TODO: generalize .goblint for everything *)
35+
ignore (Goblintutil.create_dir ".goblint");
36+
let preprocessed_dir = Goblintutil.create_dir (Filename.concat ".goblint" "preprocessed") in
37+
let preprocess obj =
38+
let file = obj.file in
39+
let preprocessed_file = Filename.concat preprocessed_dir (Filename.chop_extension (GobFilename.chop_common_prefix database_dir file) ^ ".i") in
40+
GobSys.mkdir_parents preprocessed_file;
41+
let preprocess_command = match obj.command, obj.arguments with
42+
| Some command, None ->
43+
(* TODO: extract o_file *)
44+
let preprocess_command = Str.replace_first command_program_regexp ("\\1 " ^ String.join " " (List.map Filename.quote all_cppflags) ^ " -E") command in
45+
let preprocess_command = Str.replace_first command_o_regexp ("-o " ^ preprocessed_file) preprocess_command in
46+
if preprocess_command = command then (* easier way to check if match was found (and replaced) *)
47+
failwith "CompilationDatabase.preprocess: no -o argument found for " ^ file
48+
else
49+
preprocess_command
50+
| None, Some arguments ->
51+
begin match List.findi (fun i e -> e = "-o") arguments with
52+
| (o_i, _) ->
53+
begin match List.split_at o_i arguments with
54+
| (arguments_program :: arguments_init, _ :: o_file :: arguments_tl) ->
55+
let preprocess_arguments = arguments_program :: all_cppflags @ "-E" :: arguments_init @ "-o" :: preprocessed_file :: arguments_tl in
56+
String.join " " (List.map Filename.quote preprocess_arguments) (* TODO: use quote_command after OCaml 4.10 *)
57+
| _ ->
58+
failwith "CompilationDatabase.preprocess: no -o argument value found for " ^ file
59+
end
60+
| exception Not_found ->
61+
failwith "CompilationDatabase.preprocess: no -o argument found for " ^ file
62+
end
63+
| Some _, Some _ ->
64+
failwith "CompilationDatabase.preprocess: both command and arguments specified for " ^ file
65+
| None, None ->
66+
failwith "CompilationDatabase.preprocess: neither command nor arguments specified for " ^ file
67+
in
68+
if GobConfig.get_bool "dbg.verbose" then
69+
Printf.printf "Preprocessing %s\n to %s\n using %s\n in %s\n" file preprocessed_file preprocess_command obj.directory;
70+
system ~cwd:obj.directory preprocess_command; (* command/arguments might have paths relative to directory *)
71+
preprocessed_file
72+
in
73+
parse_file filename
74+
|> List.map preprocess

src/util/defaults.ml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ let _ = ()
7373
; reg Std "allglobs" "false" "Prints access information about all globals, not just races."
7474
; reg Std "keepcpp" "false" "Keep the intermediate output of running the C preprocessor."
7575
; reg Std "tempDir" "''" "Reuse temporary directory for preprocessed files."
76-
; reg Std "cppflags" "''" "Pre-processing parameters."
76+
; reg Std "cppflags" "[]" "Pre-processing parameters."
7777
; reg Std "kernel" "false" "For analyzing Linux Device Drivers."
7878
; reg Std "dump_globs" "false" "Print out the global invariant."
7979
; reg Std "result" "'none'" "Result style: none, fast_xml, json, mongo, pretty, json-messages."

src/util/gobFilename.ml

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
open Prelude
2+
3+
(** Make path absolute, if relative. *)
4+
let absolute filename =
5+
if Filename.is_relative filename then
6+
Filename.concat (Unix.getcwd ()) filename
7+
else
8+
filename
9+
10+
let chop_prefix ~prefix filename =
11+
let prefix =
12+
if String.ends_with prefix Filename.dir_sep then
13+
prefix
14+
else
15+
prefix ^ Filename.dir_sep
16+
in
17+
if String.starts_with filename prefix then
18+
String.lchop ~n:(String.length prefix) filename
19+
else
20+
raise (Invalid_argument "GobFilename.chop_prefix: doesn't start with given prefix")
21+
22+
let common_prefix filename1 filename2 =
23+
let common_prefix_length s1 s2 =
24+
let l1 = String.length s1 in
25+
let l2 = String.length s2 in
26+
let rec common_prefix_length' i =
27+
if i >= l1 || i >= l2 then
28+
i
29+
else if s1.[i] = s2.[i] then
30+
common_prefix_length' (i + 1)
31+
else
32+
i
33+
in
34+
common_prefix_length' 0
35+
in
36+
String.left filename1 (common_prefix_length filename1 filename2)
37+
38+
let chop_common_prefix filename1 filename2 =
39+
let filename2 = absolute filename2 in
40+
chop_prefix ~prefix:(common_prefix (absolute filename1) filename2) filename2

src/util/gobSys.ml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
open Prelude
2+
3+
let rec mkdir_parents filename =
4+
let dirname = Filename.dirname filename in
5+
if not (Sys.file_exists dirname) then (
6+
mkdir_parents dirname;
7+
Unix.mkdir dirname 0o770; (* TODO: what permissions? *)
8+
)

src/util/gobUnix.ml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
open Prelude
2+
open Unix
3+
4+
let string_of_process_status = function
5+
| WEXITED n -> "terminated with exit code " ^ string_of_int n
6+
| WSIGNALED n -> "killed by signal " ^ string_of_int n
7+
| WSTOPPED n -> "stopped by signal " ^ string_of_int n

src/util/goblintutil.ml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ open GobConfig
77
(** Outputs information about what the goblin is doing *)
88
(* let verbose = ref false *)
99

10-
(** Json files that are given as arguments *)
11-
let jsonFiles : string list ref = ref []
10+
(** Files given as arguments. *)
11+
let arg_files : string list ref = ref []
1212

1313
(** If this is true we output messages and collect accesses.
1414
This is set to true in control.ml before we verify the result (or already before solving if warn = 'early') *)

0 commit comments

Comments
 (0)