From d06e50c205780c5cbe2238243c3dba1df6b89faf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Loi=CC=88c=20Correnson?= <loic.correnson@cea.fr> Date: Tue, 5 Jan 2021 09:32:23 +0100 Subject: [PATCH] [ivette/eva] group callstacks by function --- .../api/generated/plugins/eva/values/index.ts | 9 +-- ivette/src/frama-c/eva/Values.tsx | 12 +--- ivette/src/frama-c/eva/cells.ts | 33 +++++----- ivette/src/frama-c/eva/layout.ts | 47 ++++++++++----- ivette/src/frama-c/eva/model.ts | 12 ++-- ivette/src/frama-c/eva/probes.ts | 6 +- ivette/src/frama-c/eva/stacks.ts | 60 ++++++++++++++----- src/plugins/value/api/values_request.ml | 14 ++++- 8 files changed, 124 insertions(+), 69 deletions(-) diff --git a/ivette/api/generated/plugins/eva/values/index.ts b/ivette/api/generated/plugins/eva/values/index.ts index bc7353fe239..b08b8540d74 100644 --- a/ivette/api/generated/plugins/eva/values/index.ts +++ b/ivette/api/generated/plugins/eva/values/index.ts @@ -43,17 +43,14 @@ export const jCallstackSafe: Json.Safe<callstack> = /** Natural order for `callstack` */ export const byCallstack: Compare.Order<callstack> = Compare.number; -const getCallstacks_internal: Server.GetRequest< - Json.key<'#stmt'>, - callstack[] - > = { +const getCallstacks_internal: Server.GetRequest<marker[],callstack[]> = { kind: Server.RqKind.GET, name: 'plugins.eva.values.getCallstacks', - input: Json.jKey<'#stmt'>('#stmt'), + input: Json.jList(jMarker), output: Json.jList(jCallstack), }; /** Callstacks for markers */ -export const getCallstacks: Server.GetRequest<Json.key<'#stmt'>,callstack[]>= getCallstacks_internal; +export const getCallstacks: Server.GetRequest<marker[],callstack[]>= getCallstacks_internal; const getCallstackInfo_internal: Server.GetRequest< callstack, diff --git a/ivette/src/frama-c/eva/Values.tsx b/ivette/src/frama-c/eva/Values.tsx index 6bdf15345c8..5a10fc807c9 100644 --- a/ivette/src/frama-c/eva/Values.tsx +++ b/ivette/src/frama-c/eva/Values.tsx @@ -159,11 +159,9 @@ function AlarmsInfo() { const probe = model.getFocused(); if (probe) { const callstack = model.getCallstack(); - const domain = - model.values.getValues(probe.marker, probe.stmt, callstack); + const domain = model.values.getValues(probe, callstack); const alarms = domain?.alarms ?? []; if (alarms.length > 0) { - // console.log('ALARMS', alarms); const renderAlarm = ([status, alarm]: EvaAlarm) => { const className = `eva-alarm-info eva-alarm-${status}`; return ( @@ -172,7 +170,7 @@ function AlarmsInfo() { }; return ( <Vpack className="eva-info"> - {alarms.map(renderAlarm)} + {React.Children.toArray(alarms.map(renderAlarm))} </Vpack> ); } @@ -259,11 +257,7 @@ function TableCell(props: TableCellProps) { case 'values': case 'callstack': { - const domain = model.values.getValues( - probe.marker, - probe.stmt, - callstack, - ); + const domain = model.values.getValues(probe, callstack); const { alarms = [] } = domain; const { vstate: s, effects, condition } = probe; const text = valueAt(domain, s) ?? ''; diff --git a/ivette/src/frama-c/eva/cells.ts b/ivette/src/frama-c/eva/cells.ts index acb3e9ea054..5c4b29066bf 100644 --- a/ivette/src/frama-c/eva/cells.ts +++ b/ivette/src/frama-c/eva/cells.ts @@ -16,6 +16,7 @@ export type callback = () => void; export interface ModelCallbacks { forceUpdate: callback; forceLayout: callback; + mergeStacks: (fct: string) => void; } export interface Size { cols: number; rows: number } @@ -131,6 +132,11 @@ export function diffElse( // --- Value Cache // -------------------------------------------------------------------------- +export interface Probe { + fct: string | undefined; + marker: Ast.marker; +} + export class ValueCache { private readonly state: ModelCallbacks; @@ -159,33 +165,32 @@ export class ValueCache { return this.probes.get(target) ?? EMPTY; } - private static stackKey(stmt: string, callstack: Values.callstack) { - return `${stmt}::${callstack}`; + private static stackKey(fct: string, callstack: Values.callstack) { + return `${fct}::${callstack}`; } - getStackSize(stmt: string, callstack: Values.callstack) { - const key = ValueCache.stackKey(stmt, callstack); + getStackSize(fct: string, callstack: Values.callstack) { + const key = ValueCache.stackKey(fct, callstack); return this.stacks.get(key) ?? EMPTY; } // --- Cached Values & Request Update getValues( - target: Ast.marker, - stmt: string | undefined, + { fct, marker }: Probe, callstack: Values.callstack | undefined, ): EvaValues { - const key = callstack !== undefined ? `${target}@${callstack}` : target; + const key = callstack !== undefined ? `${marker}@${callstack}` : marker; const cache = this.vcache; const cached = cache.get(key); if (cached) return cached; const newValue: EvaValues = { values: '', size: EMPTY }; - if (callstack !== undefined && stmt === undefined) + if (callstack !== undefined && fct === undefined) return newValue; - // callstack !== undefined ==> stmt !== undefined) + // callstack !== undefined ==> fct !== undefined) cache.set(key, newValue); Server - .send(Values.getValues, { target, callstack }) + .send(Values.getValues, { target: marker, callstack }) .then((r) => { newValue.errors = undefined; newValue.values = r.values; @@ -193,7 +198,7 @@ export class ValueCache { newValue.v_then = r.v_then; newValue.v_else = r.v_else; newValue.alarms = r.alarms; - if (this.updateLayout(target, stmt, callstack, newValue)) + if (this.updateLayout(marker, fct, callstack, newValue)) this.state.forceLayout(); else this.state.forceUpdate(); @@ -209,7 +214,7 @@ export class ValueCache { private updateLayout( target: Ast.marker, - stmt: string | undefined, + fct: string | undefined, callstack: Values.callstack | undefined, v: EvaValues, ): boolean { @@ -231,9 +236,9 @@ export class ValueCache { } // max size for stack row if (callstack !== undefined) { - if (stmt === undefined) small = false; + if (fct === undefined) small = false; else { - const key = ValueCache.stackKey(stmt, callstack); + const key = ValueCache.stackKey(fct, callstack); const cs = this.stacks.get(key) ?? EMPTY; if (!leq(s, cs)) { this.stacks.set(key, merge(cs, s)); diff --git a/ivette/src/frama-c/eva/layout.ts b/ivette/src/frama-c/eva/layout.ts index c416fa7c894..58ae0f0b1cd 100644 --- a/ivette/src/frama-c/eva/layout.ts +++ b/ivette/src/frama-c/eva/layout.ts @@ -59,7 +59,7 @@ export class LayoutEngine { } // --- Probe Buffer - private byStacks?: string; // stmt + private byFctStacks?: string; // function private rowSize: Size = EMPTY; private buffer: Probe[] = []; private rows: Row[] = []; @@ -74,18 +74,35 @@ export class LayoutEngine { }; } - push(p: Probe) { + layout(ps: Probe[]): Row[] { + ps.sort(LayoutEngine.order).forEach(this.push); + return this.flush(); + } + + private static order(p: Probe, q: Probe): number { + const fp = p.fct; + const fq = q.fct; + if (fp < fq) return -1; + if (fp > fq) return +1; + const cp = p.byCallstacks; + const cq = q.byCallstacks; + if (!cp && cq) return (-1); + if (cp && !cq) return (+1); + return Probe.order(p, q); + } + + private push(p: Probe) { const probeSize = this.values.getProbeSize(p.marker); const s = this.crop(p.zoomed, probeSize); p.zoomable = p.zoomed || !leq(probeSize, s); p.minCols = s.cols; p.maxCols = Math.max(p.minCols, probeSize.cols); - const stmt = p.byCallstacks ? p.stmt : undefined; - if (stmt !== this.byStacks) { + const fct = p.byCallstacks ? p.fct : undefined; + if (fct !== this.byFctStacks) { this.flush(); - this.byStacks = stmt; + this.byFctStacks = fct; } - if (!stmt && s.cols + this.rowSize.cols > this.margin) + if (!fct && s.cols + this.rowSize.cols > this.margin) this.flush(); this.rowSize = addH(this.rowSize, s); this.rowSize.cols += PADDING; @@ -94,16 +111,18 @@ export class LayoutEngine { // --- Flush Rows - flush(): Row[] { + private flush(): Row[] { const ps = this.buffer; const rs = this.rows; if (ps.length > 0) { - const stmt = this.byStacks; - if (stmt) { + const fct = this.byFctStacks; + const hlines = this.rowSize.rows; + if (fct) { // --- by callstacks - const wcs = this.stacks.getStacks(stmt); + const markers = ps.map((p) => p.marker); + const wcs = this.stacks.getStacksForFunction(fct, markers); rs.push({ - key: `P${stmt}`, + key: `F${fct}`, kind: 'probes', probes: ps, stacks: wcs.length, @@ -111,13 +130,13 @@ export class LayoutEngine { }); wcs.forEach((cs, k) => { rs.push({ - key: `C${stmt}::${cs}`, + key: `C${fct}::${cs}`, kind: 'callstack', probes: ps, stackIndex: k, stacks: wcs.length, callstack: cs, - hlines: this.values.getStackSize(stmt, cs).rows, + hlines, }); }); } else { @@ -132,7 +151,7 @@ export class LayoutEngine { key: `V${n}`, kind: 'values', probes: ps, - hlines: this.rowSize.rows, + hlines, }); } } diff --git a/ivette/src/frama-c/eva/model.ts b/ivette/src/frama-c/eva/model.ts index 6f24c809c84..ce2d7b30c1f 100644 --- a/ivette/src/frama-c/eva/model.ts +++ b/ivette/src/frama-c/eva/model.ts @@ -32,6 +32,7 @@ export class Model implements ModelCallbacks { this.forceUpdate = this.forceUpdate.bind(this); this.forceLayout = this.forceLayout.bind(this); this.forceReload = this.forceReload.bind(this); + this.mergeStacks = this.mergeStacks.bind(this); this.computeLayout = this.computeLayout.bind(this); this.setLayout = throttle(this.setLayout.bind(this), 300); this.getRowKey = this.getRowKey.bind(this); @@ -63,8 +64,7 @@ export class Model implements ModelCallbacks { } getStacks(p: Probe | undefined): Values.callstack[] { - const stmt = p?.stmt; - return stmt ? this.stacks.getStacks(stmt) : []; + return p ? this.stacks.getStacksForProbe(p) : []; } // --- Caches @@ -165,8 +165,7 @@ export class Model implements ModelCallbacks { this.values, this.stacks, ); - toLayout.sort(Probe.order).forEach(engine.push); - this.rows = engine.flush(); + this.rows = engine.layout(toLayout); this.laidout.emit(); this.lock = false; } @@ -196,6 +195,11 @@ export class Model implements ModelCallbacks { // --- Foce Update forceUpdate() { this.changed.emit(); } + // --- Force Stacks Reload + mergeStacks(fct: string) { + this.stacks.mergeStacksForFunction(fct); + this.forceLayout(); + } } // -------------------------------------------------------------------------- diff --git a/ivette/src/frama-c/eva/probes.ts b/ivette/src/frama-c/eva/probes.ts index c920d71d006..aaa3f86cc52 100644 --- a/ivette/src/frama-c/eva/probes.ts +++ b/ivette/src/frama-c/eva/probes.ts @@ -112,7 +112,7 @@ export class Probe { setByCallstacks(byCS: boolean) { if (byCS !== this.byCallstacks) { this.byCallstacks = byCS; - this.model.forceLayout(); + this.model.mergeStacks(this.fct); } } @@ -137,10 +137,6 @@ export class Probe { const rq = q.rank ?? 0; if (rp < rq) return (-1); if (rp > rq) return (+1); - const cp = p.byCallstacks; - const cq = q.byCallstacks; - if (!cp && cq) return (-1); - if (cp && !cq) return (+1); if (p.marker < q.marker) return (-1); if (p.marker > q.marker) return (+1); return 0; diff --git a/ivette/src/frama-c/eva/stacks.ts b/ivette/src/frama-c/eva/stacks.ts index 013638a1630..84e5f98b290 100644 --- a/ivette/src/frama-c/eva/stacks.ts +++ b/ivette/src/frama-c/eva/stacks.ts @@ -3,6 +3,7 @@ // -------------------------------------------------------------------------- import * as Server from 'frama-c/server'; +import * as Ast from 'frama-c/api/kernel/ast'; import * as Values from 'frama-c/api/plugins/eva/values'; import { ModelCallbacks } from './cells'; @@ -19,6 +20,20 @@ export interface Callsite { rank?: number; } +// -------------------------------------------------------------------------- +// --- CallStacks by Function +// -------------------------------------------------------------------------- + +interface StacksByFct { + dirty: boolean; + stacks: callstacks; +} + +interface ProbeFct { + fct: string; + marker: Ast.marker; +} + // -------------------------------------------------------------------------- // --- CallStacks Cache // -------------------------------------------------------------------------- @@ -26,7 +41,7 @@ export interface Callsite { export class StacksCache { private readonly model: ModelCallbacks; - private readonly stacks = new Map<string, callstacks>(); + private readonly byFct = new Map<string, StacksByFct>(); private readonly calls = new Map<Values.callstack, Callsite[]>(); // -------------------------------------------------------------------------- @@ -38,24 +53,40 @@ export class StacksCache { } clear() { - this.stacks.clear(); + this.byFct.clear(); } // -------------------------------------------------------------------------- // --- Getters // -------------------------------------------------------------------------- - getStacks(stmt: string): callstacks { - const cs = this.stacks.get(stmt); - if (cs !== undefined) return cs; - this.stacks.set(stmt, []); - this.requestCallstacks(stmt); - return []; + mergeStacksForFunction(fct: string) { + const buffer = this.byFct.get(fct); + if (buffer) buffer.dirty = true; + } + + getStacksForProbe({ fct, marker }: ProbeFct): callstacks { + const fs = this.byFct.get(fct); + if (!fs) { + this.byFct.set(fct, { dirty: true, stacks: [] }); + this.requestCallstacks(fct, [marker]); + } + return fs ? fs.stacks : []; + } + + getStacksForFunction(fct: string, probes?: Ast.marker[]): callstacks { + const fs = this.byFct.get(fct); + if (!fs) this.byFct.set(fct, { dirty: false, stacks: [] }); + if (probes) { + if (!fs || fs.dirty) this.requestCallstacks(fct, probes); + if (fs && fs.dirty) fs.dirty = false; + } + return fs ? fs.stacks : []; } getCalls(cs: Values.callstack): Callsite[] { - const fs = this.calls.get(cs); - if (fs !== undefined) return fs; + const fcs = this.calls.get(cs); + if (fcs !== undefined) return fcs; this.calls.set(cs, []); this.requestCalls(cs); return []; @@ -65,12 +96,13 @@ export class StacksCache { // --- Fetchers // -------------------------------------------------------------------------- - private requestCallstacks(stmt: string) { + private requestCallstacks(fct: string, probes: Ast.marker[]) { Server - .send(Values.getCallstacks, stmt) - .then((cs: callstacks) => { - this.stacks.set(stmt, cs); + .send(Values.getCallstacks, probes) + .then((stacks: callstacks) => { + this.byFct.set(fct, { dirty: false, stacks }); this.model.forceLayout(); + this.model.forceUpdate(); }); } diff --git a/src/plugins/value/api/values_request.ml b/src/plugins/value/api/values_request.ml index 035340717f3..e6587ea8582 100644 --- a/src/plugins/value/api/values_request.ml +++ b/src/plugins/value/api/values_request.ml @@ -27,6 +27,7 @@ open Cil_types module Kmap = Kernel_function.Hashtbl module Smap = Cil_datatype.Stmt.Hashtbl module CS = Value_types.Callstack +module CSet = CS.Set module CSmap = CS.Hashtbl module Md = Markdown @@ -365,11 +366,18 @@ let proxy = let () = Request.register ~package ~kind:`GET ~name:"getCallstacks" ~descr:(Md.plain "Callstacks for markers") - ~input:(module Jstmt) + ~input:(module Jlist(Jmarker)) ~output:(module Jlist(Jcallstack)) - begin fun stmt -> + begin fun markers -> let module A = (val !proxy) in - Ranking.sort (A.callstacks stmt) + let cset = List.fold_left + (fun cset marker -> + match probe marker with + | Pexpr(_,stmt) | Plval(_,stmt) -> + List.fold_right CSet.add (A.callstacks stmt) cset + | Pnone -> cset + ) CSet.empty markers in + Ranking.sort (CSet.elements cset) end (* -------------------------------------------------------------------------- *) -- GitLab