From 437a74e81c4a08549c921310f98a22b1f2cf5116 Mon Sep 17 00:00:00 2001 From: Maxime Jacquemin <maxime2.jacquemin@gmail.com> Date: Mon, 6 Feb 2023 15:14:53 +0100 Subject: [PATCH] [Ivette] Linter and a record for getCallers request --- ivette/src/dome/renderer/text/editor.tsx | 10 ++--- ivette/src/frama-c/kernel/ASTview.tsx | 25 +++++------- ivette/src/frama-c/kernel/SourceCode.tsx | 10 ++--- .../frama-c/plugins/eva/api/general/index.ts | 39 ++++++++++++------- src/plugins/eva/api/general_requests.ml | 38 +++++++++++++----- 5 files changed, 72 insertions(+), 50 deletions(-) diff --git a/ivette/src/dome/renderer/text/editor.tsx b/ivette/src/dome/renderer/text/editor.tsx index 28c21ec5461..79ae5cd2e3b 100644 --- a/ivette/src/dome/renderer/text/editor.tsx +++ b/ivette/src/dome/renderer/text/editor.tsx @@ -83,7 +83,7 @@ export interface Field<A> extends Data<A, StateField<A>> { set: Set<A> } // information of CodeMirror) is changed. An Aspect exposes a getter that // handles all React's hooks shenanigans and an extension that must be added to // the CodeMirror initial configuration. -export interface Aspect<A> extends Data<A, Facet<A, A>> {} +export type Aspect<A> = Data<A, Facet<A, A>>; // State extensions and Aspects have to declare their dependencies, i.e. the // Field and Aspects they rely on to perform their computations. Dependencies @@ -123,7 +123,7 @@ function mapDeps<I extends Dict, A>(deps: Deps<I>, fn: Mapper<I, A>): A[] { // Helper function used to check if at least one depencency satisfied a // given predicate. function existsDeps<I extends Dict>(deps: Deps<I>, fn: Pred<I>): boolean { - return Object.keys(deps).find((k) => fn(deps[k], k)) != undefined; + return Object.keys(deps).find((k) => fn(deps[k], k)) !== undefined; } // Helper function used to transfrom a Dependencies will keeping its structure. @@ -174,7 +174,7 @@ export function createField<A>(init: A): Field<A> { const get: Get<A> = (state) => state?.field(field) ?? init; const set: Set<A> = (v, a) => v?.dispatch({ annotations: annot.of(a) }); const isUpdated: IsUpdated = (update) => - update.transactions.find((tr) => tr.annotation(annot)) != undefined; + update.transactions.find((tr) => tr.annotation(annot)) !== undefined; return { init, get, set, structure: field, extension: field, isUpdated }; } @@ -335,7 +335,7 @@ function createSelectionField(): Field<EditorSelection> { const set: Set<EditorSelection> = (view, selection) => { view?.dispatch({ selection }); field.set(view, selection); - } + }; const updater = EditorView.updateListener.of((update) => { if (update.selectionSet) field.set(update.view, update.state.selection); }); @@ -351,7 +351,7 @@ function createDocumentField(): Field<Text> { const changes = { from: 0, to: length, insert: text }; view?.dispatch({ changes, selection }); field.set(view, text); - } + }; const updater = EditorView.updateListener.of((update) => { if (update.docChanged) field.set(update.view, update.state.doc); }); diff --git a/ivette/src/frama-c/kernel/ASTview.tsx b/ivette/src/frama-c/kernel/ASTview.tsx index ebc0419dfb9..ed3deae15c9 100644 --- a/ivette/src/frama-c/kernel/ASTview.tsx +++ b/ivette/src/frama-c/kernel/ASTview.tsx @@ -51,10 +51,6 @@ import * as Preferences from 'ivette/prefs'; type Fct = string | undefined; type Marker = string | undefined; -// A Caller is just a pair of the caller's key and the statement's key where the -// call occurs. -type Caller = { fct: key<'#fct'>, marker: key<'#stmt'> }; - // A range is just a pair of position in the code. type Range = Editor.Range; @@ -462,7 +458,7 @@ async function studia(props: StudiaProps): Promise<StudiaInfos> { // ----------------------------------------------------------------------------- // This field contains all the current function's callers, as inferred by Eva. -const Callers = Editor.createField<Caller[]>([]); +const Callers = Editor.createField<Eva.CallSite[]>([]); // This field contains information on markers. type GetMarkerData = (key: string) => Ast.markerInfoData | undefined; @@ -470,11 +466,11 @@ const GetMarkerData = Editor.createField<GetMarkerData>(() => undefined); const ContextMenuHandler = createContextMenuHandler(); function createContextMenuHandler(): Editor.Extension { - const data = { tree: Tree, locations: Callers }; + const data = { tree: Tree, callers: Callers }; const deps = { ...data, update: UpdateSelection, getData: GetMarkerData }; return Editor.createEventHandler(deps, { contextmenu: (inputs, view, event) => { - const { tree, locations, update, getData } = inputs; + const { tree, callers, update, getData } = inputs; const coords = { x: event.clientX, y: event.clientY }; const position = view.posAtCoords(coords); if (!position) return; const node = coveringNode(tree, position); @@ -483,9 +479,10 @@ function createContextMenuHandler(): Editor.Extension { const info = getData(node.id); if (info?.var === 'function') { if (info.kind === 'declaration') { - const callers = Lodash.groupBy(locations, e => e.fct); - Lodash.forEach(callers, (e) => { - const callerName = e[0].fct; + const groupedCallers = Lodash.groupBy(callers, e => e.kf); + const locations = callers.map((l) => ({ fct: l.kf, marker: l.stmt })); + Lodash.forEach(groupedCallers, (e) => { + const callerName = e[0].kf; const callSites = e.length > 1 ? `(${e.length} call sites)` : ''; items.push({ label: `Go to caller ${callerName} ` + callSites, @@ -597,12 +594,8 @@ function useFctDead(fct: Fct): Eva.deadCode { } // Server request handler returning the given function's callers. -function useFctCallers(fct: Fct): Caller[] { - const callers = States.useRequest(Eva.getCallers, fct) ?? []; - const res = React.useMemo(() => { - return callers.map(([fct, marker]) => ({ fct, marker })) - }, [callers]); - return res; +function useFctCallers(fct: Fct): Eva.CallSite[] { + return States.useRequest(Eva.getCallers, fct) ?? []; } // Server request handler returning the tainted lvalues. diff --git a/ivette/src/frama-c/kernel/SourceCode.tsx b/ivette/src/frama-c/kernel/SourceCode.tsx index 05bb3d4c71d..1d06325a0be 100644 --- a/ivette/src/frama-c/kernel/SourceCode.tsx +++ b/ivette/src/frama-c/kernel/SourceCode.tsx @@ -135,7 +135,7 @@ function createSyncOnUserSelection(): Editor.Extension { const deps = { file: File, selection: Selection, doc: Document, ...actions }; return Editor.createEventHandler(deps, { mouseup: async ({ file, cmd, update }, view, event) => { - if (!view || file === '') { console.log(view, file); return; } + if (!view || file === '') return; const pos = getCursorPosition(view); const cursor = [file, pos.line, pos.column]; try { @@ -164,9 +164,8 @@ function createSyncOnOutsideSelection(): Editor.Extension { const { cursor, ivette } = locations; if (ivette === undefined || ivette === cursor) return; const source = get(ivette); if (!source) return; - console.log(ivette, cursor, source); const newFct = ivette.fct !== cursor?.fct && ivette.marker === undefined; - const onTop = cursor === undefined || newFct + const onTop = cursor === undefined || newFct; Locations.set(view, { cursor: ivette, ivette }); Editor.selectLine(view, source.line, onTop); }); @@ -214,13 +213,10 @@ function useSourceGetter(): GetSource { const markersInfo = States.useSyncArray(Ast.markerInfo); const functionsData = States.useSyncArray(Ast.functions).getArray(); return React.useCallback(({ fct, marker }) => { - console.log('*** ', fct, marker); const markerSloc = (marker !== undefined && marker !== '') ? markersInfo.getData(marker)?.sloc : undefined; - console.log('*** ', markerSloc); const fctSloc = (fct !== undefined && fct !== '') ? functionsData.find((e) => e.name === fct)?.sloc : undefined; - console.log('*** ', fctSloc); return markerSloc ?? fctSloc; }, [markersInfo, functionsData]); } @@ -252,7 +248,7 @@ export default function SourceCode(): JSX.Element { const [command] = Settings.useGlobalSettings(Preferences.EditorCommand); const { view, Component } = Editor.Editor(extensions); const [selection, update] = States.useSelection(); - const loc = selection?.current ?? {}; + const loc = React.useMemo(() => selection?.current ?? {}, [selection]); const getSource = useSourceGetter(); const file = getSource(loc)?.file ?? ''; const filename = Path.parse(file).base; diff --git a/ivette/src/frama-c/plugins/eva/api/general/index.ts b/ivette/src/frama-c/plugins/eva/api/general/index.ts index 596145f3a59..85c3985681b 100644 --- a/ivette/src/frama-c/plugins/eva/api/general/index.ts +++ b/ivette/src/frama-c/plugins/eva/api/general/index.ts @@ -94,25 +94,38 @@ const computationState_internal: State.Value<computationStateType> = { /** The current computation state of the analysis. */ export const computationState: State.Value<computationStateType> = computationState_internal; -const getCallers_internal: Server.GetRequest< - Json.key<'#fct'>, - [ Json.key<'#fct'>, Json.key<'#stmt'> ][] - > = { +/** CallSite */ +export interface CallSite { + /** Function */ + kf: Json.key<'#fct'>; + /** Statement */ + stmt: Json.key<'#stmt'>; +} + +/** Decoder for `CallSite` */ +export const jCallSite: Json.Decoder<CallSite> = + Json.jObject({ + kf: Json.jKey<'#fct'>('#fct'), + stmt: Json.jKey<'#stmt'>('#stmt'), + }); + +/** Natural order for `CallSite` */ +export const byCallSite: Compare.Order<CallSite> = + Compare.byFields + <{ kf: Json.key<'#fct'>, stmt: Json.key<'#stmt'> }>({ + kf: Compare.string, + stmt: Compare.string, + }); + +const getCallers_internal: Server.GetRequest<Json.key<'#fct'>,CallSite[]> = { kind: Server.RqKind.GET, name: 'plugins.eva.general.getCallers', input: Json.jKey<'#fct'>('#fct'), - output: Json.jArray( - Json.jPair( - Json.jKey<'#fct'>('#fct'), - Json.jKey<'#stmt'>('#stmt'), - )), + output: Json.jArray(jCallSite), signals: [], }; /** 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; +export const getCallers: Server.GetRequest<Json.key<'#fct'>,CallSite[]>= getCallers_internal; /** Data for array rows [`functions`](#functions) */ export interface functionsData { diff --git a/src/plugins/eva/api/general_requests.ml b/src/plugins/eva/api/general_requests.ml index abea21d88e4..21631306386 100644 --- a/src/plugins/eva/api/general_requests.ml +++ b/src/plugins/eva/api/general_requests.ml @@ -57,17 +57,37 @@ let _computation_signal = ~add_hook:Analysis.register_computation_hook () -module CallSite = Data.Jpair (Kernel_ast.Kf) (Kernel_ast.Stmt) -let callers kf = - let list = Results.callsites 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 +(* ----- Callsites ---------------------------------------------------------- *) + +module CallSite = struct + open Data + type callsite + let record: callsite Record.signature = Record.signature () + + let kf_field = Record.field record ~name:"kf" + ~descr:(Markdown.plain "Function") (module Kernel_ast.Kf) + + let stmt_field = Record.field record ~name:"stmt" + ~descr:(Markdown.plain "Statement") (module Kernel_ast.Stmt) + + let data = Record.publish ~package ~name:"CallSite" + ~descr:(Markdown.plain "CallSite") record + + module R : Record.S with type r = callsite = (val data) + + let convert (kf, stmts) = stmts |> List.map @@ fun stmt -> + R.default |> R.set kf_field kf |> R.set stmt_field stmt + + let callers kf = Results.callsites kf |> List.map convert |> List.concat + + 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 (R)) + callers +end -- GitLab