diff --git a/ivette/src/frama-c/plugins/region/api/index.ts b/ivette/src/frama-c/plugins/region/api/index.ts index cc0b4835c5e86873d0a477bc351efa08583ae404..942053971ed63a0baf6086a312b1c24ba5f27e28 100644 --- a/ivette/src/frama-c/plugins/region/api/index.ts +++ b/ivette/src/frama-c/plugins/region/api/index.ts @@ -137,6 +137,11 @@ export const regionDefault: region = pointed: undefined, reads: false, writes: false, bytes: false, label: '', title: '' }; +/** Region Analysis Updated */ +export const updated: Server.Signal = { + name: 'plugins.region.updated', +}; + const compute_internal: Server.ExecRequest<decl,null> = { kind: Server.RqKind.EXEC, name: 'plugins.region.compute', @@ -152,7 +157,7 @@ const regions_internal: Server.GetRequest<decl,region[]> = { name: 'plugins.region.regions', input: jDecl, output: Json.jArray(jRegion), - signals: [], + signals: [ { name: 'plugins.region.updated' } ], }; /** Compute regions for the given declaration */ export const regions: Server.GetRequest<decl,region[]>= regions_internal; @@ -162,7 +167,7 @@ const regionsAt_internal: Server.GetRequest<marker,region[]> = { name: 'plugins.region.regionsAt', input: jMarker, output: Json.jArray(jRegion), - signals: [], + signals: [ { name: 'plugins.region.updated' } ], }; /** Compute regions at the given marker position */ export const regionsAt: Server.GetRequest<marker,region[]>= regionsAt_internal; diff --git a/ivette/src/frama-c/plugins/region/index.tsx b/ivette/src/frama-c/plugins/region/index.tsx index 5cebfb451ff60953c41a983262d3478d3a6f4a8a..3bf4a449f4d090e5ca54729f46038ae513192bc3 100644 --- a/ivette/src/frama-c/plugins/region/index.tsx +++ b/ivette/src/frama-c/plugins/region/index.tsx @@ -25,14 +25,75 @@ // -------------------------------------------------------------------------- import React from 'react'; +import { Label } from 'dome/controls/labels'; +import { LCD } from 'dome/controls/displays'; +import { IconButton } from 'dome/controls/buttons'; +import * as Dome from 'dome'; +import * as Tools from 'dome/frame/toolbars'; import * as Ivette from 'ivette'; +import * as Server from 'frama-c/server'; +import * as States from 'frama-c/states'; +import * as Region from './api'; import { MemoryView } from './memory'; +import './style.css'; + +function RegionAnalys(): JSX.Element { + const [kf, setKf] = React.useState<States.Scope>(); + const [kfName, setName] = React.useState<string>(); + const [pinned, setPinned] = React.useState(false); + const [running, setRunning] = React.useState(false); + const setComputing = Dome.useProtected(setRunning); + const scope = States.useCurrentScope(); + const { kind, name } = States.useDeclaration(scope); + const regions = States.useRequest(Region.regions, kf) ?? []; + React.useEffect(() => { + if (!pinned && kind === 'FUNCTION' && scope !== kf) { + setKf(scope); + setName(name); + } else if (!Server.isRunning()) { + setKf(undefined); + setName(undefined); + setPinned(false); + } + }, [pinned, kind, name, scope, kf]); + async function compute(): Promise<void> { + try { + setComputing(true); + await Server.send(Region.compute, kf); + } finally { + setComputing(false); + } + } + return ( + <> + <Tools.ToolBar> + <Label label='Function' /> + <LCD className='region-lcd' label={kfName ?? '---'} /> + <Tools.Button + icon={running ? 'EXECUTE' : 'MEDIA.PLAY'} + title='Run region analysis on the selected function' + disabled={running} + visible={kf !== undefined && regions.length === 0} + onClick={compute} + /> + <IconButton + icon='PIN' + display={kf !== undefined} + title='Keep focus on current function' + selected={pinned} + onClick={() => setPinned(!pinned)} + /> + </Tools.ToolBar> + <MemoryView regions={regions} /> + </> + ); +} Ivette.registerComponent({ id: 'fc.region.main', label: 'Region Analysis', - preferredPosition: 'B', - children: <MemoryView />, + preferredPosition: 'BD', + children: <RegionAnalys />, }); // -------------------------------------------------------------------------- diff --git a/ivette/src/frama-c/plugins/region/memory.tsx b/ivette/src/frama-c/plugins/region/memory.tsx index bc84a8dc5e4860680934d71ebe18ad03402414fc..81c4fd55d9f1f2e24f2eb63d4d7157cf32baac32 100644 --- a/ivette/src/frama-c/plugins/region/memory.tsx +++ b/ivette/src/frama-c/plugins/region/memory.tsx @@ -26,10 +26,9 @@ import React from 'react'; import * as Dot from 'dome/graph/diagram'; -import * as States from 'frama-c/states'; import * as Region from './api'; -function makeDiagram(regions: Region.region[]): Dot.DiagramProps { +function makeDiagram(regions: readonly Region.region[]): Dot.DiagramProps { const nodes: Dot.Node[] = []; const edges: Dot.Edge[] = []; regions.forEach(r => { @@ -65,10 +64,12 @@ function makeDiagram(regions: Region.region[]): Dot.DiagramProps { return { nodes, edges }; } -export function MemoryView(): JSX.Element { - const scope = States.useCurrentScope(); - // eslint-disable-next-line react-hooks/exhaustive-deps - const regions = States.useRequest(Region.regions, scope) ?? []; +export interface MemoryViewProps { + regions: readonly Region.region[]; +} + +export function MemoryView(props: MemoryViewProps): JSX.Element { + const { regions } = props; const diagram = React.useMemo(() => makeDiagram(regions), [regions]); - return <Dot.Diagram display={regions.length > 0} {...diagram} />; + return <Dot.Diagram {...diagram} />; } diff --git a/ivette/src/frama-c/plugins/region/style.css b/ivette/src/frama-c/plugins/region/style.css new file mode 100644 index 0000000000000000000000000000000000000000..0076926a0527f0215a6fa22963d4129835df2279 --- /dev/null +++ b/ivette/src/frama-c/plugins/region/style.css @@ -0,0 +1,9 @@ +/* -------------------------------------------------------------------------- */ +/* --- CSS for Region Views --- */ +/* -------------------------------------------------------------------------- */ + +.region-lcd { + margin-top: 4px; + min-width: 160px; + text-align: center; +} diff --git a/src/plugins/region/analysis.ml b/src/plugins/region/analysis.ml index ae65c3fdca95dbbfd78c2ed5981f26f7f54fa859..b64c6571bc83fbfa648159d1701a12bf6e597a33 100644 --- a/src/plugins/region/analysis.ml +++ b/src/plugins/region/analysis.ml @@ -69,4 +69,6 @@ let get kf = let compute kf = ignore @@ get kf +let add_hook f = STATE.add_hook_on_change (fun _ -> f()) + (* -------------------------------------------------------------------------- *) diff --git a/src/plugins/region/analysis.mli b/src/plugins/region/analysis.mli index a28624691b50384f587f89659649828739b7a6c4..e9279b47420bc40e87fef33265d8a9e86a15febc 100644 --- a/src/plugins/region/analysis.mli +++ b/src/plugins/region/analysis.mli @@ -30,3 +30,6 @@ val get : kernel_function -> Code.domain (** Memoized *) val compute : kernel_function -> unit + +(** Hook on update *) +val add_hook : (unit -> unit) -> unit diff --git a/src/plugins/region/services.ml b/src/plugins/region/services.ml index 7b93f23d8f7a65f09e613596b125cf684d00d032..990424bbe4ddfb5135086297cfe92b4ad5575f2d 100644 --- a/src/plugins/region/services.ml +++ b/src/plugins/region/services.ml @@ -192,6 +192,11 @@ let map_of_declaration (decl : Printer_tag.declaration) = | SFunction kf -> (Analysis.find kf).map | _ -> raise Not_found +let signal = Request.signal ~package ~name:"updated" + ~descr:(Md.plain "Region Analysis Updated") + +let () = Analysis.add_hook (fun () -> Request.emit signal) + let () = Request.register ~package ~kind:`EXEC ~name:"compute" @@ -206,6 +211,7 @@ let () = ~descr:(Md.plain "Compute regions for the given declaration") ~input:(module Kernel_ast.Decl) ~output:(module Regions) + ~signals:[signal] begin fun decl -> try regions @@ map_of_declaration decl with Not_found -> [] @@ -217,6 +223,7 @@ let () = ~descr:(Md.plain "Compute regions at the given marker position") ~input:(module Kernel_ast.Marker) ~output:(module Regions) + ~signals:[signal] begin fun loc -> try regions @@ map_of_localizable loc with Not_found -> []