From 34794beb5d9fcf19ac0e7987f60e5eca866a204b Mon Sep 17 00:00:00 2001
From: Valentin Perrelle <valentin.perrelle@cea.fr>
Date: Fri, 22 Sep 2023 13:57:17 +0200
Subject: [PATCH] [Ivette] Dive: add statement visualization in the tree view

---
 ivette/src/frama-c/plugins/dive/dive.css | 34 ++++++++--
 ivette/src/frama-c/plugins/dive/tree.tsx | 85 ++++++++++++++++++++----
 2 files changed, 100 insertions(+), 19 deletions(-)

diff --git a/ivette/src/frama-c/plugins/dive/dive.css b/ivette/src/frama-c/plugins/dive/dive.css
index e930e49393d..9cf41c0888e 100644
--- a/ivette/src/frama-c/plugins/dive/dive.css
+++ b/ivette/src/frama-c/plugins/dive/dive.css
@@ -9,25 +9,47 @@
     background-color: var(--background-profound);
 }
 
-.diveTree ul
-{
+.diveTree {
+    overflow: auto;
+}
+
+.diveTree ul {
     list-style: none;
     border-left: 1px dotted black;
     margin-left: 8px;
     padding-left: 12px;
 }
 
-.diveTree .folder
-{
+.diveTree .folder {
     width: 16px;
     height: 16px;
     cursor: pointer;
     /* border: 1px solid var(--text); */
 }
 
-.diveTree .exploration
-{
+.diveTree .exploration {
     width: 20px;
     height: 20px;
     vertical-align: middle;
 }
+
+.diveTree .marker > .label {
+    text-overflow: ellipsis;
+    width: 240px;
+    white-space: nowrap;
+    display: inline-block;
+    overflow: hidden;
+    vertical-align: middle;
+}
+
+.diveTree .selected {
+    background: var(--code-select);
+}
+
+.diveTree .hovered {
+    background: var(--code-hover);
+}
+
+.diveTree .marker {
+    cursor: pointer;
+}
diff --git a/ivette/src/frama-c/plugins/dive/tree.tsx b/ivette/src/frama-c/plugins/dive/tree.tsx
index 3c96fa9ed87..78be7a54daa 100644
--- a/ivette/src/frama-c/plugins/dive/tree.tsx
+++ b/ivette/src/frama-c/plugins/dive/tree.tsx
@@ -22,6 +22,7 @@
 
 import React, { useEffect } from 'react';
 
+import { classes } from 'dome/misc/utils';
 import { IconButton } from 'dome/controls/buttons';
 import * as Ivette from 'ivette';
 
@@ -30,6 +31,7 @@ import * as States from 'frama-c/states';
 
 import EvaReady from 'frama-c/plugins/eva/EvaReady';
 import * as API from './api';
+import type { marker, location } from 'frama-c/kernel/api/ast';
 import gearsIcon from '../eva/images/gears.svg';
 
 import './dive.css';
@@ -75,16 +77,43 @@ function Exploring(): JSX.Element {
   return <><img src={gearsIcon} className="exploration" />Exploring...</>;
 }
 
-type withKey<P> = P & {key: string | number | null}
+function Marker(props: {location: location}): JSX.Element {
+  const location = props.location;
+  const marker = location.marker;
+  const { descr } = States.useMarker(marker);
+  const [selection, updateSelection] = States.useSelection();
+  const [hovering, updateHovered] = States.useHovered();
+  const classeName = classes(
+    'marker',
+    selection.current?.marker === marker && 'selected',
+    hovering?.marker === marker && 'hovered'
+  );
+
+  return <span
+    className={classeName}
+    onClick={() => updateSelection({ location: location })}
+    onMouseEnter={() => updateHovered(location)}
+    onMouseLeave={() => updateHovered(undefined)}>
+      {descr}
+  </span>;
+}
+
+
+type WithKey<P> = P & {key: string | number | null}
+
+type WithItemChildren<P> =
+  P & {
+    children?: WithKey<React.ReactElement> | WithKey<React.ReactElement>[]
+  }
 
 type TreeNodeProps = {
   label: React.ReactNode,
+  className?: string,
   unfolded?: boolean,
   onunfold?: () => void,
-  children?: withKey<React.ReactElement> | withKey<React.ReactElement>[]
 }
 
-function TreeNode(props: TreeNodeProps): JSX.Element {
+function TreeNode(props: WithItemChildren<TreeNodeProps>): JSX.Element {
   const [unfolded, setUnfolded] = React.useState(props.unfolded === true);
   const children =
     props.children ?
@@ -96,21 +125,33 @@ function TreeNode(props: TreeNodeProps): JSX.Element {
     setUnfolded(!unfolded);
   };
 
-  return <div>
-    {children.length > 0 ?
-      <Folder unfolded={unfolded} onclick={toggle} /> : <></>
-    }
-    <span>{props.label}</span>
+  return <div className={props.className}>
+    <span className="label">
+      {children.length > 0 ?
+        <Folder unfolded={unfolded} onclick={toggle} /> : <></>
+      }
+      {props.label}
+    </span>
     {unfolded ?
       <ul>
         {children.map((element) => <li key={element.key}>{element}</li>)}
       </ul> :
         null
-      }
+    }
     </div>;
 }
 
-function Node(props: {nodeId: number, unfolded?: boolean}): JSX.Element {
+function MarkerNode(props: WithItemChildren<{location: location}>):
+    JSX.Element {
+  return <TreeNode
+    unfolded={true}
+    label={<Marker location={props.location} />}
+    className="marker">
+      {props.children}
+  </TreeNode>;
+}
+
+function GraphNode(props: {nodeId: number, unfolded?: boolean}): JSX.Element {
   const graphData = States.useSyncArrayElt(API.graph, `n${props.nodeId}`);
   const graph = States.useSyncArrayData(API.graph);
 
@@ -121,13 +162,31 @@ function Node(props: {nodeId: number, unfolded?: boolean}): JSX.Element {
       .filter(isDependency)
       .filter((d) => d.dst === node.id);
 
+    // Transform dependencies into a map 'statement' -> 'memory location read
+    // at this statement'
+    const map = new Map<marker, [location, Set<API.nodeId>]>();
+    deps.forEach((d) => {
+      d.origins.forEach((loc) => {
+        let entry = map.get(loc.marker);
+        if (entry === undefined) {
+          entry = [loc, new Set()];
+          map.set(loc.marker, entry);
+        }
+        entry[1].add(d.src);
+     });
+    });
+
     return <TreeNode
         unfolded={props.unfolded}
         onunfold={() => explore(node)}
         label={node.label}>
           {node.backward_explored ?
-            deps.map((element) =>
-              <Node nodeId={element.src} key={element.src} />
+            Array.from(map.entries()).map(([m, [loc, sources]]) =>
+              <MarkerNode location={loc} key={m}>
+                {Array.from(sources.values()).map((src) =>
+                  <GraphNode nodeId={src} key={src} />
+                ) }
+              </MarkerNode>
             ) :
             <Exploring key={null} />
           }
@@ -165,7 +224,7 @@ export default function TreeComponent(): JSX.Element {
           <>Select an expression to investigate</>
         :
           <div className="diveTree">
-            <Node nodeId={root} unfolded={true} />
+            <GraphNode nodeId={root} unfolded={true} />
           </div>
       }
     </EvaReady>
-- 
GitLab