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