diff --git a/Makefile b/Makefile index 9c91110466759f5eb47597511f8bf3d340c9addb..8bd4ad0cf6397d041cdf7844e9b2450d3b6c0c64 100644 --- a/Makefile +++ b/Makefile @@ -827,7 +827,7 @@ $(eval $(call include_generic_plugin_Makefile,$(PLUGIN_NAME))) PLUGIN_ENABLE:=$(ENABLE_EVA) PLUGIN_NAME:=Eva PLUGIN_DIR:=src/plugins/value -PLUGIN_EXTRA_DIRS:=engine values domains domains/cvalue domains/apron \ +PLUGIN_EXTRA_DIRS:=engine values domains api domains/cvalue domains/apron \ domains/gauges domains/equality legacy partitioning utils gui_files \ values/numerors domains/numerors PLUGIN_TESTS_DIRS+=value/traces @@ -910,11 +910,12 @@ PLUGIN_CMO:= partitioning/split_strategy domains/domain_mode value_parameters \ partitioning/partitioning_index partitioning/trace_partitioning \ engine/mem_exec engine/iterator engine/initialization \ engine/compute_functions engine/analysis register \ + api/general_requests \ utils/unit_tests \ $(APRON_CMO) $(NUMERORS_CMO) PLUGIN_CMI:= values/abstract_value values/abstract_location \ domains/abstract_domain domains/simpler_domains -PLUGIN_DEPENDENCIES:=Callgraph LoopAnalysis RteGen +PLUGIN_DEPENDENCIES:=Callgraph LoopAnalysis RteGen Server # These files are used by the GUI, but do not depend on Lablgtk VALUE_GUI_AUX:=gui_files/gui_types gui_files/gui_eval \ diff --git a/headers/header_spec.txt b/headers/header_spec.txt index 0410e84facbd9940996a8d375593a164a05f0622..5d791ad40ee2ce6301155c602be4b5fb34761a11 100644 --- a/headers/header_spec.txt +++ b/headers/header_spec.txt @@ -1208,6 +1208,8 @@ src/plugins/value/Changelog_non_free: .ignore src/plugins/value/Eva.mli: CEA_LGPL_OR_PROPRIETARY src/plugins/value/alarmset.ml: CEA_LGPL_OR_PROPRIETARY src/plugins/value/alarmset.mli: CEA_LGPL_OR_PROPRIETARY +src/plugins/value/api/general_requests.ml: CEA_LGPL_OR_PROPRIETARY +src/plugins/value/api/general_requests.mli: CEA_LGPL_OR_PROPRIETARY src/plugins/value/domains/abstract_domain.mli: CEA_LGPL_OR_PROPRIETARY src/plugins/value/domains/printer_domain.ml: CEA_LGPL_OR_PROPRIETARY src/plugins/value/domains/printer_domain.mli: CEA_LGPL_OR_PROPRIETARY diff --git a/ivette/api/kernel/ast/index.ts b/ivette/api/kernel/ast/index.ts index a5bfaf9032e5e6800aa93703af31ab2e5d9bb0af..623884b14adfa9242c3387102ec72042534bad37 100644 --- a/ivette/api/kernel/ast/index.ts +++ b/ivette/api/kernel/ast/index.ts @@ -43,14 +43,14 @@ export const compute: Server.ExecRequest<null,null>= compute_internal; /** Marker kind */ export enum markerKind { - /** Expression */ - expression = 'expression', - /** Lvalue */ - lvalue = 'lvalue', /** Variable */ variable = 'variable', /** Function */ function = 'function', + /** Expression */ + expression = 'expression', + /** Lvalue */ + lvalue = 'lvalue', /** Declaration */ declaration = 'declaration', /** Statement */ diff --git a/ivette/api/plugins/eva/index.ts b/ivette/api/plugins/eva/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..4b46b34354923641cd17852220a4a7ff037b790f --- /dev/null +++ b/ivette/api/plugins/eva/index.ts @@ -0,0 +1,40 @@ +/* --- Generated Frama-C Server API --- */ + +/** + Eva General Services + @packageDocumentation + @module api/plugins/eva +*/ + +//@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'; + + +const getCallers_internal: Server.GetRequest< + Json.key<'#fct'>, + [ Json.key<'#fct'>, Json.key<'#stmt'> ][] + > = { + kind: Server.RqKind.GET, + name: 'plugins.eva.getCallers', + input: Json.jKey<'#fct'>('#fct'), + output: Json.jList(Json.jTry( + Json.jPair( + Json.jFail(Json.jKey<'#fct'>('#fct'), + '#fct expected'), + Json.jFail(Json.jKey<'#stmt'>('#stmt'), + '#stmt expected'), + ))), +}; +/** Get the list of call site of a function */ +export const getCallers: Server.GetRequest< + Json.key<'#fct'>, + [ Json.key<'#fct'>, Json.key<'#stmt'> ][] + >= getCallers_internal; + +/* ------------------------------------- */ diff --git a/ivette/src/frama-c/states.ts b/ivette/src/frama-c/states.ts index ea1737caee51e08a7610dc7560ffba30cf91c236..72dc636bbeb467e7d156457495eb61d94a8fba75 100644 --- a/ivette/src/frama-c/states.ts +++ b/ivette/src/frama-c/states.ts @@ -475,70 +475,227 @@ export interface FullLocation { * but at least one of the two must be set. */ export type Location = AtLeastOne<FullLocation>; -export interface Selection { - /** Current selection. */ - current?: Location; +export interface HistorySelection { /** Previous locations with respect to the [[current]] one. */ prevSelections: Location[]; /** Next locations with respect to the [[current]] one. */ nextSelections: Location[]; } +/** Actions on history selections: + * - `HISTORY_PREV` jumps to previous history location + * (first in [[prevSelections]]). + * - `HISTORY_NEXT` jumps to next history location + * (first in [[nextSelections]]). + */ +type HistorySelectActions = 'HISTORY_PREV' | 'HISTORY_NEXT'; + +/** A selection of multiple locations. */ +export interface MultipleSelection { + /** The index of the current selected location in [[possibleSelections]]. */ + index: number; + /** All locations forming a multiple selection. */ + allSelections: Location[]; +} + +/** A select action on multiple locations. */ +export interface MultipleSelect { + readonly index: number; + readonly locations: Location[]; +} + +/** Select the [[index]]-nth location of the current multiple selection. */ +export interface NthSelect { + readonly index: number; +} + +/** Actions on multiple selections: + * - [[MultipleSelect]]. + * - [[NthSelect]]. + * - `MULTIPLE_PREV` jumps to previous location of the multiple selections. + * - `MULTIPLE_NEXT` jumps to next location of the multiple selections. + */ +type MultipleSelectActions = + MultipleSelect | NthSelect + | 'MULTIPLE_PREV' | 'MULTIPLE_NEXT' | 'MULTIPLE_CLEAR'; + +export interface Selection { + /** Current selection. May be one in [[history]] or [[multiple]]. */ + current?: Location; + /** History of selections. */ + history: HistorySelection; + /** Multiple selections at once. */ + multiple: MultipleSelection; +} + /** A select action on a location. */ -export interface SelectAction { +export interface SingleSelect { readonly location: Location; } /** Actions on selection: - * - [[SelectAction]]. - * - `GO_BACK` jumps to previous location (first in [[prevSelections]]). - * - `GO_FORWARD` jumps to next location (first in [[nextSelections]]). + * - [[SingleSelect]]. + * - [[HistorySelectActions]]. + * - [[MultipleSelectActions]]. */ -export type SelectionActions = SelectAction | 'GO_BACK' | 'GO_FORWARD'; +export type SelectionActions = + SingleSelect | HistorySelectActions | MultipleSelectActions; -function isSelect(a: SelectionActions): a is SelectAction { - return (a as SelectAction).location !== undefined; +function isSingleSelect(a: SelectionActions): a is SingleSelect { + return (a as SingleSelect).location !== undefined; } -/** Compute the next selection based on the current one and the given action. */ -function reducer(s: Selection, action: SelectionActions): Selection { - if (isSelect(action)) { - const [prevSelections, nextSelections] = - s.current && s.current.function !== action.location.function ? - [[s.current, ...s.prevSelections], []] : - [s.prevSelections, s.nextSelections]; - return { - current: action.location, - prevSelections, - nextSelections, - }; - } - const [pS, ...prevS] = s.prevSelections; - const [nS, ...nextS] = s.nextSelections; +function isMultipleSelect(a: SelectionActions): a is MultipleSelect { + return ( + (a as MultipleSelect).locations !== undefined && + (a as MultipleSelect).index !== undefined + ); +} + +function isNthSelect(a: SelectionActions): a is NthSelect { + return (a as NthSelect).index !== undefined; +} + +/** Update selection to the given location. */ +function selectLocation(s: Selection, location: Location): Selection { + const [prevSelections, nextSelections] = + s.current && s.current.function !== location.function ? + [[s.current, ...s.history.prevSelections], []] : + [s.history.prevSelections, s.history.nextSelections]; + return { + ...s, + current: location, + history: { prevSelections, nextSelections }, + }; +} + +/** Compute the next selection picking from the current history, depending on + * action. + */ +function fromHistory(s: Selection, action: HistorySelectActions): Selection { switch (action) { - case 'GO_BACK': + case 'HISTORY_PREV': { + const [pS, ...prevS] = s.history.prevSelections; return { + ...s, current: pS, - prevSelections: prevS, - nextSelections: [(s.current as Location), ...s.nextSelections], + history: { + prevSelections: prevS, + nextSelections: + [(s.current as Location), ...s.history.nextSelections], + }, }; - case 'GO_FORWARD': + } + case 'HISTORY_NEXT': { + const [nS, ...nextS] = s.history.nextSelections; return { + ...s, current: nS, - prevSelections: [(s.current as Location), ...s.prevSelections], - nextSelections: nextS, + history: { + prevSelections: + [(s.current as Location), ...s.history.prevSelections], + nextSelections: nextS, + }, + }; + } + default: + return s; + } +} + +/** Compute the next selection picking from the current multiple, depending on + * action. + */ +function fromMultipleSelections( + s: Selection, + action: 'MULTIPLE_PREV' | 'MULTIPLE_NEXT' | 'MULTIPLE_CLEAR', +): Selection { + switch (action) { + case 'MULTIPLE_PREV': + case 'MULTIPLE_NEXT': { + const index = + action === 'MULTIPLE_NEXT' ? + s.multiple.index + 1 : + s.multiple.index - 1; + if (0 <= index && index < s.multiple.allSelections.length) { + const multiple = { ...s.multiple, index }; + return selectLocation( + { ...s, multiple }, + s.multiple.allSelections[index], + ); + } + return s; + } + case 'MULTIPLE_CLEAR': + return { + ...s, + multiple: { + index: 0, + allSelections: [], + }, }; default: return s; } } -const GlobalSelection = new GlobalStates.State<Selection>({ +/** Compute the next selection based on the current one and the given action. */ +function reducer(s: Selection, action: SelectionActions): Selection { + if (isSingleSelect(action)) { + return selectLocation(s, action.location); + } + if (isMultipleSelect(action)) { + if (action.locations.length === 0) + return s; + const index = action.index > 0 ? action.index : 0; + const selection = selectLocation(s, action.locations[index]); + return { + ...selection, + multiple: { + allSelections: action.locations, + index, + }, + }; + } + if (isNthSelect(action)) { + const { index } = action; + if (0 <= index && index < s.multiple.allSelections.length) { + const location = s?.multiple.allSelections[index]; + const selection = selectLocation(s, location); + const multiple = { ...selection.multiple, index }; + return { ...selection, multiple }; + } + return s; + } + switch (action) { + case 'HISTORY_PREV': + case 'HISTORY_NEXT': + return fromHistory(s, action); + case 'MULTIPLE_PREV': + case 'MULTIPLE_NEXT': + case 'MULTIPLE_CLEAR': + return fromMultipleSelections(s, action); + default: + return s; + } +} + +/** The initial selection is empty. */ +const emptySelection = { current: undefined, - prevSelections: [], - nextSelections: [], -}); + history: { + prevSelections: [], + nextSelections: [], + }, + multiple: { + index: 0, + allSelections: [], + }, +}; + +const GlobalSelection = new GlobalStates.State<Selection>(emptySelection); +Server.onShutdown(() => GlobalSelection.setValue(emptySelection)); /** Current selection. diff --git a/ivette/src/renderer/ASTview.tsx b/ivette/src/renderer/ASTview.tsx index a635b1a7869202b426cbf363bf03a009a6194da9..ff2b3d97d473999cd9f6c85a31fdb1524f12ce16 100644 --- a/ivette/src/renderer/ASTview.tsx +++ b/ivette/src/renderer/ASTview.tsx @@ -3,6 +3,7 @@ // -------------------------------------------------------------------------- import React from 'react'; +import _ from 'lodash'; import * as Server from 'frama-c/server'; import * as States from 'frama-c/states'; @@ -14,6 +15,7 @@ import { IconButton } from 'dome/controls/buttons'; import { Component, TitleBar } from 'frama-c/LabViews'; import { printFunction, markerInfo } from 'api/kernel/ast'; +import { getCallers } from 'api/plugins/eva'; import 'codemirror/mode/clike/clike'; import 'codemirror/theme/ambiance.css'; @@ -56,14 +58,26 @@ async function loadAST( }); } catch (err) { PP.error( - 'Fail to retrieve the AST of function', theFunction, - 'marker:', theMarker, err, + `Fail to retrieve the AST of function '${theFunction}' ` + + `and marker '${theMarker}':`, err, ); } })(); } } +/** Compute the [[functionName]] caller locations. */ +async function functionCallers(functionName: string) { + try { + const data = await Server.send(getCallers, functionName); + const locations = data.map(([fct, marker]) => ({ function: fct, marker })); + return locations; + } catch (err) { + PP.error(`Fail to retrieve callers of function '${functionName}':`, err); + return []; + } +} + // -------------------------------------------------------------------------- // --- AST Printer // -------------------------------------------------------------------------- @@ -72,12 +86,13 @@ const ASTview = () => { // Hooks const buffer = React.useMemo(() => new RichTextBuffer(), []); - const printed: React.MutableRefObject<string | undefined> = React.useRef(); + const printed = React.useRef<string | undefined>(); const [selection, updateSelection] = States.useSelection(); + const multipleSelections = selection?.multiple.allSelections; 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.useSyncModel(markerInfo); + const markersInfo = States.useSyncArray(markerInfo); const theFunction = selection?.current?.function; const theMarker = selection?.current?.marker; @@ -90,6 +105,15 @@ const ASTview = () => { } }); + React.useEffect(() => { + const decorator = (marker: string) => { + if (multipleSelections?.some((location) => location?.marker === marker)) + return 'highlighted-marker'; + return undefined; + }; + buffer.setDecorator(decorator); + }, [buffer, multipleSelections]); + // Hook: marker scrolling React.useEffect(() => { if (theMarker) buffer.scroll(theMarker, undefined); @@ -106,18 +130,45 @@ const ASTview = () => { } } - function onContextMenu(id: key<'#markerInfo'>) { - const marker = markers.getData(id); - if (marker && marker.kind === 'function') { - const item = { - label: `Go to definition of ${marker.name}`, - onClick: () => { - const location = { function: marker.name }; - updateSelection({ location }); - }, - }; - Dome.popupMenu([item]); + async function onContextMenu(id: key<'#markerInfo'>) { + const items = []; + const selectedMarkerInfo = markersInfo.find((e) => e.key === id); + switch (selectedMarkerInfo?.kind) { + case 'function': { + items.push({ + label: `Go to definition of ${selectedMarkerInfo.name}`, + onClick: () => { + const location = { function: selectedMarkerInfo.name }; + updateSelection({ location }); + }, + }); + break; + } + case 'declaration': { + if (selectedMarkerInfo?.name) { + const locations = await functionCallers(selectedMarkerInfo.name); + const locationsByFunction = _.groupBy(locations, (e) => e.function); + _.forEach(locationsByFunction, + (e) => { + const callerName = e[0].function; + items.push({ + label: + `Go to caller ${callerName} ` + + `${e.length > 1 ? `(${e.length} call sites)` : ''}`, + onClick: () => updateSelection({ + locations, + index: locations.findIndex((l) => l.function === callerName), + }), + }); + }); + } + break; + } + default: + break; } + if (items.length > 0) + Dome.popupMenu(items); } // Theme Popup diff --git a/ivette/src/renderer/Application.tsx b/ivette/src/renderer/Application.tsx index 7aee4d0277d9d5bca6044a4e436c220f2006dcbd..a2d04f1270e603b9f71a4ed6d906a6e32560d109 100644 --- a/ivette/src/renderer/Application.tsx +++ b/ivette/src/renderer/Application.tsx @@ -20,29 +20,30 @@ import ASTview from './ASTview'; import ASTinfo from './ASTinfo'; import Globals from './Globals'; import Properties from './Properties'; +import Locations from './Locations'; // -------------------------------------------------------------------------- // --- Selection Controls // -------------------------------------------------------------------------- -const SelectionControls = () => { +const HistorySelectionControls = () => { const [selection, updateSelection] = States.useSelection(); - const doPrevSelect = () => { updateSelection('GO_BACK'); }; - const doNextSelect = () => { updateSelection('GO_FORWARD'); }; + const doPrevSelect = () => { updateSelection('HISTORY_PREV'); }; + const doNextSelect = () => { updateSelection('HISTORY_NEXT'); }; return ( <Toolbar.ButtonGroup> <Toolbar.Button - icon="MEDIA.PREV" + icon="ANGLE.LEFT" onClick={doPrevSelect} - disabled={!selection || selection.prevSelections.length === 0} + disabled={!selection || selection.history.prevSelections.length === 0} title="Previous location" /> <Toolbar.Button - icon="MEDIA.NEXT" + icon="ANGLE.RIGHT" onClick={doNextSelect} - disabled={!selection || selection.nextSelections.length === 0} + disabled={!selection || selection.history.nextSelections.length === 0} title="Next location" /> </Toolbar.ButtonGroup> @@ -73,7 +74,7 @@ export default (() => { onClick={flipSidebar} /> <Controller.Control /> - <SelectionControls /> + <HistorySelectionControls /> <Toolbar.Filler /> <Toolbar.Button icon="ITEMS.GRID" @@ -99,6 +100,7 @@ export default (() => { <Properties /> <ASTview /> <ASTinfo /> + <Locations /> </Group> </LabView> </Splitter> diff --git a/ivette/src/renderer/Controller.tsx b/ivette/src/renderer/Controller.tsx index 699fa98dc8a2018dc87afac4848253179e67aaad..9a8b2c950ba6b64a685f62d0f15dfe41ff960dcc 100644 --- a/ivette/src/renderer/Controller.tsx +++ b/ivette/src/renderer/Controller.tsx @@ -257,13 +257,13 @@ const RenderConsole = () => { title="Discard changes" /> <IconButton - icon="MEDIA.PREV" + icon="ANGLE.LEFT" display={edited} onClick={doPrev} title="Previous command" /> <IconButton - icon="MEDIA.NEXT" + icon="ANGLE.RIGHT" display={edited} onClick={doNext} title="Next command" diff --git a/ivette/src/renderer/Locations.tsx b/ivette/src/renderer/Locations.tsx new file mode 100644 index 0000000000000000000000000000000000000000..3008d8d9cd2e83bd70089181f168d8f98578d9a2 --- /dev/null +++ b/ivette/src/renderer/Locations.tsx @@ -0,0 +1,130 @@ +// -------------------------------------------------------------------------- +// --- Table of (multiple) locations +// -------------------------------------------------------------------------- + +import React from 'react'; +import * as States from 'frama-c/states'; + +import { CompactModel } from 'dome/table/arrays'; +import { Table, Column } from 'dome/table/views'; +import { Label } from 'dome/controls/labels'; +import { IconButton } from 'dome/controls/buttons'; +import { Space } from 'dome/frame/toolbars'; +import { Component, TitleBar } from 'frama-c/LabViews'; + +// -------------------------------------------------------------------------- +// --- Locations Panel +// -------------------------------------------------------------------------- + +type LocationId = States.Location & { id: number }; + +const LocationsTable = () => { + + // Hooks + const [selection, updateSelection] = States.useSelection(); + const model = React.useMemo(() => ( + new CompactModel<number, LocationId>(({ id }: LocationId) => id) + ), []); + const multipleSelections = selection?.multiple; + const numberOfSelections = multipleSelections?.allSelections?.length; + + // Updates [[model]] with the current multiple selections. + React.useEffect(() => { + if (numberOfSelections > 0) { + const data: LocationId[] = + multipleSelections.allSelections.map((d, i) => ({ ...d, id: i })); + model.replaceAllDataWith(data); + } else + model.clear(); + }, [numberOfSelections, multipleSelections, model]); + + // Callbacks + const onTableSelection = React.useCallback( + ({ id }) => updateSelection({ index: id }), + [updateSelection], + ); + + const reload = () => { + const location = multipleSelections.allSelections[multipleSelections.index]; + updateSelection({ location }); + }; + + // Component + return ( + <> + <TitleBar> + <IconButton + icon="RELOAD" + onClick={reload} + enabled={numberOfSelections > 0} + title="Reload the current location" + /> + <IconButton + icon="ANGLE.LEFT" + onClick={() => updateSelection('MULTIPLE_PREV')} + enabled={numberOfSelections > 1 && multipleSelections?.index > 0} + title="Previous location" + /> + <IconButton + icon="ANGLE.RIGHT" + onClick={() => updateSelection('MULTIPLE_NEXT')} + enabled={ + numberOfSelections > 1 && + multipleSelections?.index < numberOfSelections - 1 + } + title="Next location" + /> + <Space /> + <Label + className="component-info" + title={ + `${numberOfSelections} selected ` + + `location${numberOfSelections > 1 ? 's' : ''}` + } + > + {multipleSelections?.allSelections.length === 0 ? + '0 / 0' : + `${multipleSelections?.index + 1} / ${numberOfSelections}`} + </Label> + <Space /> + <IconButton + icon="TRASH" + onClick={() => updateSelection('MULTIPLE_CLEAR')} + enabled={numberOfSelections > 0} + title={`Clear location${numberOfSelections > 1 ? 's' : ''}`} + /> + </TitleBar> + <Table + model={model} + selection={multipleSelections?.index} + onSelection={onTableSelection} + > + <Column + id="id" + label="#" + align="center" + width={25} + getter={(r: { id: number }) => r.id + 1} + /> + <Column id="function" label="Function" width={120} /> + <Column id="marker" label="Marker" fill /> + </Table> + </> + ); +}; + +// -------------------------------------------------------------------------- +// --- Export Component +// -------------------------------------------------------------------------- + +export default () => ( + <Component + id="frama-c.locations" + label="Locations" + title="Browse multiple locations" + > + <LocationsTable /> + </Component> +); + +// -------------------------------------------------------------------------- diff --git a/ivette/src/renderer/style.css b/ivette/src/renderer/style.css index 9a344e4e9e63f0b37f89ee513489e0a0661ad3b7..3d19a26bea7129623fdb9f80e4a788abf142f352 100644 --- a/ivette/src/renderer/style.css +++ b/ivette/src/renderer/style.css @@ -23,4 +23,8 @@ text-overflow: ellipsis; } +.highlighted-marker { + background-color: #FFFF66; +} + /* -------------------------------------------------------------------------- */ diff --git a/src/plugins/server/kernel_ast.ml b/src/plugins/server/kernel_ast.ml index 5a0b678ffbd832c6bede49520b1f1242d964b18c..3868f9d799dc6a5b0e15bb248aae35e4ec5705da 100644 --- a/src/plugins/server/kernel_ast.ml +++ b/src/plugins/server/kernel_ast.ml @@ -49,10 +49,10 @@ module MarkerKind = struct let kind name = Enum.tag ~name ~descr:(Md.plain (String.capitalize_ascii name)) kinds + let var = kind "variable" + let fct = kind "function" 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" diff --git a/src/plugins/value/api/general_requests.ml b/src/plugins/value/api/general_requests.ml new file mode 100644 index 0000000000000000000000000000000000000000..5c30a704fbcb1fc5e59c9b7bbcf611adc6091460 --- /dev/null +++ b/src/plugins/value/api/general_requests.ml @@ -0,0 +1,40 @@ +(**************************************************************************) +(* *) +(* This file is part of Frama-C. *) +(* *) +(* Copyright (C) 2007-2020 *) +(* 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 + +let package = Package.package ~plugin:"eva" + ~title:"Eva General Services" ~readme:"eva.md" () + +module CallSite = Data.Jpair (Kernel_ast.Kf) (Kernel_ast.Stmt) + +let callers kf = + let list = !Db.Value.callers kf in + List.concat (List.map (fun (kf, l) -> List.map (fun s -> kf, s) l) list) + +let () = Request.register ~package + ~kind:`GET ~name:"getCallers" + ~descr:(Markdown.plain "Get the list of call site of a function") + ~input:(module Kernel_ast.Kf) ~output:(module Data.Jlist (CallSite)) + callers + +(**************************************************************************) diff --git a/src/plugins/value/api/general_requests.mli b/src/plugins/value/api/general_requests.mli new file mode 100644 index 0000000000000000000000000000000000000000..6568b925a92d2cd9bfb6896e3487b64f3736be10 --- /dev/null +++ b/src/plugins/value/api/general_requests.mli @@ -0,0 +1,23 @@ +(**************************************************************************) +(* *) +(* This file is part of Frama-C. *) +(* *) +(* Copyright (C) 2007-2020 *) +(* 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). *) +(* *) +(**************************************************************************) + +(** General Eva requests registered in the server. *)