From 74faebf4b46d52b2ec0425da32eeede5aaf9e46e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20B=C3=BChler?= <david.buhler@cea.fr> Date: Fri, 24 Sep 2021 11:38:46 +0200 Subject: [PATCH] [kernel] Moves function [loc_to_localizable] from the gui plugin to the kernel. This function returns the most precise localizable that contains the given Filepath.position. It is now also needed by the server plugin. --- .../ast_printing/printer_tag.ml | 206 ++++++++++++++++++ .../ast_printing/printer_tag.mli | 6 + src/plugins/gui/design.ml | 2 +- src/plugins/gui/history.ml | 2 +- src/plugins/gui/pretty_source.ml | 203 ----------------- src/plugins/gui/pretty_source.mli | 7 - src/plugins/gui/source_manager.ml | 2 +- 7 files changed, 215 insertions(+), 213 deletions(-) diff --git a/src/kernel_services/ast_printing/printer_tag.ml b/src/kernel_services/ast_printing/printer_tag.ml index 4f07c37e146..c8cc00fef64 100644 --- a/src/kernel_services/ast_printing/printer_tag.ml +++ b/src/kernel_services/ast_printing/printer_tag.ml @@ -244,6 +244,212 @@ let loc_of_localizable = function | None -> Location.unknown | Some kf -> Kernel_function.get_location kf) + +(* -------------------------------------------------------------------------- *) +(* --- Find localizable at a Filepath.position --- *) +(* -------------------------------------------------------------------------- *) + +let dkey = Kernel.register_category "pretty-source" + +module LineToLocalizable = + Datatype.Hashtbl(Datatype.Int.Hashtbl)(Datatype.Int) + (struct let module_name = "Pretty_source.LineToLocalizable" end) +module FileToLines = + Datatype.Hashtbl(Datatype.Filepath.Hashtbl)(Datatype.Filepath) + (struct let module_name = "Pretty_source.FilesToLine" end) + +module MappingLineLocalizable = struct + module LineToLocalizableAux = + LineToLocalizable.Make( Datatype.Pair(Location)(Localizable)) + + include State_builder.Hashtbl(FileToLines)(LineToLocalizableAux) + (struct + let size = 5 + let dependencies = [Ast.self] + let name = "Pretty_source.line_to_localizable" + end) +end + +class pos_to_localizable = + object (self) + inherit Visitor.frama_c_inplace + + (* used to keep track of conditional expressions, to add them to the + list of relevant localizables *) + val mutable insideIf = None + + method add_range loc (localizable : localizable) = + if not (Location.equal loc Location.unknown) then ( + let p1, p2 = loc in + if p1.Filepath.pos_path <> p2.Filepath.pos_path then + Kernel.debug ~once:true ~dkey + "Localizable over two files: %a and %a; %a" + Datatype.Filepath.pretty p1.Filepath.pos_path + Datatype.Filepath.pretty p2.Filepath.pos_path + Localizable.pretty localizable; + let file = p1.Filepath.pos_path in + let hfile = + try MappingLineLocalizable.find file + with Not_found -> + let h = LineToLocalizable.create 17 in + MappingLineLocalizable.add file h; + h + in + for i = p1.Filepath.pos_lnum to p2.Filepath.pos_lnum do + LineToLocalizable.add hfile i (loc, localizable); + done + ); + + method! vstmt_aux s = + (* we ignore Block statements, since they tend to overlap existing + ones which are more precise *) + let skip = match s.skind with + | Block _ -> true + | _ -> false + in + if not skip then + self#add_range (Stmt.loc s) (PStmt (Option.get self#current_kf, s)); + begin + match s.skind with + | If (exp, _, _, _) -> + (* conditional expressions are treated in a special way *) + insideIf <- Some (Kstmt s); + ignore (Cil.visitCilExpr (self :> Cil.cilVisitor) exp); + insideIf <- None + | _ -> () + end; + Cil.DoChildren + + method! vexpr exp = + begin + match insideIf with + | Some ki -> + (* expressions inside conditionals have a special treatment *) + begin + match exp.enode with + | Lval lv -> + (* lvals must be generated differently from other expressions *) + self#add_range exp.eloc (PLval(self#current_kf, ki, lv)) + | _ -> + self#add_range exp.eloc (PExp(self#current_kf, ki, exp)) + end + | None -> () + end; + Cil.DoChildren + + method! vvdec vi = + if not vi.vglob && not vi.vtemp then + begin + match self#current_kf with + | None -> (* should not happen*) () + | Some kf -> + self#add_range vi.vdecl (PVDecl (Some kf,self#current_kinstr,vi)); + end; + Cil.DoChildren + + method! vglob_aux g = + (match g with + | GFun ({ svar = vi }, loc) -> + self#add_range loc + (PVDecl (Some (Globals.Functions.get vi), Kglobal, vi)) + | GVar (vi, _, loc) -> + self#add_range loc (PVDecl (None, Kglobal, vi)) + | GFunDecl (_, vi, loc) -> + self#add_range loc + (PVDecl (Some (Globals.Functions.get vi), Kglobal, vi)) + | GVarDecl (vi, loc) -> + self#add_range loc (PVDecl (None, Kglobal, vi)) + | _ -> self#add_range (Global.loc g) (PGlobal g) + ); + Cil.DoChildren + end + +(* Returns [true] if the column [col] is within location [loc]. *) +let location_contains_col loc col = + let (pos_start, pos_end) = loc in + let (col_start, col_end) = + pos_start.Filepath.pos_cnum - pos_start.Filepath.pos_bol, + pos_end.Filepath.pos_cnum - pos_end.Filepath.pos_bol + in + col_start <= col && col <= col_end + +(* Applies several heuristics to try and match the best localizable to a + given location [loc]. The list [possible_locs] should contain all + localizables in a given line. If [possible_col] is [true], then we try + to take column information into account. + Some heuristics may return an empty list, in which case a fallback is + later used to return a better choice. *) +let apply_location_heuristics precise_col possible_locs loc = + let col = loc.Filepath.pos_cnum - loc.Filepath.pos_bol in + Kernel.debug ~dkey + "apply_location_heuristics (precise_col:%b): loc: %a, col: %d@\n\ + possible_locs:@ %a" + precise_col Location.pretty (loc, loc) col + (Pretty_utils.pp_list ~sep:"@\n" + (Pretty_utils.pp_pair ~sep:" :: " Location.pretty Localizable.pretty)) possible_locs; + (* Heuristic 1: we try to obtain a subset of localizables related to a given + position, or a given column if [precise_col] is true. + May result in an empty list. *) + let filter_locs l = + List.filter (fun (((pos_start, _) as loc'), _) -> + if precise_col then location_contains_col loc' col + else loc = pos_start + ) l + in + (* Heuristic 2: prioritize expressions if they are present. + May result in an empty list. *) + let exps l = + List.filter (fun (_, lz) -> match lz with | PExp _ -> true | _ -> false) l + in + (* Heuristic 3: when we have more than one match with the exact same location, + we pick the last one in the list. This will be the first statement that has + been encountered, and this criterion seems to work well with temporaries + introduced by Cil. *) + let last l = match List.rev l with [] -> None | (_, lz) :: _ -> Some lz in + (* Heuristic 4: when there are no exact locations, we will consider the + innermost ones, that is, those at the top of the list. *) + let innermost_in loc l = + List.filter (fun (loc', _) -> Location.equal loc loc') l + in + match possible_locs, filter_locs possible_locs with + | [], _ -> (* no possible localizables *) None + | _, (_ :: _ as exact) -> + (* one or more exact localizables; we prioritize expressions *) + begin + match exps exact with + | [] -> (* no expressions, just take the last localizable *) last exact + | exps -> (* take the last (usually only) expression *) last exps + end + | (loc', _) :: __, [] -> + (* No exact loc. We consider the innermost statements, + ie those at the top of the list *) + let filtered = innermost_in loc' possible_locs in + last filtered + +let loc_to_localizable ?(precise_col=false) loc = + if not (MappingLineLocalizable.is_computed ()) then ( + let vis = new pos_to_localizable in + Visitor.visitFramacFile (vis :> Visitor.frama_c_visitor) (Ast.get ()); + MappingLineLocalizable.mark_as_computed (); + ); + try + (* Find the mapping from this file to locs-by-line *) + let hfile = MappingLineLocalizable.find loc.Filepath.pos_path in + (* Find the localizable for this line *) + let all = LineToLocalizable.find_all hfile loc.Filepath.pos_lnum in + match apply_location_heuristics precise_col all loc with + | Some locz -> + Kernel.feedback ~dkey "loc: %a -> locz: %a" + Location.pretty (loc,loc) Localizable.pretty locz; + Some locz + | None -> + Kernel.feedback ~dkey "loc: %a -> NO locz" Location.pretty (loc,loc); + None + with + | Not_found -> + Kernel.debug ~once:true ~source:loc "no matching localizable found"; + None + (* -------------------------------------------------------------------------- *) (* --- Printer API --- *) (* -------------------------------------------------------------------------- *) diff --git a/src/kernel_services/ast_printing/printer_tag.mli b/src/kernel_services/ast_printing/printer_tag.mli index a35466dfc72..4f67fdebedb 100644 --- a/src/kernel_services/ast_printing/printer_tag.mli +++ b/src/kernel_services/ast_printing/printer_tag.mli @@ -62,6 +62,12 @@ val typ_of_localizable: localizable -> typ option val loc_of_localizable : localizable -> location (** Might return [Location.unknown] *) +val loc_to_localizable: ?precise_col:bool -> Filepath.position -> localizable option +(** return the (hopefully) most precise localizable that contains the given + Filepath.position. If [precise_col] is [true], takes the column number into + account (possibly a more precise, but costly, result). + @since Frama-C+dev *) + module type Tag = sig val create : localizable -> string diff --git a/src/plugins/gui/design.ml b/src/plugins/gui/design.ml index 1c516ca0d95..1d24782c859 100644 --- a/src/plugins/gui/design.ml +++ b/src/plugins/gui/design.ml @@ -1697,7 +1697,7 @@ class main_window () : main_window_extension_points = let callback e _column = Option.iter (fun pos -> - Option.iter self#scroll (Pretty_source.loc_to_localizable pos); + Option.iter self#scroll (Printer_tag.loc_to_localizable pos); (* Note: the code below generates double scrolling: the previous call to self#scroll causes the original source viewer to scroll to the beginning of the function, and then diff --git a/src/plugins/gui/history.ml b/src/plugins/gui/history.ml index be4771062cb..d6c84731ce8 100644 --- a/src/plugins/gui/history.ml +++ b/src/plugins/gui/history.ml @@ -276,7 +276,7 @@ let translate_history_elt old_helt = Filepath.pos_cnum = old_loc.Filepath.pos_cnum; } in - match Pretty_source.loc_to_localizable new_loc with + match Printer_tag.loc_to_localizable new_loc with | None -> (** the line is unknown *) Some (Global g) | Some locali -> diff --git a/src/plugins/gui/pretty_source.ml b/src/plugins/gui/pretty_source.ml index c2aa878bbe0..a2852f171ab 100644 --- a/src/plugins/gui/pretty_source.ml +++ b/src/plugins/gui/pretty_source.ml @@ -22,7 +22,6 @@ open Cil_types open Gtk_helper -open Cil_datatype open Printer_tag type localizable = Printer_tag.localizable = @@ -41,8 +40,6 @@ type localizable = Printer_tag.localizable = definitions. *) | PIP of Property.t -let dkey = Gui_parameters.register_category "pretty-source" - let kf_of_localizable = Printer_tag.kf_of_localizable let ki_of_localizable = Printer_tag.ki_of_localizable let varinfo_of_localizable = Printer_tag.varinfo_of_localizable @@ -419,206 +416,6 @@ let display_source globals (fun () -> GtkSignal.disconnect event_tag#as_tag id); ) - -module LineToLocalizable = - Datatype.Hashtbl(Datatype.Int.Hashtbl)(Datatype.Int) - (struct let module_name = "Pretty_source.LineToLocalizable" end) -module FileToLines = - Datatype.Hashtbl(Datatype.Filepath.Hashtbl)(Datatype.Filepath) - (struct let module_name = "Pretty_source.FilesToLine" end) - -module MappingLineLocalizable = struct - module LineToLocalizableAux = - LineToLocalizable.Make( Datatype.Pair(Location)(Localizable)) - - include State_builder.Hashtbl(FileToLines)(LineToLocalizableAux) - (struct - let size = 5 - let dependencies = [Ast.self] - let name = "Pretty_source.line_to_localizable" - end) -end - -class pos_to_localizable = - object (self) - inherit Visitor.frama_c_inplace - - (* used to keep track of conditional expressions, to add them to the - list of relevant localizables *) - val mutable insideIf = None - - method add_range loc (localizable : localizable) = - if not (Location.equal loc Location.unknown) then ( - let p1, p2 = loc in - if p1.Filepath.pos_path <> p2.Filepath.pos_path then - Gui_parameters.debug ~once:true - "Localizable over two files: %a and %a; %a" - Datatype.Filepath.pretty p1.Filepath.pos_path - Datatype.Filepath.pretty p2.Filepath.pos_path - Localizable.pretty localizable; - let file = p1.Filepath.pos_path in - let hfile = - try MappingLineLocalizable.find file - with Not_found -> - let h = LineToLocalizable.create 17 in - MappingLineLocalizable.add file h; - h - in - for i = p1.Filepath.pos_lnum to p2.Filepath.pos_lnum do - LineToLocalizable.add hfile i (loc, localizable); - done - ); - - method! vstmt_aux s = - (* we ignore Block statements, since they tend to overlap existing - ones which are more precise *) - let skip = match s.skind with - | Block _ -> true - | _ -> false - in - if not skip then - self#add_range (Stmt.loc s) (PStmt (Option.get self#current_kf, s)); - begin - match s.skind with - | If (exp, _, _, _) -> - (* conditional expressions are treated in a special way *) - insideIf <- Some (Kstmt s); - ignore (Cil.visitCilExpr (self :> Cil.cilVisitor) exp); - insideIf <- None - | _ -> () - end; - Cil.DoChildren - - method! vexpr exp = - begin - match insideIf with - | Some ki -> - (* expressions inside conditionals have a special treatment *) - begin - match exp.enode with - | Lval lv -> - (* lvals must be generated differently from other expressions *) - self#add_range exp.eloc (PLval(self#current_kf, ki, lv)) - | _ -> - self#add_range exp.eloc (PExp(self#current_kf, ki, exp)) - end - | None -> () - end; - Cil.DoChildren - - method! vvdec vi = - if not vi.vglob && not vi.vtemp then - begin - match self#current_kf with - | None -> (* should not happen*) () - | Some kf -> - self#add_range vi.vdecl (PVDecl (Some kf,self#current_kinstr,vi)); - end; - Cil.DoChildren - - method! vglob_aux g = - (match g with - | GFun ({ svar = vi }, loc) -> - self#add_range loc - (PVDecl (Some (Globals.Functions.get vi), Kglobal, vi)) - | GVar (vi, _, loc) -> - self#add_range loc (PVDecl (None, Kglobal, vi)) - | GFunDecl (_, vi, loc) -> - self#add_range loc - (PVDecl (Some (Globals.Functions.get vi), Kglobal, vi)) - | GVarDecl (vi, loc) -> - self#add_range loc (PVDecl (None, Kglobal, vi)) - | _ -> self#add_range (Global.loc g) (PGlobal g) - ); - Cil.DoChildren - end - -(* Returns [true] if the column [col] is within location [loc]. *) -let location_contains_col loc col = - let (pos_start, pos_end) = loc in - let (col_start, col_end) = - pos_start.Filepath.pos_cnum - pos_start.Filepath.pos_bol, - pos_end.Filepath.pos_cnum - pos_end.Filepath.pos_bol - in - col_start <= col && col <= col_end - -(* Applies several heuristics to try and match the best localizable to a - given location [loc]. The list [possible_locs] should contain all - localizables in a given line. If [possible_col] is [true], then we try - to take column information into account. - Some heuristics may return an empty list, in which case a fallback is - later used to return a better choice. *) -let apply_location_heuristics precise_col possible_locs loc = - let col = loc.Filepath.pos_cnum - loc.Filepath.pos_bol in - Gui_parameters.debug ~dkey - "apply_location_heuristics (precise_col:%b): loc: %a, col: %d@\n\ - possible_locs:@ %a" - precise_col Location.pretty (loc, loc) col - (Pretty_utils.pp_list ~sep:"@\n" - (Pretty_utils.pp_pair ~sep:" :: " Location.pretty Localizable.pretty)) possible_locs; - (* Heuristic 1: we try to obtain a subset of localizables related to a given - position, or a given column if [precise_col] is true. - May result in an empty list. *) - let filter_locs l = - List.filter (fun (((pos_start, _) as loc'), _) -> - if precise_col then location_contains_col loc' col - else loc = pos_start - ) l - in - (* Heuristic 2: prioritize expressions if they are present. - May result in an empty list. *) - let exps l = - List.filter (fun (_, lz) -> match lz with | PExp _ -> true | _ -> false) l - in - (* Heuristic 3: when we have more than one match with the exact same location, - we pick the last one in the list. This will be the first statement that has - been encountered, and this criterion seems to work well with temporaries - introduced by Cil. *) - let last l = match List.rev l with [] -> None | (_, lz) :: _ -> Some lz in - (* Heuristic 4: when there are no exact locations, we will consider the - innermost ones, that is, those at the top of the list. *) - let innermost_in loc l = - List.filter (fun (loc', _) -> Location.equal loc loc') l - in - match possible_locs, filter_locs possible_locs with - | [], _ -> (* no possible localizables *) None - | _, (_ :: _ as exact) -> - (* one or more exact localizables; we prioritize expressions *) - begin - match exps exact with - | [] -> (* no expressions, just take the last localizable *) last exact - | exps -> (* take the last (usually only) expression *) last exps - end - | (loc', _) :: __, [] -> - (* No exact loc. We consider the innermost statements, - ie those at the top of the list *) - let filtered = innermost_in loc' possible_locs in - last filtered - -let loc_to_localizable ?(precise_col=false) loc = - if not (MappingLineLocalizable.is_computed ()) then ( - let vis = new pos_to_localizable in - Visitor.visitFramacFile (vis :> Visitor.frama_c_visitor) (Ast.get ()); - MappingLineLocalizable.mark_as_computed (); - ); - try - (* Find the mapping from this file to locs-by-line *) - let hfile = MappingLineLocalizable.find loc.Filepath.pos_path in - (* Find the localizable for this line *) - let all = LineToLocalizable.find_all hfile loc.Filepath.pos_lnum in - match apply_location_heuristics precise_col all loc with - | Some locz -> - Gui_parameters.feedback ~dkey "loc: %a -> locz: %a" - Location.pretty (loc,loc) Localizable.pretty locz; - Some locz - | None -> - Gui_parameters.feedback ~dkey "loc: %a -> NO locz" Location.pretty (loc,loc); - None - with - | Not_found -> - Gui_parameters.debug ~once:true ~source:loc "no matching localizable found"; - None - (* Local Variables: compile-command: "make -C ../../.." diff --git a/src/plugins/gui/pretty_source.mli b/src/plugins/gui/pretty_source.mli index a3544e647fb..96e25243efb 100644 --- a/src/plugins/gui/pretty_source.mli +++ b/src/plugins/gui/pretty_source.mli @@ -88,13 +88,6 @@ val localizable_from_locs : This function is inefficient as it iterates on all the current [Locs.state]. *) -val loc_to_localizable: ?precise_col:bool -> Filepath.position -> localizable option -(** return the (hopefully) most precise localizable that contains the given - Filepath.position. If [precise_col] is [true], takes the column number into - account (possibly a more precise, but costly, result). - @since Nitrogen-20111001 *) - - (* Local Variables: compile-command: "make -C ../../.." diff --git a/src/plugins/gui/source_manager.ml b/src/plugins/gui/source_manager.ml index d3d25091072..7a99892c460 100644 --- a/src/plugins/gui/source_manager.ml +++ b/src/plugins/gui/source_manager.ml @@ -171,7 +171,7 @@ let load_file w ?title ~(filename : Datatype.Filepath.t) ?(line=(-1)) ~click_cb Filepath.pos_bol = offset - col; Filepath.pos_cnum = offset;} in let localz = - Pretty_source.loc_to_localizable ~precise_col:true pos + Printer_tag.loc_to_localizable ~precise_col:true pos in click_cb localz with Not_found -> -- GitLab