[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