diff --git a/ivette/src/dome/src/renderer/text/buffers.js b/ivette/src/dome/src/renderer/text/buffers.js index 30c272d3f99a511b7d3b98b31320ac37bd418b8d..0c0265b78498c432757390e73638c8db3a2c3cbc 100644 --- a/ivette/src/dome/src/renderer/text/buffers.js +++ b/ivette/src/dome/src/renderer/text/buffers.js @@ -540,6 +540,37 @@ is blocked. .finally(endOperation); } + // -------------------------------------------------------------------------- + // --- Print Utilities + // -------------------------------------------------------------------------- + + /** + @summary Print text containing tags into buffer (bound to `this`). + @param {string|string[]} text - Text to print. + @param {object} options - CodeMirror + [text marker](https://codemirror.net/doc/manual.html#api_marker) options. + */ + printTextWithTags(text, options = {}) { + if (Array.isArray(text)) { + const tag = text.shift(); + if (tag !== '') { + const markerOptions = { id: tag, ...options }; + this.openTextMarker(markerOptions); + } + text.forEach((txt) => this.printTextWithTags(txt, options)); + if (tag !== '') { + this.closeTextMarker(); + } + } else if (typeof (text) === 'string') { + this.append(text); + } else { // This case should be useless when using TS. + console.error( + `Function 'printTextWithTags' accepts a parameter of ` + + `type string or string array: got '${typeof text}'.` + ); + } + } + } // -------------------------------------------------------------------------- diff --git a/ivette/src/frama-c/states.ts b/ivette/src/frama-c/states.ts index 5fc3a473d838bb478f267c6ab7472fe621c0bc4c..b846dee4fa49c752ec8489d5bc1fdd505a6fe8b8 100644 --- a/ivette/src/frama-c/states.ts +++ b/ivette/src/frama-c/states.ts @@ -172,22 +172,23 @@ export function useRequest(rq: string, params: any, options: any = {}) { const state = React.useRef<string>(); const project = useProject(); const [response, setResponse] = React.useState(options.offline); - const footprint = - project ? JSON.stringify([project, rq, params]) : undefined; + const footprint = project ? JSON.stringify([project, rq, params]) : undefined; async function trigger() { if (project) { try { - const r = await Server.GET({ endpoint: rq, params }); - setResponse(r); + if (rq && params) { + const r = await Server.GET({ endpoint: rq, params }); + setResponse(r); + } } catch (error) { PP.error(`Fail in useRequest '${rq}'. ${error.toString()}`); const err = options.error; - if (err !== undefined) setResponse(err); + setResponse(err); } } else { const off = options.offline; - if (off !== undefined) setResponse(off); + setResponse(off); } } @@ -447,7 +448,7 @@ class SyncArray { Dome.emit(this.UPDATE); } catch (error) { PP.error( - `Fail to set reload of syncArray '${this.id}. ${error.toString()}`, + `Fail to set reload of syncArray '${this.id}'. ${error.toString()}`, ); } } diff --git a/ivette/src/renderer/ASTinfo.tsx b/ivette/src/renderer/ASTinfo.tsx new file mode 100644 index 0000000000000000000000000000000000000000..e505f52dbcd6fd8f59b56336268cd43df87798a6 --- /dev/null +++ b/ivette/src/renderer/ASTinfo.tsx @@ -0,0 +1,67 @@ +// -------------------------------------------------------------------------- +// --- AST Information +// -------------------------------------------------------------------------- + +import React from 'react'; +import * as States from 'frama-c/states'; + +import { Vfill } from 'dome/layout/boxes'; +import { RichTextBuffer } from 'dome/text/buffers'; +import { Text } from 'dome/text/editors'; +import { Component } from 'frama-c/LabViews'; + +// -------------------------------------------------------------------------- +// --- Information Panel +// -------------------------------------------------------------------------- + +const ASTinfo = () => { + + const buffer = React.useMemo(() => new RichTextBuffer(), []); + const [select, setSelect] = States.useSelection(); + const marker = select && select.marker; + const data = States.useRequest('kernel.ast.info', marker); + + React.useEffect(() => { + buffer.clear(); + if (data) { + buffer.printTextWithTags(data, { css: 'color: blue' }); + } + }, [buffer, data]); + + // Callbacks + function onSelection(name: string) { + // For now, the only markers are functions. + setSelect({ function: name }); + } + + // Component + return ( + <> + <Vfill> + <Text + buffer={buffer} + mode="text" + theme="default" + onSelection={onSelection} + readOnly + /> + </Vfill> + </> + ); +}; + +// -------------------------------------------------------------------------- +// --- Export Component +// -------------------------------------------------------------------------- + +export default () => ( + <Component + id="frama-c.astinfo" + label="Information" + title="AST Information" + > + <ASTinfo /> + </Component> +); + +// -------------------------------------------------------------------------- diff --git a/ivette/src/renderer/ASTview.tsx b/ivette/src/renderer/ASTview.tsx index d21a73b27ea2376869a9a98478ea912595337a74..3fc396103d7f31e045b07c33646d66746e8cf821 100644 --- a/ivette/src/renderer/ASTview.tsx +++ b/ivette/src/renderer/ASTview.tsx @@ -33,22 +33,9 @@ const PP = new Dome.PP('AST View'); // --- Rich Text Printer // -------------------------------------------------------------------------- -const printAST = (buffer: any, text: string) => { - if (Array.isArray(text)) { - const tag = text.shift(); - if (tag !== '') { - buffer.openTextMarker({ id: tag }); - } - text.forEach((txt) => printAST(buffer, txt)); - if (tag !== '') { - buffer.closeTextMarker(); - } - } else if (typeof (text) === 'string') { - buffer.append(text); - } -}; - -async function loadAST(buffer: any, theFunction?: string, theMarker?: string) { +async function loadAST( + buffer: RichTextBuffer, theFunction?: string, theMarker?: string, +) { buffer.clear(); if (theFunction) { buffer.log('// Loading', theFunction, '…'); @@ -60,9 +47,10 @@ async function loadAST(buffer: any, theFunction?: string, theMarker?: string) { }); buffer.operation(() => { buffer.clear(); - if (!data) + if (!data) { buffer.log('// No code for function ', theFunction); - printAST(buffer, data); + } + buffer.printTextWithTags(data); if (theMarker) buffer.scroll(theMarker, undefined); }); @@ -89,6 +77,7 @@ const ASTview = () => { const [theme, setTheme] = Dome.useGlobalSetting('ASTview.theme', 'default'); const [fontSize, setFontSize] = Dome.useGlobalSetting('ASTview.fontSize', 12); const [wrapText, setWrapText] = Dome.useSwitch('ASTview.wrapText', false); + const markers = States.useSyncArray('kernel.ast.markerKind'); const theFunction = select && select.function; const theMarker = select && select.marker; @@ -111,8 +100,18 @@ const ASTview = () => { const zoomOut = () => fontSize > 4 && setFontSize(fontSize - 2); const onSelection = (marker: any) => setSelect({ marker }); - // Theme Popup + function contextMenu(id: string) { + const marker = markers[id]; + if (marker && marker.kind === 'function') { + const item1 = { + label: `Go to definition of ${marker.name}`, + onClick: () => setSelect({ function: marker.name }), + }; + Dome.popupMenu([item1]); + } + } + // Theme Popup const selectTheme = (id?: string) => id && setTheme(id); const checkTheme = (th: { id: string }) => ({ checked: th.id === theme, ...th }); @@ -155,6 +154,7 @@ const ASTview = () => { lineWrapping={wrapText} selection={theMarker} onSelection={onSelection} + onContextMenu={contextMenu} readOnly /> </> diff --git a/ivette/src/renderer/Application.tsx b/ivette/src/renderer/Application.tsx index 10439a75ba89564f385e65850aea5776c04a71d9..e3c343f52bd17df20d95344b3d7e8594ec368b8f 100644 --- a/ivette/src/renderer/Application.tsx +++ b/ivette/src/renderer/Application.tsx @@ -16,6 +16,7 @@ import { GridItem } from 'dome/layout/grids'; import * as Controller from './Controller'; import Properties from './Properties'; import ASTview from './ASTview'; +import ASTinfo from './ASTinfo'; // -------------------------------------------------------------------------- // --- Main View @@ -64,6 +65,7 @@ export default (() => { <Controller.Console /> <Properties /> <ASTview /> + <ASTinfo /> </Group> </LabView> </Splitter> diff --git a/src/kernel_services/ast_printing/printer_tag.ml b/src/kernel_services/ast_printing/printer_tag.ml index a2befca2aa5df23dd6a38a390fe48ae753240b3d..c66a996f4213f566054c7a0f5aee972f5fd015e0 100644 --- a/src/kernel_services/ast_printing/printer_tag.ml +++ b/src/kernel_services/ast_printing/printer_tag.ml @@ -169,14 +169,17 @@ let ki_of_localizable loc = match loc with | PIP ip -> Property.get_kinstr ip | PGlobal _ -> Kglobal -let varinfo_of_localizable loc = - match kf_of_localizable loc with - | Some kf -> Some (Kernel_function.get_vi kf) - | None -> - match loc with - | PGlobal (GVar (vi, _, _) | GVarDecl (vi, _) - | GFunDecl (_, vi, _) | GFun ({svar = vi }, _)) -> Some vi - | _ -> None +let varinfo_of_localizable = function + | PLval (_, _, (Var vi, NoOffset)) -> Some vi + | PVDecl (_, _, vi) -> Some vi + | PGlobal (GVar (vi, _, _) | GVarDecl (vi, _) + | GFunDecl (_, vi, _) | GFun ({svar = vi }, _)) -> Some vi + | _ -> None + +let typ_of_localizable = function + | PLval (_, _, lval) -> Some (Cil.typeOfLval lval) + | PExp (_, _, expr) -> Some (Cil.typeOf expr) + | _ -> None let loc_of_localizable = function | PStmt (_,st) | PStmtStart(_,st) diff --git a/src/kernel_services/ast_printing/printer_tag.mli b/src/kernel_services/ast_printing/printer_tag.mli index 074ec5c58ef05e47a804294db77e5b7fd1903f03..1986b69ae802643e3e16023b539a410c5d3a0e88 100644 --- a/src/kernel_services/ast_printing/printer_tag.mli +++ b/src/kernel_services/ast_printing/printer_tag.mli @@ -46,6 +46,7 @@ module Localizable: Datatype.S_with_collections with type t = localizable val kf_of_localizable : localizable -> kernel_function option val ki_of_localizable : localizable -> kinstr val varinfo_of_localizable : localizable -> varinfo option +val typ_of_localizable: localizable -> typ option val loc_of_localizable : localizable -> location (** Might return [Location.unknown] *) diff --git a/src/kernel_services/ast_queries/cil_datatype.mli b/src/kernel_services/ast_queries/cil_datatype.mli index f83bc30afaad8b70869486bbfaa1ba8c473877fa..460f01de5cb0fe57a65b3123cf6e77d965a652c1 100644 --- a/src/kernel_services/ast_queries/cil_datatype.mli +++ b/src/kernel_services/ast_queries/cil_datatype.mli @@ -215,7 +215,7 @@ module TypNoUnroll: S_with_collections_pretty with type t = typ module Typeinfo: S_with_collections with type t = typeinfo -module Varinfo_Id: Hptmap.Id_Datatype +module Varinfo_Id: Hptmap.Id_Datatype with type t = varinfo (** @plugin development guide *) module Varinfo: sig diff --git a/src/plugins/server/data.ml b/src/plugins/server/data.ml index fc2c301f10a7b9767efac7374f0807850a768a66..c575f3a26c7762ba76abe9854b6a7b1f845eab2d 100644 --- a/src/plugins/server/data.ml +++ b/src/plugins/server/data.ml @@ -632,10 +632,9 @@ module type IdentifiedType = sig type t val id : t -> int - include Info end -module Identified(A : IdentifiedType) : Index with type t = A.t = +module Identified(A : IdentifiedType)(I : Info) : Index with type t = A.t = struct type index = (int,A.t) Hashtbl.t @@ -646,13 +645,13 @@ struct type t = index include Datatype.Undefined let reprs = [Hashtbl.create 0] - let name = "Server.Data.Identified.Type." ^ A.name + let name = "Server.Data.Identified.Type." ^ I.name let mem_project = Datatype.never_any_project end) module STATE = State_builder.Ref(TYPE) (struct - let name = "Server.Data.Identified.State." ^ A.name + let name = "Server.Data.Identified.State." ^ I.name let dependencies = [] let default () = Hashtbl.create 0 end) @@ -666,12 +665,12 @@ struct include Collection (struct type t = A.t - let syntax = publish_id (module A) + let syntax = publish_id (module I) let to_json a = `Int (get a) let of_json js = let k = Ju.to_int js in try find k - with Not_found -> failure "[%s] No registered id #%d" A.name k + with Not_found -> failure "[%s] No registered id #%d" I.name k end) end diff --git a/src/plugins/server/data.mli b/src/plugins/server/data.mli index bac87bebda5ca38ee8299dfe2e19a7d2e9767853..f10bf45b1714a36b59dca6ee225199845e964281 100644 --- a/src/plugins/server/data.mli +++ b/src/plugins/server/data.mli @@ -321,11 +321,10 @@ module type IdentifiedType = sig type t val id : t -> int - include Info end (** Builds a {i projectified} index on types with {i unique} identifiers. *) -module Identified(A : IdentifiedType) : Index with type t = A.t +module Identified(A : IdentifiedType)(I:Info) : Index with type t = A.t (* -------------------------------------------------------------------------- *) (** {2 Error handling} diff --git a/src/plugins/server/kernel_ast.ml b/src/plugins/server/kernel_ast.ml index 21f6d0abeb19a78e6d158aa8886e7d510c3a1a5a..db4e8cd38e87e83a7f2827cae0561ac572f7808d 100644 --- a/src/plugins/server/kernel_ast.ml +++ b/src/plugins/server/kernel_ast.ml @@ -41,6 +41,41 @@ let () = Request.register ~page (* --- Printers --- *) (* -------------------------------------------------------------------------- *) +(* The kind of a marker. *) +module MarkerKind = struct + let t = + Enum.dictionary ~page ~name:"markerkind" ~title:"Marker kind" + ~descr:(Md.plain "Marker kind") () + + let kind name = Enum.tag t ~name ~descr:(Md.plain name) () + let expr = kind "expression" + let lval = kind "lvalue" + let var = kind "variable" + let fct = kind "function" + let decl = kind "declaration" + let stmt = kind "statement" + let glob = kind "global" + let term = kind "term" + let prop = kind "property" + + let tag = + let open Printer_tag in + function + | PStmt _ -> stmt + | PStmtStart _ -> stmt + | PVDecl _ -> decl + | PLval (_, _, (Var vi, NoOffset)) -> + if Cil.isFunctionType vi.vtype then fct else var + | PLval _ -> lval + | PExp _ -> expr + | PTermLval _ -> term + | PGlobal _ -> glob + | PIP _ -> prop + + let data = Enum.publish t ~tag () + include (val data : S with type t = Printer_tag.localizable) +end + module Marker = struct @@ -75,6 +110,39 @@ struct let default = index end) + let get_name = function + | PLval (_, _, (Var vi, NoOffset)) -> Some vi.vname + | PLval (_, _, lval) -> Some (Format.asprintf "%a" Printer.pp_lval lval) + | PExp (_, _, expr) -> Some (Format.asprintf "%a" Printer.pp_exp expr) + | PStmt _ | PStmtStart _ | PVDecl _ + | PTermLval _ | PGlobal _| PIP _ -> None + + let iter f = + Localizable.Hashtbl.iter (fun key str -> f (key, str)) (STATE.get ()).tags + + let array = + let model = States.model () in + let () = + States.column ~model + ~name:"kind" ~descr:(Md.plain "Marker kind") + ~data:(module MarkerKind) ~get:fst () + in + let () = + States.column ~model + ~name:"name" + ~descr:(Md.plain "Marker identifier for the end-user, if any") + ~data:(module Jstring.Joption) + ~get:(fun (tag, _) -> get_name tag) + () + in + States.register_array + ~page + ~name:"kernel.ast.markerKind" + ~descr:(Md.plain "Kind of markers") + ~key:snd + ~iter + model + let create_tag = function | PStmt(_,s) -> Printf.sprintf "#s%d" s.sid | PStmtStart(_,s) -> Printf.sprintf "#k%d" s.sid @@ -92,6 +160,7 @@ struct let tag = create_tag loc in Localizable.Hashtbl.add tags loc tag ; Hashtbl.add locs tag loc ; + States.update array (loc, tag); tag let lookup tag = Hashtbl.find (STATE.get()).locs tag @@ -177,6 +246,76 @@ let () = Request.register ~page ~input:(module Kf) ~output:(module Jtext) (fun kf -> Jbuffer.to_json Printer.pp_global (Kernel_function.get_global kf)) +(* -------------------------------------------------------------------------- *) +(* --- Information --- *) +(* -------------------------------------------------------------------------- *) + +module Info = struct + open Printer_tag + + let print_function fmt name = + let stag = Transitioning.Format.stag_of_string name in + Transitioning.Format.pp_open_stag fmt stag; + Format.pp_print_string fmt name; + Transitioning.Format.pp_close_stag fmt () + + let print_kf fmt kf = print_function fmt (Kernel_function.get_name kf) + + let print_variable fmt vi = + Format.fprintf fmt "Variable %s has type %a.@." + vi.vname Printer.pp_typ vi.vtype; + let kf = Kernel_function.find_defining_kf vi in + let pp_kf fmt kf = Format.fprintf fmt " of function %a" print_kf kf in + Format.fprintf fmt "It is a %s variable%a.@." + (if vi.vglob then "global" else if vi.vformal then "formal" else "local") + (Transitioning.Format.pp_print_option pp_kf) kf; + if vi.vtemp then + Format.fprintf fmt "This is a temporary variable%s.@." + (match vi.vdescr with None -> "" | Some descr -> " for " ^ descr); + Format.fprintf fmt "It is %sreferenced and its address is %staken." + (if vi.vreferenced then "" else "not ") + (if vi.vaddrof then "" else "not ") + + let print_varinfo fmt vi = + if Cil.isFunctionType vi.vtype + then + Format.fprintf fmt "%a is a C function of type '%a'." + print_function vi.vname Printer.pp_typ vi.vtype + else print_variable fmt vi + + let print_lvalue fmt _loc = function + | Var vi, NoOffset -> print_varinfo fmt vi + | lval -> + Format.fprintf fmt "This is an lvalue of type %a." + Printer.pp_typ (Cil.typeOfLval lval) + + let print_localizable fmt = function + | PExp (_, _, e) -> + Format.fprintf fmt "This is a pure C expression of type %a." + Printer.pp_typ (Cil.typeOf e) + | PLval (_, _, lval) as loc -> print_lvalue fmt loc lval + | PVDecl (_, _, vi) -> + Format.fprintf fmt "This is the declaration of variable %a.@.@." + Printer.pp_varinfo vi; + print_varinfo fmt vi + | PStmt (kf, _) | PStmtStart (kf, _) -> + Format.fprintf fmt "This is a statement of function %a." print_kf kf + | _ -> () + + let get_marker_info loc = + let buffer = Jbuffer.create () in + let fmt = Jbuffer.formatter buffer in + print_localizable fmt loc; + Format.pp_print_flush fmt (); + Jbuffer.contents buffer +end + +let () = Request.register ~page + ~kind:`GET ~name:"kernel.ast.info" + ~descr:(Md.plain "Get textual information about a marker") + ~input:(module Marker) ~output:(module Jtext) + Info.get_marker_info + (* -------------------------------------------------------------------------- *) (* --- Files --- *) (* -------------------------------------------------------------------------- *)