From 9a2c6edf12c89f93187f9760ec9cf7e5b9037781 Mon Sep 17 00:00:00 2001
From: rlazarini <remi.lazarini@cea.fr>
Date: Mon, 26 Aug 2024 14:26:08 +0200
Subject: [PATCH] [Ivette] Properties : add hook for menu filter + change
 DEFAULTS filters structure

---
 ivette/src/frama-c/kernel/Globals.tsx    |   4 +-
 ivette/src/frama-c/kernel/Properties.tsx | 298 ++++++++++++++---------
 2 files changed, 179 insertions(+), 123 deletions(-)

diff --git a/ivette/src/frama-c/kernel/Globals.tsx b/ivette/src/frama-c/kernel/Globals.tsx
index 71b6a277da8..422acc87666 100644
--- a/ivette/src/frama-c/kernel/Globals.tsx
+++ b/ivette/src/frama-c/kernel/Globals.tsx
@@ -82,8 +82,8 @@ function resetMode(enabled: boolean): void {
 // --- Menu item
 // --------------------------------------------------------------------------
 
-type setting = [boolean, () => void]
-function menuItem(label: string, [b, flip]: setting, enabled?: boolean)
+export type setting = [boolean, () => void]
+export function menuItem(label: string, [b, flip]: setting, enabled?: boolean)
   : Dome.PopupMenuItem {
   return {
     label: label,
diff --git a/ivette/src/frama-c/kernel/Properties.tsx b/ivette/src/frama-c/kernel/Properties.tsx
index 7ef5a843009..8bbce5a7e7c 100644
--- a/ivette/src/frama-c/kernel/Properties.tsx
+++ b/ivette/src/frama-c/kernel/Properties.tsx
@@ -43,6 +43,7 @@ import { Table, Column, ColumnProps, Renderer } from 'dome/table/views';
 import { RSplit } from 'dome/layout/splitters';
 
 import { TitleBar } from 'ivette';
+import { menuItem, setting } from './Globals';
 
 
 import * as Ast from 'frama-c/kernel/api/ast';
@@ -58,54 +59,87 @@ type Property = Properties.statusData |
 // --------------------------------------------------------------------------
 // --- Filters
 // --------------------------------------------------------------------------
+export enum EFilterType {
+  STATUS = "status",
+  KIND = "kind",
+  SOURCE = "source",
+  ALARMS = "alarms",
+  EVA = "eva"
+}
+export type TFilterType = `${EFilterType}`;
+
+interface IFilterContent {
+  value: boolean;
+  label: string;
+  title?: string;
+}
+
+function newFilter(
+  value: boolean,
+  label: string,
+  title?: string,
+): IFilterContent {
+  return { value, label, title };
+}
 
-const DEFAULTS: { [key: string]: boolean } = {
-  'currentScope': false,
-  'status.valid': true,
-  'status.valid_hyp': true,
-  'status.unknown': true,
-  'status.invalid': true,
-  'status.invalid_hyp': true,
-  'status.considered_valid': false,
-  'status.untried': false,
-  'status.dead': false,
-  'status.inconsistent': true,
-  'kind.assert': true,
-  'kind.invariant': true,
-  'kind.variant': true,
-  'kind.requires': true,
-  'kind.ensures': true,
-  'kind.instance': true,
-  'kind.assumes': true,
-  'kind.assigns': true,
-  'kind.froms': true,
-  'kind.allocates': true,
-  'kind.behavior': false,
-  'kind.reachable': false,
-  'kind.axiomatic': true,
-  'kind.pragma': true,
-  'kind.others': true,
-  'source.alarms': true, // show properties that are alarms
-  'source.others': true, // show properties that are not alarms
-  'alarms.overflow': true,
-  'alarms.division_by_zero': true,
-  'alarms.mem_access': true,
-  'alarms.index_bound': true,
-  'alarms.pointer_value': true,
-  'alarms.shift': true,
-  'alarms.ptr_comparison': true,
-  'alarms.differing_blocks': true,
-  'alarms.separation': true,
-  'alarms.overlap': true,
-  'alarms.initialization': true,
-  'alarms.dangling_pointer': true,
-  'alarms.special_float': true,
-  'alarms.float_to_int': true,
-  'alarms.function_pointer': true,
-  'alarms.bool_value': true,
-  'eva.priority_only': false,
-  'eva.data_tainted_only': false,
-  'eva.ctrl_tainted_only': false,
+const DEFAULTS: { [key: string]: IFilterContent } = {
+  'currentScope': newFilter(false, "Current scope"),
+   /** status */
+  'status.valid': newFilter(true, "Valid"),
+  'status.valid_hyp': newFilter(true, "Valid under hyp."),
+  'status.unknown': newFilter(true, "Unknown"),
+  'status.invalid': newFilter(true, "Invalid"),
+  'status.invalid_hyp': newFilter(true, "Invalid under hyp."),
+  'status.considered_valid': newFilter(false, "Considered valid"),
+  'status.untried': newFilter(false, "Untried"),
+  'status.dead': newFilter(false, "Dead"),
+  'status.inconsistent': newFilter(true, "Inconsistent"),
+  /** kind */
+  'kind.assert': newFilter(true, "Assertions"),
+  'kind.invariant': newFilter(true, "Invariants"),
+  'kind.variant': newFilter(true, "Variants"),
+  'kind.requires': newFilter(true, "Preconditions"),
+  'kind.ensures': newFilter(true, "Postconditions"),
+  'kind.instance': newFilter(true, "Instance"),
+  'kind.assigns': newFilter(true, "Assigns clauses"),
+  'kind.froms': newFilter(true, "From clauses"),
+  'kind.allocates': newFilter(true, "Allocates"),
+  'kind.behavior': newFilter(false, "Behaviors"),
+  'kind.reachable': newFilter(false, "Reachables"),
+  'kind.axiomatic': newFilter(true, "Axiomatics"),
+  'kind.pragma': newFilter(true, "Pragma"),
+  'kind.assumes': newFilter(true, "Assumes"),
+  'kind.others': newFilter(true, "Others"),
+  /** source */
+  'source.alarms': newFilter(true, "Alarms"),
+  'source.others': newFilter(true, "Others"),
+  /** alarms */
+  'alarms.overflow': newFilter(true, "Overflows"),
+  'alarms.division_by_zero': newFilter(true, "Divisions by zero"),
+  'alarms.shift': newFilter(true, "Shifts"),
+  'alarms.special_float': newFilter(true, "Special floats"),
+  'alarms.float_to_int': newFilter(true, "Float to int"),
+  'alarms.bool_value': newFilter(true, "_Bool values"),
+  'alarms.mem_access': newFilter(true, "Memory accesses"),
+  'alarms.index_bound': newFilter(true, "Index bounds"),
+  'alarms.initialization': newFilter(true, "Initializations"),
+  'alarms.dangling_pointer': newFilter(true, "Dangling pointers"),
+  'alarms.pointer_value': newFilter(true, "Pointer values"),
+  'alarms.function_pointer': newFilter(true, "Function pointers"),
+  'alarms.ptr_comparison': newFilter(true, "Pointer comparisons"),
+  'alarms.differing_blocks': newFilter(true, "Differing blocks"),
+  'alarms.separation': newFilter(true, "Separations"),
+  'alarms.overlap': newFilter(true, "Overlaps"),
+  /** eva */
+  'eva.priority_only': newFilter(false, "High-priority only",
+    "Show only high-priority properties for the Eva analysis"
+  ),
+  'eva.data_tainted_only': newFilter(false, "Data-tainted only",
+    "Show only data-tainted properties according to the Eva taint domain"
+  ),
+  'eva.ctrl_tainted_only': newFilter(false, "Control-tainted only",
+    "Show only control-tainted properties according to the Eva taint domain"
+  ),
 };
 
 function filter(path: string): boolean {
@@ -113,15 +147,15 @@ function filter(path: string): boolean {
   return Settings.getWindowSettings(
     `ivette.properties.filter.${path}`,
     Json.jBoolean,
-    defaultValue,
+    defaultValue.value,
   );
 }
 
-function useFilter(path: string): [boolean, () => void] {
+export function useFilter(path: string): [boolean, () => void] {
   const defaultValue = DEFAULTS[path] ?? true;
   return Dome.useFlipSettings(
     `ivette.properties.filter.${path}`,
-    defaultValue,
+    defaultValue.value,
   );
 }
 
@@ -143,7 +177,7 @@ function useFilterStr(path: string): Form.FieldState<string> {
 function resetFilters(prefix: string, b?: boolean) : void {
   for (const key in DEFAULTS) {
     if (key.startsWith(prefix)) {
-      const target = b ?? DEFAULTS[key];
+      const target = b ?? DEFAULTS[key].value;
       const path = `ivette.properties.filter.${key}`;
       Settings.setWindowSettings(path, target);
     }
@@ -280,6 +314,79 @@ function filterProperty(p: Property): boolean {
     && filterNames(p.names);
 }
 
+export interface IFilterState {
+  contextMenu: Dome.PopupMenuItem[],
+  show: (status: Properties.statusData) => boolean
+}
+
+function getContextMenu(
+  type: TFilterType,
+  filters: {[key: string]: setting}
+):Dome.PopupMenuItem[] {
+  const typeStr = type+".";
+  return Object.entries(DEFAULTS)
+    .filter(([k, ]) => k.startsWith(typeStr))
+    .map(([k, elt]) => menuItem(elt.label, filters[k.replace(typeStr, "")]));
+}
+
+export function useStatusFilter(): IFilterState {
+  const filters: {[key: string]: setting} = {};
+
+  filters.valid = useFilter('status.valid');
+  filters.validHyp = useFilter('status.valid_hyp');
+  filters.unknown = useFilter('status.unknown');
+  filters.invalid = useFilter('status.invalid');
+  filters.invalidHyp = useFilter('status.invalid_hyp');
+  filters.consideredValid = useFilter('status.considered_valid');
+  filters.untried = useFilter('status.untried');
+  filters.dead = useFilter('status.dead');
+  filters.inconsistent = useFilter('status.inconsistent');
+
+  return {
+    contextMenu: getContextMenu(EFilterType.STATUS, filters),
+    show: (statusData: Property) =>
+      filterStatus(statusData.status),
+  };
+}
+
+export function useKindPropertiesFilter(): IFilterState {
+  const filters: {[key: string]: setting} = {};
+
+  filters.assert = useFilter('kind.assert');
+  filters.invariant = useFilter('kind.invariant');
+  filters.variant = useFilter('kind.variant');
+  filters.requires = useFilter('kind.requires');
+  filters.ensures = useFilter('kind.ensures');
+  filters.instance = useFilter('kind.instance');
+  filters.assigns = useFilter('kind.assigns');
+  filters.froms = useFilter('kind.froms');
+  filters.allocates = useFilter('kind.allocates');
+  filters.behavior = useFilter('kind.behavior',);
+  filters.reachable = useFilter('kind.reachable',);
+  filters.axiomatic = useFilter('kind.axiomatic');
+  filters.pragma = useFilter('kind.pragma');
+  filters.assumes = useFilter('kind.assumes');
+  filters.others = useFilter('kind.others');
+
+  return {
+    contextMenu: getContextMenu(EFilterType.KIND, filters),
+    show: (statusData: Property) => filterKind(statusData.kind),
+  };
+}
+
+export function useEvaPropertiesFilter(): IFilterState {
+  const filters: {[key: string]: setting} = {};
+
+  filters.priorityOnly = useFilter('eva.priority_only');
+  filters.dataTaintedOnly = useFilter('eva.data_tainted_only');
+  filters.ctrlTaintedOnly = useFilter('eva.ctrl_tainted_only');
+
+  return {
+    contextMenu: getContextMenu(EFilterType.EVA, filters),
+    show: (statusData: Property) => filterEva(statusData),
+  };
+}
+
 // --------------------------------------------------------------------------
 // --- Property Columns
 // --------------------------------------------------------------------------
@@ -422,12 +529,12 @@ const Reload = new Dome.Event('ivette.properties.reload');
 
 interface SectionProps {
   label: string;
-  prefix?: string;
+  prefix?: TFilterType;
   unfold?: boolean;
   children: React.ReactNode;
 }
 
-function onContextMenu(prefix: string): void {
+export function onContextMenu(prefix: string): void {
   const items: Dome.PopupMenuItem[] = [
     {
       label: 'Reset to default',
@@ -503,9 +610,19 @@ function PropertyFilter(): JSX.Element {
     return "At least 2 characters";
   };
 
+  const getCheckBox = (type: TFilterType): JSX.Element => {
+    return <> {
+        Object.entries(DEFAULTS)
+          .filter(([key, ]) => key.startsWith(type+"."))
+          .map(([key, elt]) =>
+            <CheckField key={key} label={elt.label} path={key} title={elt.title}/>
+          )
+    }</>;
+  };
+
   return (
     <Scroll>
-      <CheckField label="Current scope" path="currentScope" />
+      <CheckField label={DEFAULTS.currentScope.label} path="currentScope" />
       <Section
         label="Search"
         defaultUnfold={true}
@@ -524,73 +641,12 @@ function PropertyFilter(): JSX.Element {
           checker={checkerNames}
         />
       </Section>
-      <FilterSection label="Status" prefix="status" unfold>
-        <CheckField label="Valid" path="status.valid" />
-        <CheckField label="Valid under hyp." path="status.valid_hyp" />
-        <CheckField label="Unknown" path="status.unknown" />
-        <CheckField label="Invalid" path="status.invalid" />
-        <CheckField label="Invalid under hyp." path="status.invalid_hyp" />
-        <CheckField label="Considered valid" path="status.considered_valid" />
-        <CheckField label="Untried" path="status.untried" />
-        <CheckField label="Dead" path="status.dead" />
-        <CheckField label="Inconsistent" path="status.inconsistent" />
-      </FilterSection>
-      <FilterSection label="Property kind" prefix="kind">
-        <CheckField label="Assertions" path="kind.assert" />
-        <CheckField label="Invariants" path="kind.invariant" />
-        <CheckField label="Variants" path="kind.variant" />
-        <CheckField label="Preconditions" path="kind.requires" />
-        <CheckField label="Postconditions" path="kind.ensures" />
-        <CheckField label="Instance" path="kind.instance" />
-        <CheckField label="Assigns clauses" path="kind.assigns" />
-        <CheckField label="From clauses" path="kind.froms" />
-        <CheckField label="Allocates" path="kind.allocates" />
-        <CheckField label="Behaviors" path="kind.behavior" />
-        <CheckField label="Reachables" path="kind.reachable" />
-        <CheckField label="Axiomatics" path="kind.axiomatic" />
-        <CheckField label="Pragma" path="kind.pragma" />
-        <CheckField label="Assumes" path="kind.assumes" />
-        <CheckField label="Others" path="kind.others" />
-      </FilterSection>
-      <FilterSection label="Source" prefix="source">
-        <CheckField label="Alarms" path="source.alarms" />
-        <CheckField label="Others" path="source.others" />
-      </FilterSection>
-      <FilterSection label="Alarms kind" prefix="alarms">
-        <CheckField label="Overflows" path="alarms.overflow" />
-        <CheckField label="Divisions by zero" path="alarms.division_by_zero" />
-        <CheckField label="Shifts" path="alarms.shift" />
-        <CheckField label="Special floats" path="alarms.special_float" />
-        <CheckField label="Float to int" path="alarms.float_to_int" />
-        <CheckField label="_Bool values" path="alarms.bool_value" />
-        <CheckField label="Memory accesses" path="alarms.mem_access" />
-        <CheckField label="Index bounds" path="alarms.index_bound" />
-        <CheckField label="Initializations" path="alarms.initialization" />
-        <CheckField label="Dangling pointers" path="alarms.dangling_pointer" />
-        <CheckField label="Pointer values" path="alarms.pointer_value" />
-        <CheckField label="Function pointers" path="alarms.function_pointer" />
-        <CheckField label="Pointer comparisons" path="alarms.ptr_comparison" />
-        <CheckField label="Differing blocks" path="alarms.differing_blocks" />
-        <CheckField label="Separations" path="alarms.separation" />
-        <CheckField label="Overlaps" path="alarms.overlap" />
-      </FilterSection>
-      <FilterSection label="Eva">
-        <CheckField
-          label="High-priority only"
-          path="eva.priority_only"
-          title="Show only high-priority properties for the Eva analysis"
-        />
-        <CheckField
-          label="Data-tainted only"
-          path="eva.data_tainted_only"
-          title="Show only data-tainted properties according to the Eva taint domain"
-        />
-        <CheckField
-          label="Control-tainted only"
-          path="eva.ctrl_tainted_only"
-          title="Show only control-tainted properties according to the Eva taint domain"
-        />
-      </FilterSection>
+
+      <FilterSection label="Status" prefix="status" unfold> { getCheckBox("status") } </FilterSection>
+      <FilterSection label="Property kind" prefix="kind"> { getCheckBox("kind") } </FilterSection>
+      <FilterSection label="Source" prefix="source"> { getCheckBox("source") } </FilterSection>
+      <FilterSection label="Alarms kind" prefix="alarms"> { getCheckBox("alarms") } </FilterSection>
+      <FilterSection label="Eva"> { getCheckBox("eva") } </FilterSection>
     </Scroll>
   );
 }
-- 
GitLab