From 546050ee5fab57d0df37f0a25a8f6dfa7c7754cc Mon Sep 17 00:00:00 2001
From: Maxime Jacquemin <maxime2.jacquemin@gmail.com>
Date: Wed, 27 Oct 2021 18:19:30 +0200
Subject: [PATCH] [ivette] Fixing react unmounted component bug

The Valuecomponent had lots of weird bugs, triggering the warning
<Can't perform a React state update on an unmounted component>.
This was caused by an ugly, full of unsafe effect, way to handle the
Model used by a lot of components used by the ValueComponent

Now, the Model is created only once by the ValueComponent itself, and
given as a property to every component that needs it.

As a consequence, the useModel() function is now useless.
---
 ivette/src/frama-c/plugins/eva/index.tsx      | 13 +++++++---
 ivette/src/frama-c/plugins/eva/model.ts       | 24 ++-----------------
 ivette/src/frama-c/plugins/eva/probeinfos.tsx | 12 +++++-----
 ivette/src/frama-c/plugins/eva/valueinfos.tsx | 10 ++++----
 ivette/src/frama-c/plugins/eva/valuetable.tsx | 15 ++++++------
 5 files changed, 31 insertions(+), 43 deletions(-)

diff --git a/ivette/src/frama-c/plugins/eva/index.tsx b/ivette/src/frama-c/plugins/eva/index.tsx
index 1d862289c35..46b6fac4e5b 100644
--- a/ivette/src/frama-c/plugins/eva/index.tsx
+++ b/ivette/src/frama-c/plugins/eva/index.tsx
@@ -28,9 +28,11 @@
 import React from 'react';
 import * as Dome from 'dome';
 import * as Ivette from 'ivette';
+import * as Server from 'frama-c/server';
 import { Vfill } from 'dome/layout/boxes';
 import { IconButton } from 'dome/controls/buttons';
 import { AutoSizer } from 'react-virtualized';
+import { Model } from './model';
 
 // Locals
 import { ProbeInfos } from './probeinfos';
