506 lines
17 KiB
OCaml
506 lines
17 KiB
OCaml
(* Yoann Padioleau
|
|
*
|
|
* Copyright (C) 2002-2013 Yoann Padioleau
|
|
*
|
|
* 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
|
|
|
|
module Ast = Ast_cpp
|
|
module Flag = Flag_parsing_cpp
|
|
module PI = Parse_info
|
|
module Stat = Parse_info
|
|
module T = Parser_cpp
|
|
module TH = Token_helpers_cpp
|
|
module Lexer = Lexer_cpp
|
|
module Semantic = Parser_cpp_mly_helper
|
|
module Hack = Parsing_hacks_lib
|
|
module FT = File_type
|
|
|
|
(*****************************************************************************)
|
|
(* Prelude *)
|
|
(*****************************************************************************)
|
|
(*
|
|
* A heuristic based C/cpp/C++ parser.
|
|
*
|
|
* See "Parsing C/C++ Code without Pre-Preprocessing - Yoann Padioleau, CC'09"
|
|
* avalaible at http://padator.org/papers/yacfe-cc09.pdf
|
|
*)
|
|
|
|
(*****************************************************************************)
|
|
(* Types *)
|
|
(*****************************************************************************)
|
|
|
|
type toplevels_and_tokens = (Ast.toplevel * Parser_cpp.token list) list
|
|
|
|
let program_of_program2 xs =
|
|
xs +> List.map fst
|
|
|
|
exception Parse_error of Parse_info.info
|
|
|
|
(*****************************************************************************)
|
|
(* Wrappers *)
|
|
(*****************************************************************************)
|
|
let pr2, _pr2_once = Common2.mk_pr2_wrappers Flag_parsing_cpp.verbose_parsing
|
|
|
|
(*****************************************************************************)
|
|
(* Error diagnostic *)
|
|
(*****************************************************************************)
|
|
|
|
let error_msg_tok tok =
|
|
Parse_info.error_message_info (TH.info_of_tok tok)
|
|
|
|
(*****************************************************************************)
|
|
(* Stats on what was passed/commentized *)
|
|
(*****************************************************************************)
|
|
|
|
let commentized xs = xs +> Common.map_filter (function
|
|
| T.TComment_Pp (cppkind, ii) ->
|
|
if !Flag.filter_classic_passed
|
|
then
|
|
(match cppkind with
|
|
| Token_cpp.CppOther ->
|
|
let s = PI.str_of_info ii in
|
|
(match s with
|
|
| s when s =~ "KERN_.*" -> None
|
|
| s when s =~ "__.*" -> None
|
|
| _ -> Some (ii.PI.token)
|
|
)
|
|
|
|
| Token_cpp.CppDirective | Token_cpp.CppAttr | Token_cpp.CppMacro
|
|
-> None
|
|
| Token_cpp.CppMacroExpanded
|
|
| Token_cpp.CppPassingNormal
|
|
| Token_cpp.CppPassingCosWouldGetError
|
|
-> raise Todo
|
|
)
|
|
else Some (ii.PI.token)
|
|
|
|
| T.TAny_Action ii ->
|
|
Some (ii.PI.token)
|
|
| _ ->
|
|
None
|
|
)
|
|
|
|
let count_lines_commentized xs =
|
|
let line = ref (-1) in
|
|
let count = ref 0 in
|
|
commentized xs +> List.iter (function
|
|
| PI.OriginTok pinfo
|
|
| PI.ExpandedTok (_,pinfo,_) ->
|
|
let newline = pinfo.PI.line in
|
|
if newline <> !line
|
|
then begin
|
|
line := newline;
|
|
incr count
|
|
end
|
|
| _ -> ()
|
|
);
|
|
!count
|
|
|
|
|
|
(* See also problematic_lines and parsing_stat.ml *)
|
|
|
|
(* for most problematic tokens *)
|
|
let is_same_line_or_close line tok =
|
|
TH.line_of_tok tok =|= line ||
|
|
TH.line_of_tok tok =|= line - 1 ||
|
|
TH.line_of_tok tok =|= line - 2
|
|
|
|
(*****************************************************************************)
|
|
(* Lexing only *)
|
|
(*****************************************************************************)
|
|
|
|
(* called by parse below *)
|
|
let tokens2 file =
|
|
let table = Parse_info.full_charpos_to_pos_large file in
|
|
|
|
Common.with_open_infile file (fun chan ->
|
|
let lexbuf = Lexing.from_channel chan in
|
|
try
|
|
let rec tokens_aux () =
|
|
let tok = Lexer.token lexbuf in
|
|
(* fill in the line and col information *)
|
|
let tok = tok +> TH.visitor_info_of_tok (fun ii ->
|
|
{ ii with PI.token=
|
|
(* could assert pinfo.filename = file ? *)
|
|
match ii.PI.token with
|
|
| PI.OriginTok pi ->
|
|
PI.OriginTok (Parse_info.complete_token_location_large file
|
|
table pi)
|
|
| PI.ExpandedTok (pi,vpi, off) ->
|
|
PI.ExpandedTok(
|
|
(Parse_info.complete_token_location_large file table pi),vpi,
|
|
off)
|
|
| PI.FakeTokStr (s,vpi_opt) -> PI.FakeTokStr (s,vpi_opt)
|
|
| PI.Ab -> raise Impossible
|
|
})
|
|
in
|
|
|
|
if TH.is_eof tok
|
|
then [tok]
|
|
else tok::(tokens_aux ())
|
|
in
|
|
tokens_aux ()
|
|
with
|
|
| Lexer.Lexical s ->
|
|
failwith (spf "lexical error %s \n = %s"
|
|
s (PI.error_message file (PI.lexbuf_to_strpos lexbuf)))
|
|
| e -> raise e
|
|
)
|
|
|
|
let tokens a =
|
|
Common.profile_code "Parse_cpp.tokens" (fun () -> tokens2 a)
|
|
|
|
(*****************************************************************************)
|
|
(* Fuzzy parsing *)
|
|
(*****************************************************************************)
|
|
|
|
let rec multi_grouped_list xs =
|
|
xs +> List.map multi_grouped
|
|
|
|
and multi_grouped = function
|
|
| Token_views_cpp.Braces (tok1, xs, (Some tok2)) ->
|
|
Ast_fuzzy.Braces (tokext tok1, multi_grouped_list xs, tokext tok2)
|
|
| Token_views_cpp.Parens (tok1, xs, (Some tok2)) ->
|
|
Ast_fuzzy.Parens (tokext tok1, multi_grouped_list_comma xs, tokext tok2)
|
|
| Token_views_cpp.Angle (tok1, xs, (Some tok2)) ->
|
|
Ast_fuzzy.Angle (tokext tok1, multi_grouped_list xs, tokext tok2)
|
|
| Token_views_cpp.Tok (tok) ->
|
|
(match PI.str_of_info (tokext tok) with
|
|
| "..." -> Ast_fuzzy.Dots (tokext tok)
|
|
| s when Ast_fuzzy.is_metavar s -> Ast_fuzzy.Metavar (s, tokext tok)
|
|
| s -> Ast_fuzzy.Tok (s, tokext tok)
|
|
)
|
|
| _ -> failwith "could not find closing brace/parens/angle"
|
|
and tokext tok_extended =
|
|
TH.info_of_tok tok_extended.Token_views_cpp.t
|
|
and multi_grouped_list_comma xs =
|
|
let rec aux acc xs =
|
|
match xs with
|
|
| [] ->
|
|
if null acc
|
|
then []
|
|
else [Left (acc +> List.rev +> multi_grouped_list)]
|
|
| (x::xs) ->
|
|
(match x with
|
|
| Token_views_cpp.Tok tok when PI.str_of_info (tokext tok) = "," ->
|
|
let before = acc +> List.rev +> multi_grouped_list in
|
|
if null before
|
|
then aux [] xs
|
|
else (Left before)::(Right (tokext tok))::aux [] xs
|
|
| _ ->
|
|
aux (x::acc) xs
|
|
)
|
|
in
|
|
aux [] xs
|
|
|
|
|
|
(* This is similar to what I did for OPA. This is also similar
|
|
* to what I do for parsing hacks, but this fuzzy AST can be useful
|
|
* on its own, e.g. for a not too bad sgrep/spatch.
|
|
*
|
|
* note: this is similar to what cpplint/fblint of andrei does?
|
|
*)
|
|
let parse_fuzzy file =
|
|
Common.save_excursion Flag.sgrep_mode true (fun () ->
|
|
let toks_orig = tokens file in
|
|
let toks =
|
|
toks_orig +> Common.exclude (fun x ->
|
|
Token_helpers_cpp.is_comment x || Token_helpers_cpp.is_eof x
|
|
)
|
|
in
|
|
let extended = toks +> List.map Token_views_cpp.mk_token_extended in
|
|
Parsing_hacks_cpp.find_template_inf_sup extended;
|
|
let groups = Token_views_cpp.mk_multi extended in
|
|
multi_grouped_list groups, toks_orig
|
|
)
|
|
|
|
(*****************************************************************************)
|
|
(* Extract macros *)
|
|
(*****************************************************************************)
|
|
|
|
(* It can be used to to parse the macros defined in a macro.h file. It
|
|
* can also be used to try to extract the macros defined in the file
|
|
* that we try to parse *)
|
|
let extract_macros2 file =
|
|
Common.save_excursion Flag_parsing_cpp.verbose_lexing false (fun () ->
|
|
let toks = tokens (* todo: ~profile:false *) file in
|
|
let toks = Parsing_hacks_define.fix_tokens_define toks in
|
|
Pp_token.extract_macros toks
|
|
)
|
|
let extract_macros a =
|
|
Common.profile_code_exclusif "Parse_cpp.extract_macros" (fun () ->
|
|
extract_macros2 a)
|
|
|
|
(* less: pass it as a parameter to parse_program instead ?
|
|
* old: was a ref, but a hashtbl.t is actually already a kind of ref
|
|
*)
|
|
let (_defs : (string, Pp_token.define_body) Hashtbl.t) =
|
|
Hashtbl.create 101
|
|
|
|
|
|
(* We used to have also a init_defs_builtins() so that we could use a
|
|
* standard.h containing macros that were always useful, and a macros.h
|
|
* that the user could customize for his own project.
|
|
* But this was adding complexity so now we just have _defs and people
|
|
* can call add_defs to add local macro definitions.
|
|
*)
|
|
let add_defs file =
|
|
if not (Sys.file_exists file)
|
|
then failwith (spf "Could not find %s, have you set FLITTER_HOME correctly?"
|
|
file);
|
|
pr2 (spf "Using %s macro file" file);
|
|
let xs = extract_macros file in
|
|
xs +> List.iter (fun (k, v) -> Hashtbl.add _defs k v)
|
|
|
|
let init_defs file =
|
|
Hashtbl.clear _defs;
|
|
add_defs file
|
|
|
|
(*****************************************************************************)
|
|
(* Error recovery *)
|
|
(*****************************************************************************)
|
|
(* see parsing_recovery_cpp.ml *)
|
|
|
|
(*****************************************************************************)
|
|
(* Consistency checking *)
|
|
(*****************************************************************************)
|
|
(* todo: a parsing_consistency_cpp.ml *)
|
|
|
|
(*****************************************************************************)
|
|
(* Helper for main entry point *)
|
|
(*****************************************************************************)
|
|
|
|
(* Hacked lex. This function use refs passed by parse.
|
|
* 'tr' means 'token refs'. This is used mostly to enable
|
|
* error recovery (This used to do lots of stuff, such as
|
|
* calling some lookahead heuristics to reclassify
|
|
* tokens such as TIdent into TIdent_Typeded but this is
|
|
* now done in a fix_tokens style in parsing_hacks_typedef.ml.
|
|
*)
|
|
let rec lexer_function tr = fun lexbuf ->
|
|
match tr.PI.rest with
|
|
| [] -> (pr2 "LEXER: ALREADY AT END"; tr.PI.current)
|
|
| v::xs ->
|
|
tr.PI.rest <- xs;
|
|
tr.PI.current <- v;
|
|
tr.PI.passed <- v::tr.PI.passed;
|
|
|
|
if !Flag.debug_lexer then pr2_gen v;
|
|
|
|
if TH.is_comment v
|
|
then lexer_function (*~pass*) tr lexbuf
|
|
else v
|
|
|
|
(* was a define ? *)
|
|
let passed_a_define tr =
|
|
let xs = tr.PI.passed +> List.rev +> Common.exclude TH.is_comment in
|
|
if List.length xs >= 2
|
|
then
|
|
(match Common2.head_middle_tail xs with
|
|
| T.TDefine _, _, T.TCommentNewline_DefineEndOfMacro _ -> true
|
|
| _ -> false
|
|
)
|
|
else begin
|
|
pr2 "WIERD: length list of error recovery tokens < 2 ";
|
|
false
|
|
end
|
|
|
|
(*****************************************************************************)
|
|
(* Main entry point *)
|
|
(*****************************************************************************)
|
|
(*
|
|
* note: as now we go in two passes, there is first all the error message of
|
|
* the lexer, and then the error of the parser. It is not anymore
|
|
* interwinded.
|
|
*
|
|
* !!!This function use refs, and is not reentrant !!! so take care.
|
|
* It uses the _defs global defined above!!!!
|
|
*)
|
|
let parse_with_lang ?(lang=Flag_parsing_cpp.Cplusplus) file =
|
|
|
|
let stat = Parse_info.default_stat file in
|
|
let filelines = Common2.cat_array file in
|
|
|
|
(* -------------------------------------------------- *)
|
|
(* call lexer and get all the tokens *)
|
|
(* -------------------------------------------------- *)
|
|
let toks_orig = tokens file in
|
|
|
|
let toks =
|
|
try Parsing_hacks.fix_tokens ~macro_defs:_defs lang toks_orig
|
|
with Token_views_cpp.UnclosedSymbol s ->
|
|
pr2 s;
|
|
if !Flag.debug_cplusplus
|
|
then raise (Token_views_cpp.UnclosedSymbol s)
|
|
else toks_orig
|
|
in
|
|
|
|
let tr = Parse_info.mk_tokens_state toks in
|
|
let lexbuf_fake = Lexing.from_function (fun _buf _n -> raise Impossible) in
|
|
|
|
let rec loop () =
|
|
|
|
let info = TH.info_of_tok tr.PI.current in
|
|
(* todo?: I am not sure that it represents current_line, cos maybe
|
|
* tr.current partipated in the previous parsing phase, so maybe tr.current
|
|
* is not the first token of the next parsing phase. Same with checkpoint2.
|
|
* It would be better to record when we have a } or ; in parser.mly,
|
|
* cos we know that they are the last symbols of external_declaration2.
|
|
*)
|
|
let checkpoint = PI.line_of_info info in
|
|
(* bugfix: may not be equal to 'file' as after macro expansions we can
|
|
* start to parse a new entity from the body of a macro, for instance
|
|
* when parsing a define_machine() body, cf standard.h
|
|
*)
|
|
let checkpoint_file = PI.file_of_info info in
|
|
|
|
tr.PI.passed <- [];
|
|
(* for some statistics *)
|
|
let was_define = ref false in
|
|
|
|
let elem =
|
|
(try
|
|
(* -------------------------------------------------- *)
|
|
(* Call parser *)
|
|
(* -------------------------------------------------- *)
|
|
Parser_cpp.toplevel (lexer_function tr) lexbuf_fake
|
|
with e ->
|
|
if not !Flag.error_recovery
|
|
then raise (Parse_error (TH.info_of_tok tr.PI.current));
|
|
|
|
if !Flag.show_parsing_error then
|
|
(match e with
|
|
(* Lexical is not anymore launched I think *)
|
|
| Lexer.Lexical s ->
|
|
pr2 ("lexical error " ^s^ "\n =" ^ error_msg_tok tr.PI.current)
|
|
| Parsing.Parse_error ->
|
|
pr2 ("parse error \n = " ^ error_msg_tok tr.PI.current)
|
|
| Semantic.Semantic (s, _i) ->
|
|
pr2 ("semantic error " ^s^ "\n ="^ error_msg_tok tr.PI.current)
|
|
| e -> raise e
|
|
);
|
|
|
|
let line_error = TH.line_of_tok tr.PI.current in
|
|
|
|
let pbline =
|
|
tr.PI.passed
|
|
+> List.filter (is_same_line_or_close line_error)
|
|
+> List.filter TH.is_ident_like
|
|
in
|
|
let error_info =
|
|
(pbline +> List.map (fun tok->PI.str_of_info (TH.info_of_tok tok))),
|
|
line_error
|
|
in
|
|
stat.Stat.problematic_lines <-
|
|
error_info::stat.Stat.problematic_lines;
|
|
|
|
(* error recovery, go to next synchro point *)
|
|
let (passed', rest') =
|
|
Parsing_recovery_cpp.find_next_synchro tr.PI.rest tr.PI.passed in
|
|
tr.PI.rest <- rest';
|
|
tr.PI.passed <- passed';
|
|
|
|
tr.PI.current <- List.hd passed';
|
|
|
|
(* <> line_error *)
|
|
let info = TH.info_of_tok tr.PI.current in
|
|
let checkpoint2 = PI.line_of_info info in
|
|
let checkpoint2_file = PI.file_of_info info in
|
|
|
|
was_define := passed_a_define tr;
|
|
(if !was_define && !Flag.filter_define_error
|
|
then ()
|
|
else
|
|
(* bugfix: *)
|
|
(if (checkpoint_file = checkpoint2_file) && checkpoint_file = file
|
|
then PI.print_bad line_error (checkpoint, checkpoint2) filelines
|
|
else pr2 "PB: bad: but on tokens not from original file"
|
|
)
|
|
);
|
|
|
|
let info_of_bads =
|
|
Common2.map_eff_rev TH.info_of_tok tr.PI.passed in
|
|
|
|
Some (Ast.NotParsedCorrectly info_of_bads)
|
|
)
|
|
in
|
|
|
|
(* again not sure if checkpoint2 corresponds to end of bad region *)
|
|
let info = TH.info_of_tok tr.PI.current in
|
|
let checkpoint2 = PI.line_of_info info in
|
|
let checkpoint2_file = PI.file_of_info info in
|
|
|
|
let diffline =
|
|
if (checkpoint_file = checkpoint2_file) && (checkpoint_file = file)
|
|
then (checkpoint2 - checkpoint)
|
|
else 0
|
|
(* TODO? so if error come in middle of something ? where the
|
|
* start token was from original file but synchro found in body
|
|
* of macro ? then can have wrong number of lines stat.
|
|
* Maybe simpler just to look at tr.passed and count
|
|
* the lines in the token from the correct file ?
|
|
*)
|
|
in
|
|
let info = List.rev tr.PI.passed in
|
|
|
|
(* some stat updates *)
|
|
stat.Stat.commentized <-
|
|
stat.Stat.commentized + count_lines_commentized info;
|
|
(match elem with
|
|
| Some (Ast.NotParsedCorrectly _xs) ->
|
|
if !was_define && !Flag.filter_define_error
|
|
then stat.Stat.commentized <- stat.Stat.commentized + diffline
|
|
else stat.Stat.bad <- stat.Stat.bad + diffline
|
|
|
|
| _ -> stat.Stat.correct <- stat.Stat.correct + diffline
|
|
);
|
|
|
|
(match elem with
|
|
| None -> []
|
|
| Some xs -> (xs, info):: loop () (* recurse *)
|
|
)
|
|
in
|
|
let v = loop() in
|
|
(v, stat)
|
|
|
|
let parse2 file =
|
|
match File_type.file_type_of_file file with
|
|
| FT.PL (FT.C _) ->
|
|
(try
|
|
parse_with_lang ~lang:Flag.C file
|
|
with _exn ->
|
|
parse_with_lang ~lang:Flag.Cplusplus file
|
|
)
|
|
| FT.PL (FT.Cplusplus _) ->
|
|
parse_with_lang ~lang:Flag.Cplusplus file
|
|
| _ -> failwith (spf "not a C/C++ file: %s" file)
|
|
|
|
|
|
let parse file =
|
|
Common.profile_code "Parse_cpp.parse" (fun () ->
|
|
try
|
|
parse2 file
|
|
with Stack_overflow ->
|
|
pr2 (spf "PB stack overflow in %s" file);
|
|
[(Ast.NotParsedCorrectly [], ([]))], {Stat.
|
|
correct = 0;
|
|
bad = Common2.nblines_with_wc file;
|
|
filename = file;
|
|
have_timeout = true;
|
|
commentized = 0;
|
|
problematic_lines = [];
|
|
}
|
|
)
|
|
|
|
let parse_program file =
|
|
let (ast2, _stat) = parse file in
|
|
program_of_program2 ast2
|