[clang frontend] Pre-process AST locations to make them explicit and easier to deal with. Don't dive into include files.
Summary: @public The clang location information is described in an incremental way: each location information is a delta with respect to the previous one in the AST. This is based on a the visit of the AST nodes which corresponds to the order in which the lines are printed with the standard clang AST dump: clang -cc1 -ast-dump filename.c This diff adds a preprocessing phase to the front-end so that location information is composed during a visit, and explicit location information is used instead. In the case of include files, we report the last known location before including the file. The current file for a function is the file where it is defined. So if a function is entirely defined in a .h file, then the location information will consistently be about the .h file. If instead a function is defined in the source file being analyzed, and some AST nodes come from macro expansion, line information will refer to the original file. The front-end tests reveal that the location information was incorrect in a few dot files. Test Plan: arc unit, after having fixed the wrong location in the existing .dot filesmaster
parent
d95c4cd12c
commit
10970c4f51
@ -0,0 +1,290 @@
|
||||
(*
|
||||
* Copyright (c) 2015 - Facebook.
|
||||
* All rights reserved.
|
||||
*)
|
||||
|
||||
(** Module to preprocess location information in the AST.
|
||||
The original location information is incremental, each location is a delta
|
||||
w.r.t. the previous one. This module processes the AST and makes locations explicit. *)
|
||||
|
||||
open Utils
|
||||
open Clang_ast_j
|
||||
|
||||
module L = Logging
|
||||
module F = Format
|
||||
|
||||
|
||||
(** Get the sub-declarations of the current declaration. *)
|
||||
let decl_get_sub_decls decl = match decl with
|
||||
| CXXRecordDecl (_, _, _, decl_list, _, _)
|
||||
| RecordDecl (_, _, _, decl_list, _, _)
|
||||
| ObjCInterfaceDecl (_, _, decl_list, _, _)
|
||||
| ObjCProtocolDecl (_, _, decl_list, _, _)
|
||||
| ObjCCategoryDecl (_, _, decl_list, _, _)
|
||||
| ObjCCategoryImplDecl (_, _, decl_list, _, _)
|
||||
| ObjCImplementationDecl (_, _, decl_list, _, _)
|
||||
| EnumDecl (_, _, _, decl_list, _, _)
|
||||
| LinkageSpecDecl (_, decl_list, _)
|
||||
| NamespaceDecl (_, _, decl_list, _, _) ->
|
||||
decl_list
|
||||
| _ ->
|
||||
[]
|
||||
|
||||
|
||||
(** Set the sub-declarations of the current declaration. *)
|
||||
let decl_set_sub_decls decl decl_list' = match decl with
|
||||
| CXXRecordDecl (decl_info, name, opt_type, decl_list, decl_context_info, record_decl_info) ->
|
||||
CXXRecordDecl (decl_info, name, opt_type, decl_list', decl_context_info, record_decl_info)
|
||||
| RecordDecl (decl_info, name, opt_type, decl_list, decl_context_info, record_decl_info) ->
|
||||
RecordDecl (decl_info, name, opt_type, decl_list', decl_context_info, record_decl_info)
|
||||
| ObjCInterfaceDecl (decl_info, name, decl_list, decl_context_info, obj_c_interface_decl_info) ->
|
||||
ObjCInterfaceDecl (decl_info, name, decl_list', decl_context_info, obj_c_interface_decl_info)
|
||||
| ObjCProtocolDecl (decl_info, name, decl_list, decl_context_info, obj_c_protocol_decl_info) ->
|
||||
ObjCProtocolDecl (decl_info, name, decl_list', decl_context_info, obj_c_protocol_decl_info)
|
||||
| ObjCCategoryDecl (decl_info, name, decl_list, decl_context_info, category_decl_info) ->
|
||||
ObjCCategoryDecl (decl_info, name, decl_list', decl_context_info, category_decl_info)
|
||||
| ObjCCategoryImplDecl (decl_info, name, decl_list, decl_context_info, category_impl_info) ->
|
||||
ObjCCategoryImplDecl (decl_info, name, decl_list', decl_context_info, category_impl_info)
|
||||
| ObjCImplementationDecl (decl_info, class_name, decl_list, decl_context_info, idi) ->
|
||||
ObjCImplementationDecl (decl_info, class_name, decl_list', decl_context_info, idi)
|
||||
| EnumDecl (decl_info, name, opt_type, decl_list, decl_context_info, enum_decl_info) ->
|
||||
EnumDecl (decl_info, name, opt_type, decl_list', decl_context_info, enum_decl_info)
|
||||
| LinkageSpecDecl (decl_info, decl_list, decl_context_info) ->
|
||||
LinkageSpecDecl (decl_info, decl_list', decl_context_info)
|
||||
| NamespaceDecl (decl_info, name, decl_list, decl_context_info, namespace_decl_info) ->
|
||||
NamespaceDecl (decl_info, name, decl_list', decl_context_info, namespace_decl_info)
|
||||
| _ ->
|
||||
decl
|
||||
|
||||
|
||||
(** Pretty print a source location. *)
|
||||
let pp_source_loc fmt source_loc =
|
||||
let file = match source_loc.sl_file with
|
||||
| Some file -> file
|
||||
| None -> "None" in
|
||||
let line = match source_loc.sl_line with
|
||||
| Some n -> string_of_int n
|
||||
| None -> "None" in
|
||||
let column = match source_loc.sl_column with
|
||||
| Some n -> string_of_int n
|
||||
| None -> "None" in
|
||||
if file = "None" && line = "None" && column = "None"
|
||||
then F.fprintf fmt "_"
|
||||
else F.fprintf fmt "%s:%s:%s" file line column
|
||||
|
||||
|
||||
(** Pretty print a source range. *)
|
||||
let pp_source_range fmt (sloc1, sloc2) =
|
||||
F.fprintf fmt "<%a, %a>" pp_source_loc sloc1 pp_source_loc sloc2
|
||||
|
||||
|
||||
(** Pretty print an AST. *)
|
||||
let pp_ast_decl fmt ast_decl =
|
||||
let rec dump_stmt prefix stmt =
|
||||
let prefix1 = prefix ^ " " in
|
||||
let stmt_str = Clang_ast_proj.get_stmt_kind_string stmt in
|
||||
let stmt_info, stmt_list = Clang_ast_proj.get_stmt_tuple stmt in
|
||||
let decl_list = match stmt with
|
||||
| DeclStmt (_, _, decl_list) -> decl_list
|
||||
| _ -> [] in
|
||||
F.fprintf fmt "%s%s %a@\n"
|
||||
prefix
|
||||
stmt_str
|
||||
pp_source_range stmt_info.si_source_range;
|
||||
list_iter (dump_stmt prefix1) stmt_list;
|
||||
list_iter (dump_decl prefix1) decl_list
|
||||
and dump_decl prefix decl =
|
||||
let prefix1 = prefix ^ " " in
|
||||
match decl with
|
||||
| FunctionDecl (decl_info, name, qt, fdecl_info) ->
|
||||
F.fprintf fmt "%sFunctionDecl %s %a@\n"
|
||||
prefix
|
||||
name
|
||||
pp_source_range decl_info.di_source_range;
|
||||
list_iter (dump_decl prefix1) fdecl_info.fdi_decls_in_prototype_scope;
|
||||
list_iter (dump_decl prefix1) fdecl_info.fdi_parameters;
|
||||
Option.may (dump_stmt prefix1) fdecl_info.fdi_body
|
||||
| ObjCMethodDecl (decl_info, name, obj_c_method_decl_info) ->
|
||||
F.fprintf fmt "%sObjCMethodDecl %s %a@\n"
|
||||
prefix
|
||||
name
|
||||
pp_source_range decl_info.di_source_range;
|
||||
Option.may (dump_stmt prefix1) obj_c_method_decl_info.omdi_body
|
||||
| VarDecl (decl_info, string, qual_type, var_decl_info) ->
|
||||
F.fprintf fmt "%sVarDecl %a@\n"
|
||||
prefix
|
||||
pp_source_range decl_info.di_source_range;
|
||||
Option.may (dump_stmt prefix1) var_decl_info.vdi_init_expr
|
||||
| _ ->
|
||||
let decl_kind_str = Clang_ast_proj.get_decl_kind_string decl in
|
||||
let decl_info = Clang_ast_proj.get_decl_tuple decl in
|
||||
let decl_list = decl_get_sub_decls decl in
|
||||
F.fprintf fmt "%s%s %a@\n"
|
||||
prefix
|
||||
decl_kind_str
|
||||
pp_source_range decl_info.di_source_range;
|
||||
list_iter (dump_decl prefix1) decl_list in
|
||||
|
||||
let decl_str = Clang_ast_proj.get_decl_kind_string ast_decl in
|
||||
match ast_decl with
|
||||
| TranslationUnitDecl (_, decl_list, _) ->
|
||||
F.fprintf fmt "%s (%d declarations)@\n" decl_str (list_length decl_list);
|
||||
list_iter (dump_decl "") decl_list
|
||||
| _ ->
|
||||
assert false
|
||||
|
||||
|
||||
(** Compose incremental location information and make locations explicit. *)
|
||||
module LocComposer : sig
|
||||
(** Status of the composer. *)
|
||||
type status
|
||||
|
||||
(** Create a new composer with the initial status. *)
|
||||
val create : unit -> status
|
||||
|
||||
(** Compose a new source_range to the current one. *)
|
||||
val compose : status -> source_range -> source_range
|
||||
|
||||
(** Set the current file if specified in the source_range.
|
||||
The composer will not descend into file included from the current one.
|
||||
For locations in included files, it will return instead the last known
|
||||
location of the current file. *)
|
||||
val set_current_file : status -> source_range -> unit
|
||||
end = struct
|
||||
type status =
|
||||
{ mutable curr_file: string option;
|
||||
mutable curr_source_range: source_range;
|
||||
mutable in_curr_file : bool }
|
||||
|
||||
let empty_sloc = { Clang_ast_t.sl_file = None; sl_line = None; sl_column = None }
|
||||
|
||||
let create () =
|
||||
{ curr_file = None;
|
||||
curr_source_range = (empty_sloc, empty_sloc);
|
||||
in_curr_file = true; }
|
||||
|
||||
let set_current_file st (sloc1, sloc2) =
|
||||
match sloc1.sl_file, sloc2.sl_file with
|
||||
| _, Some fname
|
||||
| Some fname, None ->
|
||||
st.curr_file <- Some fname;
|
||||
st.in_curr_file <- true
|
||||
| _ ->
|
||||
()
|
||||
|
||||
let sloc_is_current_file st sloc = match st.curr_file, sloc.sl_file with
|
||||
| Some curr_f, Some f ->
|
||||
Some (f = curr_f)
|
||||
| None, _ -> None
|
||||
| _, None -> None
|
||||
|
||||
let update_sloc st old_sloc new_sloc =
|
||||
match sloc_is_current_file st new_sloc with
|
||||
| Some true ->
|
||||
st.in_curr_file <- true;
|
||||
new_sloc
|
||||
| Some false ->
|
||||
st.in_curr_file <- false;
|
||||
old_sloc
|
||||
| None ->
|
||||
if st.in_curr_file
|
||||
then
|
||||
let update x_opt y_opt =
|
||||
if y_opt <> None then y_opt else x_opt in
|
||||
{ sl_file = update old_sloc.sl_file new_sloc.sl_file;
|
||||
sl_line = update old_sloc.sl_line new_sloc.sl_line;
|
||||
sl_column = update old_sloc.sl_column new_sloc.sl_column }
|
||||
else
|
||||
old_sloc
|
||||
|
||||
let update_status st (sloc1, sloc2) =
|
||||
if sloc1 = empty_sloc && sloc2 = empty_sloc
|
||||
then
|
||||
()
|
||||
else
|
||||
let _, old_sloc2 = st.curr_source_range in
|
||||
let new_sloc1 = update_sloc st old_sloc2 sloc1 in
|
||||
let new_sloc2 = update_sloc st new_sloc1 sloc2 in
|
||||
st.curr_source_range <- (new_sloc1, new_sloc2)
|
||||
|
||||
let compose st source_range =
|
||||
update_status st source_range;
|
||||
st.curr_source_range
|
||||
end
|
||||
|
||||
|
||||
(** Apply a location composer to the locations in a statement. *)
|
||||
let rec stmt_process_locs loc_composer stmt =
|
||||
let update (stmt_info, stmt_list) =
|
||||
let stmt_info' =
|
||||
{ stmt_info with
|
||||
si_source_range = LocComposer.compose loc_composer stmt_info.si_source_range } in
|
||||
let stmt_list' = list_map (stmt_process_locs loc_composer) stmt_list in
|
||||
(stmt_info', stmt_list') in
|
||||
match Clang_ast_proj.update_stmt_tuple update stmt with
|
||||
| DeclStmt (stmt_info, stmt_list, decl_list) ->
|
||||
let decl_list' = list_map (decl_process_locs loc_composer) decl_list in
|
||||
DeclStmt (stmt_info, stmt_list, decl_list')
|
||||
| stmt' ->
|
||||
stmt'
|
||||
|
||||
(** Apply a location composer to the locations in a declaration. *)
|
||||
and decl_process_locs loc_composer decl =
|
||||
let decl' =
|
||||
let update decl_info =
|
||||
{ decl_info with
|
||||
di_source_range = LocComposer.compose loc_composer decl_info.di_source_range } in
|
||||
let decl_list = decl_get_sub_decls decl in
|
||||
let decl1 = Clang_ast_proj.update_decl_tuple update decl in
|
||||
let decl_list' = list_map (decl_process_locs loc_composer) decl_list in
|
||||
decl_set_sub_decls decl1 decl_list' in
|
||||
match decl' with
|
||||
| FunctionDecl (decl_info', name, qt, fdecl_info) ->
|
||||
let fdi_decls_in_prototype_scope' =
|
||||
list_map (decl_process_locs loc_composer) fdecl_info.fdi_decls_in_prototype_scope in
|
||||
let fdi_parameters' =
|
||||
list_map (decl_process_locs loc_composer) fdecl_info.fdi_parameters in
|
||||
let body' = Option.map (stmt_process_locs loc_composer) fdecl_info.fdi_body in
|
||||
let fdecl_info' =
|
||||
{ fdecl_info with
|
||||
fdi_body = body';
|
||||
fdi_parameters = fdi_parameters';
|
||||
fdi_decls_in_prototype_scope = fdi_decls_in_prototype_scope'; } in
|
||||
FunctionDecl (decl_info', name, qt, fdecl_info')
|
||||
| ObjCMethodDecl (decl_info', name, obj_c_method_decl_info) ->
|
||||
let body' =
|
||||
Option.map (stmt_process_locs loc_composer) obj_c_method_decl_info.omdi_body in
|
||||
let obj_c_method_decl_info' = { obj_c_method_decl_info with omdi_body = body' } in
|
||||
ObjCMethodDecl (decl_info', name, obj_c_method_decl_info')
|
||||
| VarDecl (decl_info, string, qual_type, var_decl_info) ->
|
||||
let vdi_init_expr' =
|
||||
Option.map (stmt_process_locs loc_composer) var_decl_info.vdi_init_expr in
|
||||
let var_decl_info' =
|
||||
{ var_decl_info with vdi_init_expr = vdi_init_expr' } in
|
||||
VarDecl (decl_info, string, qual_type, var_decl_info')
|
||||
| _ ->
|
||||
decl'
|
||||
|
||||
|
||||
(** Process locations in the AST by making them explicit.
|
||||
Each toplevel declaration determines the current file,
|
||||
and once diving into the details of the declaration, location
|
||||
information about other (include) files is ignored. *)
|
||||
let ast_decl_process_locs loc_composer ast_decl =
|
||||
|
||||
let toplevel_decl_process_locs decl =
|
||||
let decl_info = Clang_ast_proj.get_decl_tuple decl in
|
||||
LocComposer.set_current_file loc_composer decl_info.di_source_range;
|
||||
decl_process_locs loc_composer decl in
|
||||
|
||||
match ast_decl with
|
||||
| TranslationUnitDecl (decl_info, decl_list, decl_context_info) ->
|
||||
let decl_list' = list_map toplevel_decl_process_locs decl_list in
|
||||
TranslationUnitDecl (decl_info, decl_list', decl_context_info)
|
||||
| _ ->
|
||||
assert false
|
||||
|
||||
|
||||
let preprocess_ast_decl ast_decl =
|
||||
let loc_composer = LocComposer.create () in
|
||||
ast_decl_process_locs loc_composer ast_decl
|
@ -0,0 +1,14 @@
|
||||
(*
|
||||
* Copyright (c) 2015 - Facebook.
|
||||
* All rights reserved.
|
||||
*)
|
||||
|
||||
(** Module to preprocess location information in the AST.
|
||||
The original location information is incremental, each location is a delta
|
||||
w.r.t. the previous one. This module processes the AST and makes locations explicit. *)
|
||||
|
||||
(** Pretty print an AST. *)
|
||||
val pp_ast_decl : Format.formatter -> Clang_ast_j.decl -> unit
|
||||
|
||||
(** Preprocess the AST to make locations explicit. *)
|
||||
val preprocess_ast_decl : Clang_ast_j.decl -> Clang_ast_j.decl
|
Loading…
Reference in new issue