@@ -45,6 +47,10 @@ import './style.css';
 // --------------------------------------------------------------------------
 
 function ValuesComponent() {
+  const [model] = React.useState(() => new Model());
+  model.mount();
+  Dome.useUpdate(model.changed, model.laidout);
+  Server.onShutdown(() => model.unmount());
   const [zoom, setZoom] = Dome.useNumberSettings('eva-zoom-factor', 0);
   return (
     <>
@@ -61,19 +67,20 @@ function ValuesComponent() {
         />
       </Ivette.TitleBar>
       <Vfill>
-        <ProbeInfos />
+        <ProbeInfos model={model} />
         <Vfill>
           <AutoSizer>
             {(dim: Dimension) => (
               <ValuesPanel
                 zoom={zoom}
+                model={model}
                 {...dim}
               />
             )}
           </AutoSizer>
         </Vfill>
-        <AlarmsInfos />
-        <StackInfos />
+        <AlarmsInfos model={model} />
+        <StackInfos model={model} />
       </Vfill>
     </>
   );
diff --git a/ivette/src/frama-c/plugins/eva/model.ts b/ivette/src/frama-c/plugins/eva/model.ts
index a267c0e5de4..9d73106c3d8 100644
--- a/ivette/src/frama-c/plugins/eva/model.ts
+++ b/ivette/src/frama-c/plugins/eva/model.ts
@@ -280,26 +280,6 @@ export class Model implements ModelCallbacks {
 
 }
 
-// --------------------------------------------------------------------------
-// --- EVA Model Hook
-// --------------------------------------------------------------------------
-
-let MODEL: Model | undefined;
-
-Server.onShutdown(() => {
-  if (MODEL) {
-    MODEL.unmount();
-    MODEL = undefined;
-  }
-});
-
-export function useModel(): Model {
-  if (!MODEL) {
-    MODEL = new Model();
-    MODEL.mount();
-  }
-  Dome.useUpdate(MODEL.changed, MODEL.laidout);
-  return MODEL;
+export interface ModelProp {
+  model: Model;
 }
-
-// --------------------------------------------------------------------------
diff --git a/ivette/src/frama-c/plugins/eva/probeinfos.tsx b/ivette/src/frama-c/plugins/eva/probeinfos.tsx
index 230afeac91d..cd2686661c1 100644
--- a/ivette/src/frama-c/plugins/eva/probeinfos.tsx
+++ b/ivette/src/frama-c/plugins/eva/probeinfos.tsx
@@ -37,15 +37,15 @@ import * as States from 'frama-c/states';
 // Locals
 import { SizedArea } from './sized';
 import { sizeof } from './cells';
-import { useModel } from './model';
+import { ModelProp } from './model';
 import { Stmt } from './valueinfos';
 
 // --------------------------------------------------------------------------
 // --- Probe Editor
 // --------------------------------------------------------------------------
 
-function ProbeEditor() {
-  const model = useModel();
+function ProbeEditor(props: ModelProp) {
+  const { model } = props;
   const probe = model.getFocused();
   if (!probe || !probe.code) return null;
   const { label } = probe;
@@ -105,8 +105,8 @@ function ProbeEditor() {
 // --- Probe Panel
 // --------------------------------------------------------------------------
 
-export function ProbeInfos() {
-  const model = useModel();
+export function ProbeInfos(props: ModelProp) {
+  const { model } = props;
   const probe = model.getFocused();
   const fct = probe?.fct;
   const byCS = fct ? model.isByCallstacks(fct) : false;
@@ -117,7 +117,7 @@ export function ProbeInfos() {
   const vstmt = model.getVstmt();
   return (
     <Hpack className="eva-probeinfo">
-      <ProbeEditor />
+      <ProbeEditor model={model} />
       <Filler />
       <ButtonGroup
         enabled={!!probe}
diff --git a/ivette/src/frama-c/plugins/eva/valueinfos.tsx b/ivette/src/frama-c/plugins/eva/valueinfos.tsx
index d474b6ce9f5..0812d0f2b60 100644
--- a/ivette/src/frama-c/plugins/eva/valueinfos.tsx
+++ b/ivette/src/frama-c/plugins/eva/valueinfos.tsx
@@ -31,11 +31,11 @@ import { Hpack, Vpack } from 'dome/layout/boxes';
 import { Code, Cell } from 'dome/controls/labels';
 import * as States from 'frama-c/states';
 import * as Ast from 'frama-c/api/kernel/ast';
+import { ModelProp } from 'frama-c/plugins/eva/model';
 
 // Locals
 import { EvaAlarm } from './cells';
 import { Callsite } from './stacks';
-import { useModel } from './model';
 
 // --------------------------------------------------------------------------
 // --- Stmt Printer
@@ -66,8 +66,8 @@ export function Stmt(props: StmtProps) {
 // --- Alarms Panel
 // --------------------------------------------------------------------------
 
-export function AlarmsInfos() {
-  const model = useModel();
+export function AlarmsInfos(props: ModelProp) {
+  const { model } = props;
   const probe = model.getFocused();
   if (probe) {
     const callstack = model.getCallstack();
@@ -94,8 +94,8 @@ export function AlarmsInfos() {
 // --- Stack Panel
 // --------------------------------------------------------------------------
 
-export function StackInfos() {
-  const model = useModel();
+export function StackInfos(props: ModelProp) {
+  const { model } = props;
   const [, setSelection] = States.useSelection();
   const focused = model.getFocused();
   const callstack = model.getCalls();
diff --git a/ivette/src/frama-c/plugins/eva/valuetable.tsx b/ivette/src/frama-c/plugins/eva/valuetable.tsx
index 165e0788d82..577249f0764 100644
--- a/ivette/src/frama-c/plugins/eva/valuetable.tsx
+++ b/ivette/src/frama-c/plugins/eva/valuetable.tsx
@@ -33,6 +33,7 @@ import { Hpack, Filler } from 'dome/layout/boxes';
 import { Icon } from 'dome/controls/icons';
 import { Cell } from 'dome/controls/labels';
 import { IconButton } from 'dome/controls/buttons';
+import { ModelProp, Model } from 'frama-c/plugins/eva/model';
 
 // Frama-C
 import * as States from 'frama-c/states';
@@ -44,7 +45,6 @@ import { sizeof, EvaValues, EvaState } from './cells';
 import { Probe } from './probes';
 import { Row } from './layout';
 import { Callsite } from './stacks';
-import { useModel } from './model';
 import { Stmt } from './valueinfos';
 import './style.css';
 
@@ -91,7 +91,7 @@ function computeDiffs(
 // --- Table Cell
 // --------------------------------------------------------------------------
 
-interface TableCellProps {
+interface TableCellProps extends ModelProp {
   probe: Probe;
   row: Row;
 }
@@ -99,9 +99,8 @@ interface TableCellProps {
 const CELLPADDING = 12;
 
 function TableCell(props: TableCellProps) {
-  const model = useModel();
+  const { probe, row, model } = props;
   const [, setSelection] = States.useSelection();
-  const { probe, row } = props;
   const { kind, callstack } = row;
   const minWidth = CELLPADDING + WSIZER.dimension(probe.minCols);
   const maxWidth = CELLPADDING + WSIZER.dimension(probe.maxCols);
@@ -276,10 +275,11 @@ function TableHead(props: TableHeadProps) {
 interface TableRowProps {
   style: React.CSSProperties;
   index: number;
+  data: Model;
 }
 
 function TableRow(props: TableRowProps) {
-  const model = useModel();
+  const model = props.data;
   const row = model.getRow(props.index);
   if (!row) return null;
   const { kind, probes } = row;
@@ -319,6 +319,7 @@ function TableRow(props: TableRowProps) {
       key={probe.marker}
       probe={probe}
       row={row}
+      model={model}
     />
   );
   return (
@@ -348,11 +349,11 @@ export interface Dimension {
 
 export interface ValuesPanelProps extends Dimension {
   zoom: number;
+  model: Model;
 }
 
 export function ValuesPanel(props: ValuesPanelProps) {
-  const model = useModel();
-  const { zoom, width, height } = props;
+  const { zoom, width, height, model } = props;
   // --- reset line cache
   const listRef = React.useRef<VariableSizeList>(null);
   Dome.useEvent(model.laidout, () => {
-- 
GitLab