diff --git a/ivette/src/frama-c/kernel/Messages.tsx b/ivette/src/frama-c/kernel/Messages.tsx index b3d26cfc80b4f02920fd83fdfda051e182e6f77a..d635b8fa23449d2a3661fc008177daf3b796ff1c 100644 --- a/ivette/src/frama-c/kernel/Messages.tsx +++ b/ivette/src/frama-c/kernel/Messages.tsx @@ -81,6 +81,7 @@ const kindFilter: KindFilter = { messages. They are all shown by default. */ const pluginFilter: PluginFilter = { 'aorai': true, + 'cg': true, 'dive': true, 'e-acsl': true, 'eva': true, diff --git a/ivette/src/frama-c/plugins/callgraph/api/index.ts b/ivette/src/frama-c/plugins/callgraph/api/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..e231a66fbbae3bb315cd9a85a06f47096f0311db --- /dev/null +++ b/ivette/src/frama-c/plugins/callgraph/api/index.ts @@ -0,0 +1,217 @@ +/* ************************************************************************ */ +/* */ +/* This file is part of Frama-C. */ +/* */ +/* Copyright (C) 2007-2023 */ +/* CEA (Commissariat à l'énergie atomique et aux énergies */ +/* alternatives) */ +/* */ +/* you can redistribute it and/or modify it under the terms of the GNU */ +/* Lesser General Public License as published by the Free Software */ +/* Foundation, version 2.1. */ +/* */ +/* It is distributed in the hope that it will be useful, */ +/* but WITHOUT ANY WARRANTY; without even the implied warranty of */ +/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ +/* GNU Lesser General Public License for more details. */ +/* */ +/* See the GNU Lesser General Public License version 2.1 */ +/* for more details (enclosed in the file licenses/LGPLv2.1). */ +/* */ +/* ************************************************************************ */ + +/* --- Generated Frama-C Server API --- */ + +/** + Callgraph + @packageDocumentation + @module frama-c/plugins/callgraph/api +*/ + +//@ts-ignore +import * as Json from 'dome/data/json'; +//@ts-ignore +import * as Compare from 'dome/data/compare'; +//@ts-ignore +import * as Server from 'frama-c/server'; +//@ts-ignore +import * as State from 'frama-c/states'; + +//@ts-ignore +import { byFct } from 'frama-c/kernel/api/ast'; +//@ts-ignore +import { fct } from 'frama-c/kernel/api/ast'; +//@ts-ignore +import { fctDefault } from 'frama-c/kernel/api/ast'; +//@ts-ignore +import { jFct } from 'frama-c/kernel/api/ast'; +//@ts-ignore +import { byTag } from 'frama-c/kernel/api/data'; +//@ts-ignore +import { jTag } from 'frama-c/kernel/api/data'; +//@ts-ignore +import { tag } from 'frama-c/kernel/api/data'; +//@ts-ignore +import { tagDefault } from 'frama-c/kernel/api/data'; + +export interface vertex { + /** The function represented by the node */ + kf: fct; + /** whether this node is the root of a service */ + is_root: boolean; + /** the root of this node's service */ + root: fct; +} + +/** Decoder for `vertex` */ +export const jVertex: Json.Decoder<vertex> = + Json.jObject({ kf: jFct, is_root: Json.jBoolean, root: jFct,}); + +/** Natural order for `vertex` */ +export const byVertex: Compare.Order<vertex> = + Compare.byFields + <{ kf: fct, is_root: boolean, root: fct }>({ + kf: byFct, + is_root: Compare.boolean, + root: byFct, + }); + +/** Default value for `vertex` */ +export const vertexDefault: vertex = + { kf: fctDefault, is_root: false, root: fctDefault }; + +/** Whether the call goes through services or not */ +export enum edgeKind { + /** a call between two services */ + inter_services = 'inter_services', + /** a call inside a service */ + inter_functions = 'inter_functions', + /** both cases above */ + both = 'both', +} + +/** Decoder for `edgeKind` */ +export const jEdgeKind: Json.Decoder<edgeKind> = Json.jEnum(edgeKind); + +/** Natural order for `edgeKind` */ +export const byEdgeKind: Compare.Order<edgeKind> = Compare.byEnum(edgeKind); + +/** Default value for `edgeKind` */ +export const edgeKindDefault: edgeKind = edgeKind.inter_services; + +const edgeKindTags_internal: Server.GetRequest<null,tag[]> = { + kind: Server.RqKind.GET, + name: 'plugins.callgraph.edgeKindTags', + input: Json.jNull, + output: Json.jArray(jTag), + signals: [], +}; +/** Registered tags for the above type. */ +export const edgeKindTags: Server.GetRequest<null,tag[]>= edgeKindTags_internal; + +export interface edge { + /** src */ + src: fct; + /** dst */ + dst: fct; + /** kind */ + kind: edgeKind; +} + +/** Decoder for `edge` */ +export const jEdge: Json.Decoder<edge> = + Json.jObject({ src: jFct, dst: jFct, kind: jEdgeKind,}); + +/** Natural order for `edge` */ +export const byEdge: Compare.Order<edge> = + Compare.byFields + <{ src: fct, dst: fct, kind: edgeKind }>({ + src: byFct, + dst: byFct, + kind: byEdgeKind, + }); + +/** Default value for `edge` */ +export const edgeDefault: edge = + { src: fctDefault, dst: fctDefault, kind: edgeKindDefault }; + +/** The callgraph of the current project */ +export interface graph { + /** vertices */ + vertices: vertex[]; + /** edges */ + edges: edge[]; +} + +/** Decoder for `graph` */ +export const jGraph: Json.Decoder<graph> = + Json.jObject({ vertices: Json.jArray(jVertex), edges: Json.jArray(jEdge),}); + +/** Natural order for `graph` */ +export const byGraph: Compare.Order<graph> = + Compare.byFields + <{ vertices: vertex[], edges: edge[] }>({ + vertices: Compare.array(byVertex), + edges: Compare.array(byEdge), + }); + +/** Default value for `graph` */ +export const graphDefault: graph = { vertices: [], edges: [] }; + +/** Signal for state [`callgraph`](#callgraph) */ +export const signalCallgraph: Server.Signal = { + name: 'plugins.callgraph.signalCallgraph', +}; + +const getCallgraph_internal: Server.GetRequest<null,graph | undefined> = { + kind: Server.RqKind.GET, + name: 'plugins.callgraph.getCallgraph', + input: Json.jNull, + output: Json.jOption(jGraph), + signals: [], +}; +/** Getter for state [`callgraph`](#callgraph) */ +export const getCallgraph: Server.GetRequest<null,graph | undefined>= getCallgraph_internal; + +const callgraph_internal: State.Value<graph | undefined> = { + name: 'plugins.callgraph.callgraph', + signal: signalCallgraph, + getter: getCallgraph, +}; +/** The current callgraph or an empty graph if it has not been computed yet */ +export const callgraph: State.Value<graph | undefined> = callgraph_internal; + +/** Signal for state [`isComputed`](#iscomputed) */ +export const signalIsComputed: Server.Signal = { + name: 'plugins.callgraph.signalIsComputed', +}; + +const getIsComputed_internal: Server.GetRequest<null,boolean> = { + kind: Server.RqKind.GET, + name: 'plugins.callgraph.getIsComputed', + input: Json.jNull, + output: Json.jBoolean, + signals: [], +}; +/** Getter for state [`isComputed`](#iscomputed) */ +export const getIsComputed: Server.GetRequest<null,boolean>= getIsComputed_internal; + +const isComputed_internal: State.Value<boolean> = { + name: 'plugins.callgraph.isComputed', + signal: signalIsComputed, + getter: getIsComputed, +}; +/** This boolean is true if the graph has been computed */ +export const isComputed: State.Value<boolean> = isComputed_internal; + +const compute_internal: Server.ExecRequest<null,null> = { + kind: Server.RqKind.EXEC, + name: 'plugins.callgraph.compute', + input: Json.jNull, + output: Json.jNull, + signals: [], +}; +/** Compute the callgraph for the current project */ +export const compute: Server.ExecRequest<null,null>= compute_internal; + +/* ------------------------------------- */ diff --git a/ivette/src/frama-c/plugins/callgraph/callgraph.css b/ivette/src/frama-c/plugins/callgraph/callgraph.css new file mode 100644 index 0000000000000000000000000000000000000000..4360e1461c2db03e11e4e3d205ce09e17ea74809 --- /dev/null +++ b/ivette/src/frama-c/plugins/callgraph/callgraph.css @@ -0,0 +1,6 @@ +.callgraph-computing { + max-width: 90%; + max-height: 200px; + fill: var(--info-text-discrete); + margin: auto; +} diff --git a/ivette/src/frama-c/plugins/callgraph/graph-style.json b/ivette/src/frama-c/plugins/callgraph/graph-style.json new file mode 100644 index 0000000000000000000000000000000000000000..e1aff869e939ed8a04bbc64618ede99ce4e19db3 --- /dev/null +++ b/ivette/src/frama-c/plugins/callgraph/graph-style.json @@ -0,0 +1,42 @@ +[ + { + "selector": "node", + "style": { + "shape": "round-rectangle", + "background-color": "#666", + "label": "data(id)", + "color": "white", + "text-outline-width": 2, + "text-outline-color": "#666", + "text-valign" : "center", + "padding" : "6px", + "border-width": 1, + "text-wrap" : "wrap" + } + }, + { + "selector": "node[?is_root]", + "style": { + "border-width": "2px" + } + }, + { + "selector": "edge", + "style": { + "width": 2, + "line-color": "#888", + "curve-style": "bezier", + "target-arrow-shape": "vee", + "target-arrow-color": "#888", + "arrow-scale": 2.0 + } + }, + { + "selector": ".callstack-selected", + "style": { + "overlay-color": "#8bf", + "overlay-padding": "8px", + "overlay-opacity": 0.4 + } + } +] diff --git a/ivette/src/frama-c/plugins/callgraph/index.tsx b/ivette/src/frama-c/plugins/callgraph/index.tsx new file mode 100644 index 0000000000000000000000000000000000000000..e0b1861ef05335fefc5bed6d61b246789f732162 --- /dev/null +++ b/ivette/src/frama-c/plugins/callgraph/index.tsx @@ -0,0 +1,210 @@ +/* ************************************************************************ */ +/* */ +/* This file is part of Frama-C. */ +/* */ +/* Copyright (C) 2007-2023 */ +/* CEA (Commissariat à l'énergie atomique et aux énergies */ +/* alternatives) */ +/* */ +/* you can redistribute it and/or modify it under the terms of the GNU */ +/* Lesser General Public License as published by the Free Software */ +/* Foundation, version 2.1. */ +/* */ +/* It is distributed in the hope that it will be useful, */ +/* but WITHOUT ANY WARRANTY; without even the implied warranty of */ +/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ +/* GNU Lesser General Public License for more details. */ +/* */ +/* See the GNU Lesser General Public License version 2.1 */ +/* for more details (enclosed in the file licenses/LGPLv2.1). */ +/* */ +/* ************************************************************************ */ + +import React, { useEffect, useState } from 'react'; +import _ from 'lodash'; +import * as Ivette from 'ivette'; +import * as Server from 'frama-c/server'; + +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 Cy from 'cytoscape'; +import CytoscapeComponent from 'react-cytoscapejs'; +import 'frama-c/plugins/dive/cytoscape_libs'; +import 'cytoscape-panzoom/cytoscape.js-panzoom.css'; +import style from './graph-style.json'; + +import { useGlobalState } from 'dome/data/states'; +import { useRequest, useSelection, useSyncValue } from 'frama-c/states'; + +import gearsIcon from 'frama-c/plugins/eva/images/gears.svg'; +import { CallstackState } from 'frama-c/plugins/eva/valuetable'; + +import './callgraph.css'; + + +// -------------------------------------------------------------------------- +// --- Nodes label measurement +// -------------------------------------------------------------------------- + +/* eslint-disable @typescript-eslint/no-explicit-any */ +function getWidth(node: any): string { + const padding = 10; + const min = 50; + const canvas = document.querySelector('canvas[data-id="layer2-node"]'); + if (canvas instanceof HTMLCanvasElement) { + const context = canvas.getContext('2d'); + if (context) { + const fStyle = node.pstyle('font-style').strValue; + const weight = node.pstyle('font-weight').strValue; + const size = node.pstyle('font-size').pfValue; + const family = node.pstyle('font-family').strValue; + context.font = `${fStyle} ${weight} ${size}px ${family}`; + const width = context.measureText(node.data('id')).width; + return `${Math.max(min, width + padding)}px`; + } + } + return `${min}px`; +} +/* eslint-enable @typescript-eslint/no-explicit-any */ + +(style as unknown[]).push({ + selector: 'node', + style: { width: getWidth } + }); + + +// -------------------------------------------------------------------------- +// --- Graph +// -------------------------------------------------------------------------- + +function edgeId(source: AstAPI.fct, target: AstAPI.fct): string { + return `${source}-${target}`; +} + +function convertGraph(graph: CgAPI.graph): object[] { + const elements = []; + for (const v of graph.vertices) { + elements.push({ data: { ...v, id: v.kf } }); + } + for (const e of graph.edges) { + const id = edgeId(e.src, e.dst); + elements.push({ data: { ...e, id, source: e.src, target: e.dst } }); + } + return elements; +} + +type callstack = { + callee: AstAPI.fct, + caller?: AstAPI.fct, + stmt?: AstAPI.marker, + rank?: number +}[] + +function selectFct(cy: Cy.Core, fct: string | undefined): void { + const className = 'marker-selected'; + cy.$(`.${className}`).removeClass(className); + if (fct) { + cy.$(`node[id='${fct}']`).addClass(className); + } +} + +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 { + const isComputed = useSyncValue(CgAPI.isComputed); + const graph = useSyncValue(CgAPI.callgraph); + const [cy, setCy] = useState<Cy.Core>(); + const [cs] = useGlobalState(CallstackState); + const [selection, setSelection] = useSelection(); + const callstack = useRequest(ValuesAPI.getCallstackInfo, cs); + + const layout = { name: 'cola', nodeSpacing: 32 }; + const computedStyle = getComputedStyle(document.documentElement); + const styleVariables = + { ['code-select']: computedStyle.getPropertyValue("--code-select") }; + + const completeStyle = [ + ...style, + { + "selector": ".marker-selected", + "style": { "background-color": styleVariables['code-select'] } + } + ]; + + // Marker selection + useEffect(() => { + cy && selectFct(cy, selection.current?.fct); + }, [cy, selection]); + + // Callstack selection + useEffect(() => { + cy && selectCallstack(cy, callstack); + }, [cy, callstack]); + + // Click on graph + useEffect(() => { + if (cy) { + cy.off('click'); + cy.on('click', 'node', (event) => { + const fct = event.target.id() as string; + setSelection({ location: { fct } }); + }); + } + }, [cy, setSelection]); + + + if (isComputed === false) { + Server.send(CgAPI.compute, null); + return (<img src={gearsIcon} className="callgraph-computing" />); + } + else if (graph !== undefined) { + return ( + <CytoscapeComponent + elements={convertGraph(graph)} + stylesheet={completeStyle} + cy={setCy} + layout={layout} + style={{ width: '100%', height: '100%' }} + />); + } + else { + return (<></>); + } +} + + +// -------------------------------------------------------------------------- +// --- Ivette Component +// -------------------------------------------------------------------------- + +function CallgraphComponent(): JSX.Element { + // Component + return ( + <> + <Callgraph /> + </> + ); +} + + +Ivette.registerComponent({ + id: 'frama-c.plugins.callgraph', + label: 'Call Graph', + group: 'frama-c.plugins', + rank: 3, + title: + 'Display a graph showing calls between functions.', + children: <CallgraphComponent />, +}); diff --git a/ivette/src/frama-c/plugins/callgraph/pkg.json b/ivette/src/frama-c/plugins/callgraph/pkg.json new file mode 100644 index 0000000000000000000000000000000000000000..01be594c9eea276d40d1967201e4675ea3cdc1f8 --- /dev/null +++ b/ivette/src/frama-c/plugins/callgraph/pkg.json @@ -0,0 +1,3 @@ +{ + "name": "Frama-C/Callgraph" +} diff --git a/ivette/src/frama-c/plugins/eva/valuetable.tsx b/ivette/src/frama-c/plugins/eva/valuetable.tsx index 2b2490d51ad12a8a60b1140347a359400b40d4b9..95a83acbc7044e37a0de10da29d3254b55f1f7ab 100644 --- a/ivette/src/frama-c/plugins/eva/valuetable.tsx +++ b/ivette/src/frama-c/plugins/eva/valuetable.tsx @@ -972,7 +972,7 @@ function useEvaluationMode(props: EvaluationModeProps): void { /* -------------------------------------------------------------------------- */ /* 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 FocusState = new GlobalState<Probe | undefined>(undefined); diff --git a/src/plugins/callgraph/callgraph_api.ml b/src/plugins/callgraph/callgraph_api.ml index 712ccd11aed0a5ecd49c658f5e3973f8574bcb6b..8ae2e7c7fca02cb0f65f6ff832b96ecad84799a3 100644 --- a/src/plugins/callgraph/callgraph_api.ml +++ b/src/plugins/callgraph/callgraph_api.ml @@ -43,6 +43,9 @@ module type Graph = sig val is_computed: unit -> bool (** 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 end @@ -60,7 +63,6 @@ module type Services = sig val entry_point: unit -> G.V.t option val is_root: Kernel_function.t -> bool - end (* diff --git a/src/plugins/callgraph/cg.ml b/src/plugins/callgraph/cg.ml index 24c12d9a2ea69474d83e03035708ec5161c69154..1d0816867404ecbe6f0b4d0135e33702d3e57f56 100644 --- a/src/plugins/callgraph/cg.ml +++ b/src/plugins/callgraph/cg.ml @@ -66,8 +66,11 @@ module State = let dependencies = [ Eva.Analysis.self; Globals.Functions.self ] end) +module StateHook = Hook.Build (D) + let self = State.self let is_computed () = State.is_computed () +let add_hook = StateHook.extend (** @return the list of functions which address is taken.*) let get_pointed_kfs = @@ -101,7 +104,6 @@ let get_pointed_kfs = match !res with | None -> let l = compute () in - State.mark_as_computed (); res := Some l; l | Some l -> l @@ -232,6 +234,8 @@ let compute () = semantic_compute g end else (if Eva.Analysis.is_computed () then semantic_compute else syntactic_compute) g; + State.mark_as_computed (); + StateHook.apply g; g let get () = State.memo compute diff --git a/src/plugins/callgraph/requests.ml b/src/plugins/callgraph/requests.ml new file mode 100644 index 0000000000000000000000000000000000000000..cdef7f503d12adab16126d22d094cecf24057518 --- /dev/null +++ b/src/plugins/callgraph/requests.ml @@ -0,0 +1,179 @@ +(**************************************************************************) +(* *) +(* This file is part of Frama-C. *) +(* *) +(* Copyright (C) 2007-2023 *) +(* CEA (Commissariat à l'énergie atomique et aux énergies *) +(* alternatives) *) +(* *) +(* you can redistribute it and/or modify it under the terms of the GNU *) +(* Lesser General Public License as published by the Free Software *) +(* Foundation, version 2.1. *) +(* *) +(* It is distributed in the hope that it will be useful, *) +(* but WITHOUT ANY WARRANTY; without even the implied warranty of *) +(* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *) +(* GNU Lesser General Public License for more details. *) +(* *) +(* See the GNU Lesser General Public License version 2.1 *) +(* for more details (enclosed in the file licenses/LGPLv2.1). *) +(* *) +(**************************************************************************) + +open Server + +module G = Services.G + +(* --- Package declaration --- *) + +let package = Package.package ~plugin:"callgraph" ~title:"Callgraph" () + + +(* --- Helper modules --- *) + +module Record () = +struct + module Record = Data.Record + type record + let record : record Record.signature = Record.signature () + let field name ?(descr = name) = + Record.field record ~name ~descr:(Markdown.plain descr) + let publish ?descr name = + let descr = Option.map Markdown.plain descr in + Record.publish record ~package ~name ?descr +end + +module Enum (X: sig type t end) = +struct + module Enum = Data.Enum + let dictionary: X.t Enum.dictionary = Enum.dictionary () + let tag name descr = Enum.tag ~name ~descr:(Markdown.plain descr) dictionary + let publish lookup name descr = + Enum.set_lookup dictionary lookup; + Request.dictionary ~package ~name ~descr:(Markdown.plain descr) dictionary +end + + +(* --- Types --- *) + +module Vertex = +struct + include Record () + + let kf = field "kf" (module Kernel_ast.Function) + ~descr: "The function represented by the node" + 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") + type t = Cil_types.kernel_function Service_graph.vertex + + let to_json (v : Cil_types.kernel_function Service_graph.vertex) = + default |> + set kf v.node |> + set is_root v.is_root |> + set root v.root.node |> + to_json + + let of_json _js = Data.failure "Vertex.of_json not implemented" +end + +module EdgeKind = +struct + include Enum (struct type t = Service_graph.edge end) + + let inter_services = tag "inter_services" "a call between two services" + let inter_functions = tag "inter_functions" "a call inside a service" + let both = tag "both" "both cases above" + + let lookup = function + | Service_graph.Inter_services -> inter_services + | Inter_functions -> inter_functions + | Both -> both + + include (val publish lookup + "edgeKind" "Whether the call goes through services or not") +end + +module Edge = +struct + include Record () + + let src = field "src" (module Kernel_ast.Function) + let dst = field "dst" (module Kernel_ast.Function) + let kind = field "kind" (module EdgeKind) + + include (val publish "edge") + type t = G.E.t + + let to_json (e : t) = + default |> + set src (G.E.src e).node |> + set dst (G.E.dst e).node |> + set kind (G.E.label e) |> + to_json + + let of_json _js = Data.failure "Edge.of_json not implemented" +end + +module Graph = +struct + include Record () + + let vertices = field "vertices" (module Data.Jlist (Vertex)) + let edges = field "edges" (module Data.Jlist (Edge)) + + include (val publish "graph" ~descr:"The callgraph of the current project") + type t = G.t + + let get_vertices (g : t) = + G.fold_vertex (fun v acc -> v :: acc ) g [] + + let get_edges (g : t) = + G.fold_edges_e (fun v acc -> v :: acc ) g [] + + let to_json (g : t) = + default |> + set vertices (get_vertices g) |> + set edges (get_edges g) |> + to_json + + let of_json _js = Data.failure "Graph.of_json not implemented" +end + + +(* --- Requests --- *) + +let _signal = + States.register_value + ~package ~name:"callgraph" + ~descr:(Markdown.plain + "The current callgraph or an empty graph if it has not been computed yet") + ~output:(module Data.Joption (Graph)) + ~add_hook:Services.add_hook + ~get: + begin fun () -> + if Services.is_computed () then + Some (Services.get ()) + else + None + end + () + +let _signal = + States.register_value + ~package ~name:"isComputed" + ~descr:(Markdown.plain + "This boolean is true if the graph has been computed") + ~output:(module Data.Jbool) + ~add_hook:Services.add_hook + ~get:Services.is_computed + () + +let () = Request.register ~package + ~kind:`EXEC ~name:"compute" + ~descr:(Markdown.plain "Compute the callgraph for the current project") + ~input:(module Data.Junit) ~output:(module Data.Junit) + (fun () -> ignore (Services.get ())) diff --git a/src/plugins/callgraph/requests.mli b/src/plugins/callgraph/requests.mli new file mode 100644 index 0000000000000000000000000000000000000000..d68abd32119990046a8bed6a36cfb5e6c3b8c264 --- /dev/null +++ b/src/plugins/callgraph/requests.mli @@ -0,0 +1,23 @@ +(**************************************************************************) +(* *) +(* This file is part of Frama-C. *) +(* *) +(* Copyright (C) 2007-2023 *) +(* CEA (Commissariat à l'énergie atomique et aux énergies *) +(* alternatives) *) +(* *) +(* you can redistribute it and/or modify it under the terms of the GNU *) +(* Lesser General Public License as published by the Free Software *) +(* Foundation, version 2.1. *) +(* *) +(* It is distributed in the hope that it will be useful, *) +(* but WITHOUT ANY WARRANTY; without even the implied warranty of *) +(* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *) +(* GNU Lesser General Public License for more details. *) +(* *) +(* See the GNU Lesser General Public License version 2.1 *) +(* for more details (enclosed in the file licenses/LGPLv2.1). *) +(* *) +(**************************************************************************) + +(* This module only register requests for Frama-C Server. Nothing is exported *) diff --git a/src/plugins/callgraph/services.ml b/src/plugins/callgraph/services.ml index b2d3f2893fbb6f37313b3472a20777c936386867..1345b6fcdf985cdd78322a2a91fa205a253dc58f 100644 --- a/src/plugins/callgraph/services.ml +++ b/src/plugins/callgraph/services.ml @@ -77,9 +77,12 @@ module State = let dependencies = [ Cg.self; Kernel.MainFunction.self ] end) +module StateHook = Hook.Build (S.Service_graph.Datatype) + (* eta-expansion required to mask optional argument [?project] *) let is_computed () = State.is_computed () let self = State.self +let add_hook = StateHook.extend let compute () = let cg = Cg.get () in @@ -92,6 +95,7 @@ let compute () = in let sg = S.compute cg isr_names in State.mark_as_computed (); + StateHook.apply sg; sg let get () = State.memo compute