Skip to content
Snippets Groups Projects
Commit e6e0cdc8 authored by Valentin Perrelle's avatar Valentin Perrelle Committed by David Bühler
Browse files

[Ivette] Displays the focused callstack

parent 337637f6
No related branches found
No related tags found
No related merge requests found
...@@ -55,34 +55,38 @@ import { tag } from 'frama-c/kernel/api/data'; ...@@ -55,34 +55,38 @@ import { tag } from 'frama-c/kernel/api/data';
import { tagDefault } from 'frama-c/kernel/api/data'; import { tagDefault } from 'frama-c/kernel/api/data';
export interface vertex { export interface vertex {
/** kf */ /** The function represented by the node */
kf: fct; kf: fct;
/** is_root */ /** whether this node is the root of a service */
is_root: boolean; is_root: boolean;
/** the root of this node's service */
root: fct;
} }
/** Decoder for `vertex` */ /** Decoder for `vertex` */
export const jVertex: Json.Decoder<vertex> = export const jVertex: Json.Decoder<vertex> =
Json.jObject({ kf: jFct, is_root: Json.jBoolean,}); Json.jObject({ kf: jFct, is_root: Json.jBoolean, root: jFct,});
/** Natural order for `vertex` */ /** Natural order for `vertex` */
export const byVertex: Compare.Order<vertex> = export const byVertex: Compare.Order<vertex> =
Compare.byFields Compare.byFields
<{ kf: fct, is_root: boolean }>({ <{ kf: fct, is_root: boolean, root: fct }>({
kf: byFct, kf: byFct,
is_root: Compare.boolean, is_root: Compare.boolean,
root: byFct,
}); });
/** Default value for `vertex` */ /** Default value for `vertex` */
export const vertexDefault: vertex = { kf: fctDefault, is_root: false }; export const vertexDefault: vertex =
{ kf: fctDefault, is_root: false, root: fctDefault };
/** Whether The nature of a node */ /** Whether the call goes through services or not */
export enum edgeKind { export enum edgeKind {
/** */ /** a call between two services */
inter_services = 'inter_services', inter_services = 'inter_services',
/** */ /** a call inside a service */
inter_functions = 'inter_functions', inter_functions = 'inter_functions',
/** */ /** both cases above */
both = 'both', both = 'both',
} }
...@@ -137,30 +141,22 @@ export interface graph { ...@@ -137,30 +141,22 @@ export interface graph {
vertices: vertex[]; vertices: vertex[];
/** edges */ /** edges */
edges: edge[]; edges: edge[];
/** entry_point */
entry_point?: fct;
} }
/** Decoder for `graph` */ /** Decoder for `graph` */
export const jGraph: Json.Decoder<graph> = export const jGraph: Json.Decoder<graph> =
Json.jObject({ Json.jObject({ vertices: Json.jArray(jVertex), edges: Json.jArray(jEdge),});
vertices: Json.jArray(jVertex),
edges: Json.jArray(jEdge),
entry_point: Json.jOption(jFct),
});
/** Natural order for `graph` */ /** Natural order for `graph` */
export const byGraph: Compare.Order<graph> = export const byGraph: Compare.Order<graph> =
Compare.byFields Compare.byFields
<{ vertices: vertex[], edges: edge[], entry_point?: fct }>({ <{ vertices: vertex[], edges: edge[] }>({
vertices: Compare.array(byVertex), vertices: Compare.array(byVertex),
edges: Compare.array(byEdge), edges: Compare.array(byEdge),
entry_point: Compare.defined(byFct),
}); });
/** Default value for `graph` */ /** Default value for `graph` */
export const graphDefault: graph = export const graphDefault: graph = { vertices: [], edges: [] };
{ vertices: [], edges: [], entry_point: undefined };
/** Signal for state [`callgraph`](#callgraph) */ /** Signal for state [`callgraph`](#callgraph) */
export const signalCallgraph: Server.Signal = { export const signalCallgraph: Server.Signal = {
......
...@@ -30,5 +30,13 @@ ...@@ -30,5 +30,13 @@
"target-arrow-color": "#888", "target-arrow-color": "#888",
"arrow-scale": 2.0 "arrow-scale": 2.0
} }
},
{
"selector": ".callstack-selected",
"style": {
"overlay-color": "#8bf",
"overlay-padding": "8px",
"overlay-opacity": 0.4
}
} }
] ]
...@@ -20,22 +20,27 @@ ...@@ -20,22 +20,27 @@
/* */ /* */
/* ************************************************************************ */ /* ************************************************************************ */
import React, { useState } from 'react'; import React, { useEffect, useState } from 'react';
import _ from 'lodash'; import _ from 'lodash';
import * as Ivette from 'ivette'; import * as Ivette from 'ivette';
import * as Server from 'frama-c/server'; import * as Server from 'frama-c/server';
import * as API from './api'; import * as AstAPI from 'frama-c/kernel/api/ast';
import * as CgAPI from './api';
import * as ValuesAPI from 'frama-c/plugins/eva/api/values';
import Cytoscape from 'cytoscape'; import Cy from 'cytoscape';
import CytoscapeComponent from 'react-cytoscapejs'; import CytoscapeComponent from 'react-cytoscapejs';
import 'frama-c/plugins/dive/cytoscape_libs'; import 'frama-c/plugins/dive/cytoscape_libs';
import 'cytoscape-panzoom/cytoscape.js-panzoom.css'; import 'cytoscape-panzoom/cytoscape.js-panzoom.css';
import style from './graph-style.json'; import style from './graph-style.json';
import { useSyncValue } from 'frama-c/states';
import { useGlobalState } from 'dome/data/states';
import { useRequest, useSyncValue } from 'frama-c/states';
import gearsIcon from 'frama-c/plugins/eva/images/gears.svg'; import gearsIcon from 'frama-c/plugins/eva/images/gears.svg';
import { CallstackState } from 'frama-c/plugins/eva/valuetable';
import './callgraph.css'; import './callgraph.css';
...@@ -73,27 +78,56 @@ function getWidth(node: any): string { ...@@ -73,27 +78,56 @@ function getWidth(node: any): string {
// --- Graph // --- Graph
// -------------------------------------------------------------------------- // --------------------------------------------------------------------------
function convertGraph(graph: API.graph): object[] { function edgeId(source: AstAPI.fct, target: AstAPI.fct): string {
return `${source}-${target}`;
}
function convertGraph(graph: CgAPI.graph): object[] {
const elements = []; const elements = [];
for (const v of graph.vertices) { for (const v of graph.vertices) {
elements.push({data: {...v, id:v.kf}}); elements.push({data: {...v, id: v.kf}});
} }
for (const e of graph.edges) { for (const e of graph.edges) {
elements.push({data: {source: e.src, target: e.dst}}); const id = edgeId(e.src, e.dst);
elements.push({data: {...e, id, source: e.src, target: e.dst}});
} }
console.log(elements);
return elements; return elements;
} }
type callstack = {
callee: AstAPI.fct,
caller?: AstAPI.fct,
stmt?: AstAPI.marker,
rank?: number
}[]
function selectCallstack(cy: Cy.Core, callstack: callstack | undefined): void {
const className = 'callstack-selected';
cy.$(`.${className}`).removeClass(className);
callstack?.forEach((call) => {
cy.$(`node[id='${call.callee}']`).addClass(className);
if (call.caller) {
const id = edgeId(call.caller, call.callee);
cy.$(`edge[id='${id}']`).addClass(className);
}
});
}
function Callgraph() : JSX.Element { function Callgraph() : JSX.Element {
const isComputed = useSyncValue(API.isComputed); const isComputed = useSyncValue(CgAPI.isComputed);
const graph = useSyncValue(API.callgraph); const graph = useSyncValue(CgAPI.callgraph);
const [cy, setCy] = useState<Cytoscape.Core>(); const [cy, setCy] = useState<Cy.Core>();
const [cs] = useGlobalState(CallstackState);
const callstack = useRequest(ValuesAPI.getCallstackInfo, cs);
const layout = {name: 'cola', nodeSpacing: 32}; const layout = {name: 'cola', nodeSpacing: 32};
useEffect(() => {
cy && selectCallstack(cy, callstack);
}, [cy, callstack]);
if (isComputed === false) { if (isComputed === false) {
Server.send(API.compute, null); Server.send(CgAPI.compute, null);
return (<img src={gearsIcon} className="callgraph-computing" />); return (<img src={gearsIcon} className="callgraph-computing" />);
} }
else if (graph !== undefined) { else if (graph !== undefined) {
......
...@@ -972,7 +972,7 @@ function useEvaluationMode(props: EvaluationModeProps): void { ...@@ -972,7 +972,7 @@ function useEvaluationMode(props: EvaluationModeProps): void {
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
/* Table's state. It is global for when the user changes the view. */ /* Table's state. It is global for when the user changes the view. */
const CallstackState = new GlobalState<callstack>('Summary'); export const CallstackState = new GlobalState<callstack>('Summary');
const FunctionsManagerState = new GlobalState(new FunctionsManager()); const FunctionsManagerState = new GlobalState(new FunctionsManager());
const FocusState = new GlobalState<Probe | undefined>(undefined); const FocusState = new GlobalState<Probe | undefined>(undefined);
......
...@@ -43,6 +43,9 @@ module type Graph = sig ...@@ -43,6 +43,9 @@ module type Graph = sig
val is_computed: unit -> bool val is_computed: unit -> bool
(** Is the graph already built? *) (** Is the graph already built? *)
val add_hook: (G.t -> unit) -> unit
(** Call registered hook each time the graph is computed *)
val self: State.t val self: State.t
end end
...@@ -60,8 +63,6 @@ module type Services = sig ...@@ -60,8 +63,6 @@ module type Services = sig
val entry_point: unit -> G.V.t option val entry_point: unit -> G.V.t option
val is_root: Kernel_function.t -> bool val is_root: Kernel_function.t -> bool
val add_hook: (G.t -> unit) -> unit
end end
(* (*
......
...@@ -66,8 +66,11 @@ module State = ...@@ -66,8 +66,11 @@ module State =
let dependencies = [ Eva.Analysis.self; Globals.Functions.self ] let dependencies = [ Eva.Analysis.self; Globals.Functions.self ]
end) end)
module StateHook = Hook.Build (D)
let self = State.self let self = State.self
let is_computed () = State.is_computed () let is_computed () = State.is_computed ()
let add_hook = StateHook.extend
(** @return the list of functions which address is taken.*) (** @return the list of functions which address is taken.*)
let get_pointed_kfs = let get_pointed_kfs =
...@@ -101,7 +104,6 @@ let get_pointed_kfs = ...@@ -101,7 +104,6 @@ let get_pointed_kfs =
match !res with match !res with
| None -> | None ->
let l = compute () in let l = compute () in
State.mark_as_computed ();
res := Some l; res := Some l;
l l
| Some l -> l | Some l -> l
...@@ -232,6 +234,8 @@ let compute () = ...@@ -232,6 +234,8 @@ let compute () =
semantic_compute g semantic_compute g
end else end else
(if Eva.Analysis.is_computed () then semantic_compute else syntactic_compute) g; (if Eva.Analysis.is_computed () then semantic_compute else syntactic_compute) g;
State.mark_as_computed ();
StateHook.apply g;
g g
let get () = State.memo compute let get () = State.memo compute
......
...@@ -22,8 +22,15 @@ ...@@ -22,8 +22,15 @@
open Server open Server
module G = Services.G
(* --- Package declaration --- *)
let package = Package.package ~plugin:"callgraph" ~title:"Callgraph" () let package = Package.package ~plugin:"callgraph" ~title:"Callgraph" ()
(* --- Helper modules --- *)
module Record () = module Record () =
struct struct
module Record = Data.Record module Record = Data.Record
...@@ -31,14 +38,11 @@ struct ...@@ -31,14 +38,11 @@ struct
let record : record Record.signature = Record.signature () let record : record Record.signature = Record.signature ()
let field name ?(descr = name) = let field name ?(descr = name) =
Record.field record ~name ~descr:(Markdown.plain descr) Record.field record ~name ~descr:(Markdown.plain descr)
let option name ?(descr = name) =
Record.option record ~name ~descr:(Markdown.plain descr)
let publish ?descr name = let publish ?descr name =
let descr = Option.map Markdown.plain descr in let descr = Option.map Markdown.plain descr in
Record.publish record ~package ~name ?descr Record.publish record ~package ~name ?descr
end end
module Enum (X: sig type t end) = module Enum (X: sig type t end) =
struct struct
module Enum = Data.Enum module Enum = Data.Enum
...@@ -49,12 +53,19 @@ struct ...@@ -49,12 +53,19 @@ struct
Request.dictionary ~package ~name ~descr:(Markdown.plain descr) dictionary Request.dictionary ~package ~name ~descr:(Markdown.plain descr) dictionary
end end
(* --- Types --- *)
module Vertex = module Vertex =
struct struct
include Record () include Record ()
let kf = field "kf" (module Kernel_ast.Function) let kf = field "kf" (module Kernel_ast.Function)
~descr: "The function represented by the node"
let is_root = field "is_root" Data.jbool let is_root = field "is_root" Data.jbool
~descr: "whether this node is the root of a service"
let root = field "root" (module Kernel_ast.Function)
~descr: "the root of this node's service"
include (val publish "vertex") include (val publish "vertex")
type t = Cil_types.kernel_function Service_graph.vertex type t = Cil_types.kernel_function Service_graph.vertex
...@@ -63,6 +74,7 @@ struct ...@@ -63,6 +74,7 @@ struct
default |> default |>
set kf v.node |> set kf v.node |>
set is_root v.is_root |> set is_root v.is_root |>
set root v.root.node |>
to_json to_json
let of_json _js = Data.failure "Vertex.of_json not implemented" let of_json _js = Data.failure "Vertex.of_json not implemented"
...@@ -72,16 +84,17 @@ module EdgeKind = ...@@ -72,16 +84,17 @@ module EdgeKind =
struct struct
include Enum (struct type t = Service_graph.edge end) include Enum (struct type t = Service_graph.edge end)
let inter_services = tag "inter_services" "" let inter_services = tag "inter_services" "a call between two services"
let inter_functions = tag "inter_functions" "" let inter_functions = tag "inter_functions" "a call inside a service"
let both = tag "both" "" let both = tag "both" "both cases above"
let lookup = function let lookup = function
| Service_graph.Inter_services -> inter_services | Service_graph.Inter_services -> inter_services
| Inter_functions -> inter_functions | Inter_functions -> inter_functions
| Both -> both | Both -> both
include (val publish lookup "edgeKind" "Whether The nature of a node") include (val publish lookup
"edgeKind" "Whether the call goes through services or not")
end end
module Edge = module Edge =
...@@ -93,13 +106,13 @@ struct ...@@ -93,13 +106,13 @@ struct
let kind = field "kind" (module EdgeKind) let kind = field "kind" (module EdgeKind)
include (val publish "edge") include (val publish "edge")
type t = Services.G.E.t type t = G.E.t
let to_json (e : t) = let to_json (e : t) =
default |> default |>
set src (Services.G.E.src e).node |> set src (G.E.src e).node |>
set dst (Services.G.E.dst e).node |> set dst (G.E.dst e).node |>
set kind (Services.G.E.label e) |> set kind (G.E.label e) |>
to_json to_json
let of_json _js = Data.failure "Edge.of_json not implemented" let of_json _js = Data.failure "Edge.of_json not implemented"
...@@ -111,31 +124,28 @@ struct ...@@ -111,31 +124,28 @@ struct
let vertices = field "vertices" (module Data.Jlist (Vertex)) let vertices = field "vertices" (module Data.Jlist (Vertex))
let edges = field "edges" (module Data.Jlist (Edge)) let edges = field "edges" (module Data.Jlist (Edge))
let entry_point = option "entry_point" (module Kernel_ast.Function)
include (val publish "graph" ~descr:"The callgraph of the current project") include (val publish "graph" ~descr:"The callgraph of the current project")
type t = Services.G.t type t = G.t
let get_vertices (g : t) = let get_vertices (g : t) =
Services.G.fold_vertex (fun v acc -> v :: acc ) g [] G.fold_vertex (fun v acc -> v :: acc ) g []
let get_edges (g : t) = let get_edges (g : t) =
Services.G.fold_edges_e (fun v acc -> v :: acc ) g [] G.fold_edges_e (fun v acc -> v :: acc ) g []
let get_entry_point () =
Services.entry_point () |>
Option.map (fun v -> v.Service_graph.node)
let to_json (g : t) = let to_json (g : t) =
default |> default |>
set vertices (get_vertices g) |> set vertices (get_vertices g) |>
set edges (get_edges g) |> set edges (get_edges g) |>
set entry_point (get_entry_point ()) |>
to_json to_json
let of_json _js = Data.failure "Graph.of_json not implemented" let of_json _js = Data.failure "Graph.of_json not implemented"
end end
(* --- Requests --- *)
let _signal = let _signal =
States.register_value States.register_value
~package ~name:"callgraph" ~package ~name:"callgraph"
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment