Release of coccinelle 1.0.0-rc9
[bpt/coccinelle.git] / parsing_c / cpp_ast_c.ml
index df0718b..d2e4698 100644 (file)
@@ -1,46 +1,71 @@
+(* Yoann Padioleau
+ *
+ * Copyright (C) 2010, University of Copenhagen DIKU and INRIA.
+ * Copyright (C) 2008, 2009 University of Urbana Champaign
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License (GPL)
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program 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
+ * file license.txt for more details.
+ *)
 open Common
 
 open Ast_c
 
+(*****************************************************************************)
+(* Wrappers *)
+(*****************************************************************************)
+let pr2, pr2_once =
+  Common.mk_pr2_wrappers Flag_parsing_c.verbose_cpp_ast
+let pr2_debug,pr2_debug_once =
+  Common.mk_pr2_wrappers Flag_parsing_c.debug_cpp_ast
+
 (*****************************************************************************)
 (* Cpp Ast Manipulations *)
 (*****************************************************************************)
 
 (*
  * cpp-include-expander-builtin.
- * 
- * alternative1: parse and call cpp tour a tour ?
+ *
+ * alternative1: parse and call cpp tour a tour. So let cpp work at
+ *  the token level. That's what most tools do.
  * alternative2: apply cpp at the very end. Process that go through ast
- * and do the stuff such as #include,  macro expand, 
- * ifdef. 
- * 
+ *  and do the stuff such as #include,  macro expand,
+ *  ifdef but on the ast!
+ *
  * But need keep those info in ast at least, even bad
- * macro for instance, and for parse error region ? maybe can 
+ * macro for instance, and for parse error region ? maybe can
  * get another chance ?
  * I think it's better to do the cpp-include-expander in a different step
- * rather than embedding it in the parser. The parser is already too complex. 
+ * rather than embedding it in the parser. The parser is already too complex.
  * Also keep with the tradition to try to parse as-is.
- * 
+ *
  * todo? but maybe could discover new info that could help reparse
  * the ParseError in original file. Try again parsing it by
- * putting it in a minifile ? 
- * 
- * 
+ * putting it in a minifile ?
+ *
+ *
  * todo? maybe can do some pass that work at the ifdef level and for instance
  * try to paren them, so have in Ast some stuff that are not
  * present at parsing time but that can then be constructed after
  * some processing (a little bit like my type for expression filler,
  * or position info filler, or include relative position filler).
- * 
+ *
  * ??add such info about what was done somewhere ? could build new
  * ??ast each time but too tedious (maybe need delta-programming!)
  *
- * 
- * TODO: macro expand, 
+ * todo? maybe change cpp_ast_c to go deeper on local "" ?
+ *
+ *
+ * TODO: macro expand,
  * TODO: handle ifdef
- * 
- * 
- * 
+ *
+ *
+ *
  * cpp_ifdef_statementize: again better to separate concern and in parser
  *  just add the directives in a flat way (IfdefStmt) and later do more
  *  processing and transform them in a tree with some IfdefStmt2.
@@ -52,61 +77,154 @@ open Ast_c
 (* Types  *)
 (*****************************************************************************)
 
-type cpp_option = 
-  | I of Common.filename
+type cpp_option =
+  | I of Common.dirname
   | D of string * string option
 
 
 
-let i_of_cpp_options xs = 
+let i_of_cpp_options xs =
   xs +> Common.map_filter (function
   | I f -> Some f
   | D _ -> None
   )
 
-let cpp_option_of_cmdline (xs, ys) = 
+let cpp_option_of_cmdline (xs, ys) =
   (xs +> List.map (fun s -> I s)) ++
-  (ys +> List.map (fun s -> 
+  (ys +> List.map (fun s ->
     if s =~ "\\([A-Z][A-Z0-9_]*\\)=\\(.*\\)"
     then
       let (def, value) = matched2 s in
       D (def, Some value)
-    else 
+    else
       D (s, None)
   ))
 
+(*****************************************************************************)
+(* Debug *)
+(*****************************************************************************)
+let (show_cpp_i_opts: string list -> unit) = fun xs ->
+  if not (null xs) then begin
+    pr2 "-I";
+    xs +> List.iter pr2
+  end
+
+
+let (show_cpp_d_opts: string list -> unit) = fun xs ->
+  if not (null xs) then begin
+    pr2 "-D";
+    xs +> List.iter pr2
+  end
+
+(* ---------------------------------------------------------------------- *)
+let trace_cpp_process depth mark inc_file =
+  pr2_debug (spf "%s>%s %s"
+          (Common.repeat "-" depth +> Common.join "")
+          mark
+          (s_of_inc_file_bis inc_file));
+  ()
+
+
+
 (*****************************************************************************)
 (* Helpers *)
 (*****************************************************************************)
 
+
+let _hcandidates = Hashtbl.create 101
+
+let init_adjust_candidate_header_files dir =
+  let ext = "[h]" in
+  let files = Common.files_of_dir_or_files ext [dir] in
+
+  files +> List.iter (fun file ->
+    let base = Filename.basename file in
+    pr2_debug file;
+    Hashtbl.add _hcandidates base file;
+  );
+  ()
+
+
+
 (* may return a list of match ? *)
-let find_header_file cppopts dirname inc_file =
+let find_header_file1 cppopts dirname inc_file =
   match inc_file with
-  | Local f -> 
-      let finalfile = 
+  | Local f ->
+      let finalfile =
         Filename.concat dirname (Ast_c.s_of_inc_file inc_file) in
-      if Sys.file_exists finalfile 
+      if Sys.file_exists finalfile
       then [finalfile]
       else []
-  | NonLocal f -> 
-      i_of_cpp_options cppopts +> Common.map_filter (fun dirname -> 
-        let finalfile = 
+  | NonLocal f ->
+      i_of_cpp_options cppopts +> Common.map_filter (fun dirname ->
+        let finalfile =
           Filename.concat dirname (Ast_c.s_of_inc_file inc_file) in
-        if Sys.file_exists finalfile 
+        if Sys.file_exists finalfile
         then Some finalfile
         else None
       )
-  | Wierd s -> 
-      pr2 ("CPPAST: wierd include not handled:" ^ s);
+  | Weird s ->
+      pr2 ("CPPAST: weird include not handled:" ^ s);
       []
 
+(* todo? can try find most precise ? first just use basename but
+ * then maybe look if have also some dir in common ?
+ *)
+let find_header_file2 inc_file =
+  match inc_file with
+  | Local f
+  | NonLocal f ->
+      let s = (Ast_c.s_of_inc_file inc_file) in
+      let base = Filename.basename s in
+
+      let res = Hashtbl.find_all _hcandidates base in
+      (match res with
+      | [file] ->
+          pr2_debug ("CPPAST: find header in other dir: " ^ file);
+          res
+      | [] ->
+          []
+      | x::y::xs -> res
+      )
+  | Weird s ->
+      []
+
+
+let find_header_file cppopts dirname inc_file =
+  let res1 = find_header_file1 cppopts dirname inc_file in
+  match res1 with
+  | [file] -> res1
+  | [] -> find_header_file2 inc_file
+  | x::y::xs -> res1
+
+
+
+
+(* ---------------------------------------------------------------------- *)
+let _headers_hash = Hashtbl.create 101
+
+(* On freebsd ocaml is trashing, use up to 1.6Go of memory and then
+ * building the database_c takes ages.
+ *
+ * So just limit with following threshold to avoid this trashing, simple.
+ *
+ * On netbsd, got a Out_of_memory exn on this file;
+ * /home/pad/software-os-src2/netbsd/dev/microcode/cyclades-z/
+ * even if the cache is small. That's because huge single
+ * ast element and probably the ast marshalling fail.
+ *)
+let default_threshold_cache_nb_files = 200
+
+let parse_c_and_cpp_cache
+  ?(threshold_cache_nb_files= default_threshold_cache_nb_files) file =
+
+  if Hashtbl.length _headers_hash > threshold_cache_nb_files
+  then Hashtbl.clear _headers_hash;
+
+  Common.memoized _headers_hash file (fun () ->
+    Parse_c.parse_c_and_cpp file
+  )
 
-let trace_cpp_process depth mark inc_file =
-  pr2 (spf "%s>%s %s" 
-          (Common.repeat "-" depth +> Common.join "")
-          mark
-          (s_of_inc_file_bis inc_file));
-  ()
 
 
 (*****************************************************************************)
@@ -114,29 +232,37 @@ let trace_cpp_process depth mark inc_file =
 (*****************************************************************************)
 
 
-let (cpp_expand_include: 
+let (cpp_expand_include2:
+ ?depth_limit:int option ->
+ ?threshold_cache_nb_files:int ->
  cpp_option list -> Common.dirname -> Ast_c.program -> Ast_c.program) =
- fun iops dirname ast -> 
+ fun ?(depth_limit=None) ?threshold_cache_nb_files iops dirname ast ->
+
+  if !Flag_parsing_c.debug_cpp_ast
+  then pr2_xxxxxxxxxxxxxxxxx();
 
-  pr2_xxxxxxxxxxxxxxxxx();
   let already_included = ref [] in
 
-  let rec aux stack dirname ast = 
+  let rec aux stack dirname ast =
     let depth = List.length stack in
 
     ast +> Visitor_c.vk_program_s { Visitor_c.default_visitor_c_s with
-      Visitor_c.kcppdirective_s = (fun (k, bigf) cpp -> 
-        match cpp with 
+      Visitor_c.kcppdirective_s = (fun (k, bigf) cpp ->
+        match cpp with
         | Include {i_include = (inc_file, ii);
                    i_rel_pos = h_rel_pos;
                    i_is_in_ifdef = b;
                    i_content = copt;
-                   } 
-          -> 
+                   }
+          ->
+          (match depth_limit with
+          | Some limit when depth >= limit -> cpp
+          | _ ->
+
             (match find_header_file iops dirname inc_file with
-            | [file] -> 
+            | [file] ->
                 if List.mem file !already_included
-                then begin 
+                then begin
                   (* pr2 ("already included: " ^ file); *)
                   trace_cpp_process depth "*" inc_file;
                   k cpp
@@ -144,12 +270,14 @@ let (cpp_expand_include:
                   trace_cpp_process depth "" inc_file;
                   Common.push2 file already_included;
                   (* CONFIG *)
-                  Flag_parsing_c.verbose_parsing := false; 
-                  Flag_parsing_c.verbose_lexing := false; 
-                  let (ast2, _stat) = Parse_c.parse_c_and_cpp file in
+                  Flag_parsing_c.verbose_parsing := false;
+                  Flag_parsing_c.verbose_lexing := false;
+                  let (ast2, _stat) =
+                    parse_c_and_cpp_cache ?threshold_cache_nb_files file
+                  in
 
                   let ast = Parse_c.program_of_program2 ast2 in
-                  let dirname' = Filename.dirname file in 
+                  let dirname' = Filename.dirname file in
 
                   (* recurse *)
                   let ast' = aux (file::stack) dirname' ast in
@@ -160,24 +288,28 @@ let (cpp_expand_include:
                            i_content = Some (file, ast');
                   }
                 end
-            | [] -> 
+            | [] ->
                 trace_cpp_process depth "!!" inc_file;
                 pr2 "CPPAST: file not found";
                 k cpp
-            | x::y::zs -> 
+            | x::y::zs ->
                 trace_cpp_process depth "!!" inc_file;
                 pr2 "CPPAST: too much candidates";
                 k cpp
             )
+          )
         | _ -> k cpp
       );
     }
   in
   aux [] dirname ast
-    
 
 
-(* 
+let cpp_expand_include ?depth_limit ?threshold_cache_nb_files a b c =
+  Common.profile_code "cpp_expand_include"
+   (fun () -> cpp_expand_include2 ?depth_limit ?threshold_cache_nb_files a b c)
+
+(*
 let unparse_showing_include_content ?
 *)
 
@@ -187,10 +319,10 @@ let unparse_showing_include_content ?
 (*****************************************************************************)
 
 
-let is_ifdef_and_same_tag tag x = 
+let is_ifdef_and_same_tag tag x =
   match x with
-  | IfdefStmt (IfdefDirective ((_, tag2),_)) -> 
-      tag = tag2
+  | IfdefStmt (IfdefDirective ((_, tag2),_)) ->
+      tag =*= tag2
   | StmtElem _ | CppDirectiveStmt _ -> false
   | IfdefStmt2 _ -> raise Impossible
 
@@ -205,14 +337,16 @@ let is_ifdef_and_same_tag tag x =
  * indice. Or simply count  the number of directives with the same tag and
  * put this information in the tag. Hence the total_with_this_tag below.
  *)
-let should_ifdefize tag ifdefs_directives xxs = 
+let should_ifdefize (tag,ii) ifdefs_directives xxs =
   let IfdefTag (_tag, total_with_this_tag) = tag in
-  
+
   if total_with_this_tag <> List.length ifdefs_directives
   then begin
-    pr2 "CPPASTC: can not ifdefize, some of its directives were passed";
-    false 
-  end else 
+    let strloc = Ast_c.strloc_of_info (List.hd ii) in
+    pr2 (spf "CPPASTC: can not ifdefize ifdef at %s" strloc);
+    pr2 "CPPASTC: some of its directives were passed";
+    false
+  end else
     (* todo? put more condition ? dont ifdefize declaration ? *)
     true
 
@@ -220,58 +354,68 @@ let should_ifdefize tag ifdefs_directives xxs =
 
 
 
-(* return a triple, (ifdefs directive * grouped xs * remaining sequencable) 
- * XXX1 XXX2 elsif YYY1 else ZZZ1 endif WWW1 WWW2 
+(* return a triple, (ifdefs directive * grouped xs * remaining sequencable)
+ * XXX1 XXX2 elsif YYY1 else ZZZ1 endif WWW1 WWW2
  * => [elsif, else, endif], [XXX1 XXX2; YYY1; ZZZ1], [WWW1 WWW2]
  *)
-let group_ifdef tag xs = 
+let group_ifdef tag xs =
   let (xxs, xs) = group_by_post (is_ifdef_and_same_tag tag) xs in
-  
-  xxs +> List.map snd +> List.map (fun x -> 
-    match x with 
+
+  xxs +> List.map snd +> List.map (fun x ->
+    match x with
     | IfdefStmt y -> y
     | StmtElem _ | CppDirectiveStmt _ | IfdefStmt2 _ -> raise Impossible
   ),
-  xxs +> List.map fst, 
+  xxs +> List.map fst,
   xs
 
 
-let rec cpp_ifdef_statementize ast = 
+let rec cpp_ifdef_statementize ast =
   Visitor_c.vk_program_s { Visitor_c.default_visitor_c_s with
-    Visitor_c.kstatementseq_list_s = (fun (k, bigf) xs -> 
-      
-      let rec aux xs = 
+    Visitor_c.kstatementseq_list_s = (fun (k, bigf) xs ->
+      let rec aux xs =
         match xs with
         | [] -> []
-        | stseq::xs -> 
+        | stseq::xs ->
             (match stseq with
-            | StmtElem st -> 
+            | StmtElem st ->
                 Visitor_c.vk_statement_sequencable_s bigf stseq::aux xs
-            | CppDirectiveStmt directive -> 
+            | CppDirectiveStmt directive ->
                 Visitor_c.vk_statement_sequencable_s bigf stseq::aux xs
-            | IfdefStmt ifdef -> 
+            | IfdefStmt ifdef ->
                 (match ifdef with
-                | IfdefDirective ((Ifdef,tag),ii) -> 
+                | IfdefDirective ((Ifdef,tag),ii) ->
 
                     let (restifdefs, xxs, xs') = group_ifdef tag xs in
-                    if should_ifdefize tag (ifdef::restifdefs) xxs 
+                    if should_ifdefize (tag,ii) (ifdef::restifdefs) xxs
                     then
                       let res = IfdefStmt2 (ifdef::restifdefs, xxs) in
                       Visitor_c.vk_statement_sequencable_s bigf res::aux xs'
-                    else 
+                    else
                       Visitor_c.vk_statement_sequencable_s bigf stseq::aux xs
-                      
-                | IfdefDirective (((IfdefElseif|IfdefElse|IfdefEndif),b),ii) -> 
-                    pr2 "wierd: first directive is not a ifdef";
-                    (* maybe not wierd, just that should_ifdefize 
+
+                | IfdefDirective (((IfdefElseif|IfdefElse|IfdefEndif),b),ii) ->
+                    pr2 "weird: first directive is not a ifdef";
+                    (* maybe not weird, just that should_ifdefize
                      * returned false *)
                     Visitor_c.vk_statement_sequencable_s bigf stseq::aux xs
                 )
 
-            | IfdefStmt2 (ifdef, xxs) -> 
+            | IfdefStmt2 (ifdef, xxs) ->
                 failwith "already applied cpp_ifdef_statementize"
             )
       in
       aux xs
     );
   } ast
+
+
+(*****************************************************************************)
+(* Macro *)
+(*****************************************************************************)
+
+let (cpp_expand_macro_expr:
+  Ast_c.define_kind -> Ast_c.argument Ast_c.wrap2 list ->
+  Ast_c.expression option) =
+ fun defkind args ->
+   raise Todo