From b2e0be9fce7d87188603e9afe2786798644dc142 Mon Sep 17 00:00:00 2001 From: rlazarini <remi.lazarini@cea.fr> Date: Tue, 7 Jan 2025 16:25:35 +0100 Subject: [PATCH] [Ivette] added doc Callgraph + added color in markdown iconTag + added markdown ledTag --- .../src/dome/renderer/controls/displays.tsx | 7 +- ivette/src/dome/renderer/controls/icons.tsx | 7 +- ivette/src/dome/renderer/controls/style.css | 2 +- ivette/src/dome/renderer/style.css | 2 +- ivette/src/dome/renderer/text/markdown.tsx | 30 ++++- .../frama-c/plugins/callgraph/callgraph.css | 5 - .../plugins/callgraph/components/node.tsx | 18 ++- .../callgraph/components/threeStateButton.tsx | 10 +- .../plugins/callgraph/components/titlebar.tsx | 52 ++++---- .../plugins/callgraph/components/toolbar.tsx | 111 +++++++++++++++++- ivette/src/frama-c/plugins/plugins.md | 80 +++++++++++++ 11 files changed, 264 insertions(+), 60 deletions(-) create mode 100644 ivette/src/frama-c/plugins/plugins.md diff --git a/ivette/src/dome/renderer/controls/displays.tsx b/ivette/src/dome/renderer/controls/displays.tsx index 3363c1486fc..dc1c52d1479 100644 --- a/ivette/src/dome/renderer/controls/displays.tsx +++ b/ivette/src/dome/renderer/controls/displays.tsx @@ -62,8 +62,11 @@ export function LCD(props: LabelProps): JSX.Element { // --- Led // -------------------------------------------------------------------------- -export type LEDstatus = - undefined | 'inactive' | 'active' | 'positive' | 'negative' | 'warning'; +export const LEDStatusList = [ + 'active', 'inactive', 'positive', 'negative', 'warning' +] as const; + +export type LEDstatus = typeof LEDStatusList[number] | undefined; export interface LEDprops { /** diff --git a/ivette/src/dome/renderer/controls/icons.tsx b/ivette/src/dome/renderer/controls/icons.tsx index 3cfff841d62..e0055388c0f 100644 --- a/ivette/src/dome/renderer/controls/icons.tsx +++ b/ivette/src/dome/renderer/controls/icons.tsx @@ -95,8 +95,11 @@ export function SVG(props: SVGprops): null | JSX.Element { // --- Icon Component // -------------------------------------------------------------------------- -export type IconKind = - 'disabled' | 'selected' | 'positive' | 'negative' | 'warning' | 'default'; +export const iconKindList = [ + 'disabled', 'selected', 'positive', 'negative', 'warning', 'default' +] as const; + +export type IconKind = typeof iconKindList[number] /** Icon Component Properties */ export interface IconProps extends SVGprops { diff --git a/ivette/src/dome/renderer/controls/style.css b/ivette/src/dome/renderer/controls/style.css index 2d21b7bcd7c..126ed6edcd9 100644 --- a/ivette/src/dome/renderer/controls/style.css +++ b/ivette/src/dome/renderer/controls/style.css @@ -267,7 +267,7 @@ } .dome-xButton-led { - display: inline ; + display: inline-block ; position: relative ; border-color: var(--border) ; border-style: solid ; diff --git a/ivette/src/dome/renderer/style.css b/ivette/src/dome/renderer/style.css index 0c341b788d3..f3f86601cd0 100644 --- a/ivette/src/dome/renderer/style.css +++ b/ivette/src/dome/renderer/style.css @@ -278,7 +278,7 @@ input[type="checkbox"]:checked { } .dome-xModal-overlay { - z-index: 100; + z-index: 1000; position: fixed; top: 0; left: 0; diff --git a/ivette/src/dome/renderer/text/markdown.tsx b/ivette/src/dome/renderer/text/markdown.tsx index 8641cc6e7a7..64c924b2fbd 100644 --- a/ivette/src/dome/renderer/text/markdown.tsx +++ b/ivette/src/dome/renderer/text/markdown.tsx @@ -26,22 +26,37 @@ import remarkCustomHeaderId from 'remark-custom-header-id'; import * as Themes from 'dome/themes'; import { classes } from 'dome/misc/utils'; -import { Icon } from 'dome/controls/icons'; +import { Icon, iconKindList } from 'dome/controls/icons'; import { CodeBlock, atomOneDark, atomOneLight } from "react-code-blocks"; +import { LED, LEDStatusList } from 'dome/controls/displays'; + export interface Pattern { pattern: RegExp, replace: (key: number, match?: RegExpExecArray) => JSX.Element | null } export const iconTag: Pattern = { - pattern: /(\[ex:\])?\[icon-([^\]]+)\]/g, + pattern: /(\[ex:\])?\[icon-([^-\]]+)(-([^\]]+))?\]/g, replace: (key: number, match?: RegExpExecArray) => { - if(match && match[1] === "[ex:]") { - return <span key={key}>{`[icon-${match[2]}]`}</span>; + if(!match) return null; + const id = match[2]; + const kind = iconKindList.find(elt => elt === match[4]); + const color = !kind ? match[4] : undefined; + if(match[1] === "[ex:]") { + return <span key={key}>{`[icon-${id}-${match[4]}]`}</span>; } - return match ? <Icon key={key} id={match[2]}/> : null; + return <Icon key={key} id={id} kind={kind} fill={color}/>; + } +}; + +export const ledTag: Pattern = { + pattern: /\[led-([^\]]+)\]/g, + replace: (key: number, match?: RegExpExecArray) => { + if(!match) return null; + const status = LEDStatusList.find(elt => elt === match[1]); + return <LED key={key} status={status} />; } }; @@ -120,6 +135,7 @@ export function Markdown( const markdownClasses = classes( "dome-xMarkdown", "dome-pages", className ); + let liKey: number = 0; const scroll = (id: string): void => { const elt = document.getElementById(id); @@ -134,7 +150,9 @@ export function Markdown( }; options.components = { p: ({ children }) => <div>{replaceTags(children, patterns)}</div>, - li: ({ children }) => <li>{replaceTags(children, patterns)}</li>, + li: ({ children }) => { + return <li key={liKey++}>{replaceTags(children, patterns)}</li>; + }, /** Uses codeBlock if ``` is used in markdown with a language, * otherwise the code-inline class is added */ code: ({ className, children }) => { diff --git a/ivette/src/frama-c/plugins/callgraph/callgraph.css b/ivette/src/frama-c/plugins/callgraph/callgraph.css index 758c026c0ea..fb03f827b38 100644 --- a/ivette/src/frama-c/plugins/callgraph/callgraph.css +++ b/ivette/src/frama-c/plugins/callgraph/callgraph.css @@ -82,7 +82,6 @@ .node-graph { display: flex; align-items: center; - display: flex; background-color: rgb(from var(--background-profound) r g b / .5); padding: .5em; border-radius: .5em; @@ -96,10 +95,6 @@ &.node-selected { border: solid var(--activated-button-color) 2px; } - - .dome-xButton-led { - display: block; - } } .cg-display-mode { diff --git a/ivette/src/frama-c/plugins/callgraph/components/node.tsx b/ivette/src/frama-c/plugins/callgraph/components/node.tsx index 39eb1eb75b6..2eec2ab21f2 100644 --- a/ivette/src/frama-c/plugins/callgraph/components/node.tsx +++ b/ivette/src/frama-c/plugins/callgraph/components/node.tsx @@ -42,16 +42,14 @@ const isTaintedScope = (node: NodeObject3D<CGNode>): boolean => { const getNodeAlarms = (node: CGNode): JSX.Element => { return <> - <div> - {node.alarmStatuses && node.alarmStatuses.invalid > 0 && LED({ - status: "negative", - title: node.alarmStatuses.invalid+" invalid", - })} - {node.alarmStatuses && node.alarmStatuses.unknown > 0 && LED({ - status: "warning", - title: node.alarmStatuses.unknown+" unknown", - })} - </div> + {node.alarmStatuses && node.alarmStatuses.invalid > 0 && LED({ + status: "negative", + title: node.alarmStatuses.invalid+" invalid", + })} + {node.alarmStatuses && node.alarmStatuses.unknown > 0 && LED({ + status: "warning", + title: node.alarmStatuses.unknown+" unknown", + })} </>; }; diff --git a/ivette/src/frama-c/plugins/callgraph/components/threeStateButton.tsx b/ivette/src/frama-c/plugins/callgraph/components/threeStateButton.tsx index 83eea62d370..fc104952a46 100644 --- a/ivette/src/frama-c/plugins/callgraph/components/threeStateButton.tsx +++ b/ivette/src/frama-c/plugins/callgraph/components/threeStateButton.tsx @@ -32,14 +32,16 @@ export interface IThreeStateButton { value: number, } +export type TThreesButtonState = [ + IThreeStateButton, + (newValue: IThreeStateButton) => void +]; + interface ThreeStateButtonProps { label?: string; icon?: string; title?: string; - buttonState: [ - IThreeStateButton, - (newValue: IThreeStateButton) => void - ]; + buttonState: TThreesButtonState; } export function ThreeStateButton( diff --git a/ivette/src/frama-c/plugins/callgraph/components/titlebar.tsx b/ivette/src/frama-c/plugins/callgraph/components/titlebar.tsx index a83e80787b7..6ecf4e8128f 100644 --- a/ivette/src/frama-c/plugins/callgraph/components/titlebar.tsx +++ b/ivette/src/frama-c/plugins/callgraph/components/titlebar.tsx @@ -26,30 +26,26 @@ import * as Ivette from 'ivette'; import * as Dome from 'dome'; import { IconButton } from 'dome/controls/buttons'; -import { Inset } from 'dome/frame/toolbars'; -import * as Dialogs from 'dome/dialogs'; +import { Button, Inset } from 'dome/frame/toolbars'; +import { HelpIcon } from 'dome/help'; +import docPlugins from '../../plugins.md?raw'; +import { DocShowNodesButton } from './toolbar'; +import { ledTag, iconTag } from 'dome/text/markdown'; -// Help popup -async function displayShortcuts(): Promise<void> { - await Dialogs.showMessageBox({ - buttons: [{ label: "Ok" }], - details: ( - 'In the graph:\n' + - ' - Left-click: rotate the graph\n' + - ' - Right-click: move in the graph\n' + - ' - Mouse-wheel: zoom\n' + - '\n' + - 'On nodes:\n' + - ' - Left-Click: select node (in the graph)\n'+ - ' - Ctrl+click: add node to the selected nodes (multi-selection)\n' + - ' - Alt+click: select function (in all Ivette components)\n' + - '\n' + - 'Function filters (in the titlebar of this component) are synchronized ' + - 'with the filter of the functions sidebar.' - ), - message: 'Callgraph Help', - }); -} +export const TSButtonTag = { + pattern: /\[button-displaymode\]/g, + replace: (key: number, match?: RegExpExecArray) => { + return match ? <span key={key}>{DocShowNodesButton()}</span> : null; + } +}; + +export const selectBtnTag = { + pattern: /\[button-select\]/g, + replace: (key: number, match?: RegExpExecArray) => { + return match ? <Button key={key} label="Select" title={`Nodes selection`}/> + : null; + } +}; /* -------------------------------------------------------------------------- */ /* --- Callgraph titlebar component --- */ @@ -89,11 +85,11 @@ export function CallgraphTitleBar(props: CallgraphTitleBarProps): JSX.Element { title={"Automatically select node of the function selected in AST"} /> <Inset /> - <IconButton - icon="HELP" - onClick={displayShortcuts} - title='Callgraph help' - /> + <HelpIcon + label='Plugins - Callgraph' + scrollTo={'plugins-callgraph'} + patterns={[iconTag, ledTag, selectBtnTag, TSButtonTag]} + >{ docPlugins }</HelpIcon> <Inset /> </Ivette.TitleBar> ); diff --git a/ivette/src/frama-c/plugins/callgraph/components/toolbar.tsx b/ivette/src/frama-c/plugins/callgraph/components/toolbar.tsx index 2154ae91ebc..9f495ab78da 100644 --- a/ivette/src/frama-c/plugins/callgraph/components/toolbar.tsx +++ b/ivette/src/frama-c/plugins/callgraph/components/toolbar.tsx @@ -27,16 +27,125 @@ import * as Dome from 'dome'; import { State } from 'dome/data/states'; import { Spinner } from 'dome/controls/buttons'; import { ToolBar, ButtonGroup, Button, Filler } from 'dome/frame/toolbars'; +import * as Themes from 'dome/themes'; import { ModeDisplay, SelectedNodesData } from "frama-c/plugins/callgraph/definitions"; -import { IThreeStateButton, ThreeStateButton } from "./threeStateButton"; +import { + IThreeStateButton, ThreeStateButton, TThreesButtonState +} from "./threeStateButton"; /* -------------------------------------------------------------------------- */ /* --- Callgraph Toolsbar component --- */ /* -------------------------------------------------------------------------- */ +interface ShowNodesButtonProps { + displayModeState: [ModeDisplay, (newValue: ModeDisplay) => void], + selectedParentsState: TThreesButtonState, + selectedChildrenState: TThreesButtonState, +} + +function ShowNodesButton(props: ShowNodesButtonProps): JSX.Element { + const { + displayModeState, selectedParentsState, selectedChildrenState + } = props; + const [ displayMode, setDisplayMode] = displayModeState; + + return ( + <ButtonGroup> + <Button + label='all' + title='show all nodes' + selected={displayMode === 'all'} + onClick={() => setDisplayMode("all")} + /> + <Button + label='linked' + title='only show nodes linked to the selected ones' + selected={displayMode === 'linked'} + onClick={() => setDisplayMode("linked")} + /> + <Button + label='selected' + title='only show selected nodes, their parents and their childrens' + selected={displayMode === 'selected'} + onClick={() => setDisplayMode("selected")} + /> + { displayMode === "selected" ? ( + <> + <ThreeStateButton + label={"Parents"} + title={"Choose how many parents you want to see."} + buttonState={selectedParentsState} + /> + <ThreeStateButton + label={"Children"} + title={"Choose how many children you want to see."} + buttonState={selectedChildrenState} + /> + </> + ) : <></> + } + </ButtonGroup> + ); +} + +export function DocShowNodesButton(): JSX.Element { + const displayModeState = React.useState<ModeDisplay>("all"); + const selectedParentsState = React.useState<IThreeStateButton>( + { active: false, max: false, value: 1 }); + const selectedChildrenState = React.useState<IThreeStateButton>( + { active: true, max: true, value: 1 }); + const [ displayMode, ] = displayModeState; + const [ parent, ] = selectedParentsState; + const [ children, ] = selectedChildrenState; + + const style = Themes.useStyle(); + const infosStyle = { color: style.getPropertyValue('--text-highlighted') }; + + function getDocSelected( + parent: IThreeStateButton, + children: IThreeStateButton + ):JSX.Element { + function getDocTSB(name: string, tsb: IThreeStateButton):string { + return !tsb.active ? '' : + tsb.max ? ` all ${name}` : + tsb.value > 0 ? + (tsb.value+' level'+(tsb.value > 1 ? 's':'')+` of ${name}`): + ""; + } + const p = getDocTSB('parents', parent); + const c = getDocTSB('children', children); + + return ( + <div style={infosStyle}> + Selected nodes displayed { (p || c) && " with " } + { p }{ p && c && " and " }{ c } + { !p && !c && " only " }. + </div> + ); + } + + const docAll = <div style={infosStyle}>All nodes displayed.</div>; + const docLinked = <div style={infosStyle}>Hide unlinked nodes.</div>; + const docSelected = getDocSelected(parent, children); + + return ( + <> + <ShowNodesButton + displayModeState={displayModeState} + selectedParentsState={selectedParentsState} + selectedChildrenState={selectedChildrenState} + /> + { displayMode === 'all' ? docAll : + displayMode === 'linked' ? docLinked : + docSelected + } + </> + ); +} + interface CallgraphToolsBarProps { /* eslint-disable max-len */ displayModeState: [ModeDisplay, (newValue: ModeDisplay) => void], diff --git a/ivette/src/frama-c/plugins/plugins.md b/ivette/src/frama-c/plugins/plugins.md new file mode 100644 index 00000000000..896c3185a36 --- /dev/null +++ b/ivette/src/frama-c/plugins/plugins.md @@ -0,0 +1,80 @@ +# Plugins {#plugins} + +## Callgraph {#plugins-callgraph} + +This module provides a graphical display of the callgraph and makes it easy to highlight certain data, such as : + +* The location of unproven properties. +* Functions containing tainted properties. +* ... + +Below is a list of shortcuts: + +* In the graph: + * Left-click: rotate the graph + * Right-click: move in the graph + * Mouse-wheel: zoom +* On nodes: + * Left-Click: select node (in the graph) + * Ctrl+click: add node to the selected nodes (multi-selection) + * Alt+click: select function (in all Ivette components) + +This component is divided into 4 parts, the [titlebar](#plugins-callgraph-titlebar), the [toolbar](#plugins-callgraph-toolbar), a [panel](#plugins-callgraph-panel) and a [graph](#plugins-callgraph-graph). + +### Titlebar {#plugins-callgraph-titlebar} + +The titlebar contains the name of the module on the left and the following buttons on the right: + +* [icon-TUNINGS] : Filter functions appearing in the graph. Ce filtre est synchronisé avec celui de la sidebar. +* [icon-TARGET] : Move the camera to show each node after each render. +* [icon-PIN] : Automatically select node of the function selected in AST. +* [icon-HELP] : show this help modal. + +### Toolbar {#plugins-callgraph-toolbar} + +The toolbar contains display and selection parameters on the left and graph management parameters on the right. +On the far right is the button for opening the side panel. + +On the left, there is a group of buttons for selecting the nodes that will appear in the graph : + +* The first group of buttons is used to select the nodes that will appear in the graph... + * Try yourself : [button-displaymode] +* [button-select] :This button allows you to select a list of predefined nodes (nodes with unproven properties, with tainted variables, etc.). + +On the right: + +* Horizontal and vertical distance management between graph nodes. +* [icon-SIDEBAR] : Opens or closes the side panel. + +### Panel {#plugins-callgraph-panel} + +The panel displays additional information about the graph in general and about the properties of the selected nodes. +The filters above the list can be used to limit the amount of information, and are synchronised with the filters in the `Properties` component. + +At the top right, 2 buttons allow you to change the side of the panel and close it. + +### Graph {#plugins-callgraph-graph} + +The graph is in 3D but is displayed as a tree. This type of display prevents cycles from appearing in the graph. + +If a cycle is detected : + +* In the case of a recursive function: The link is deleted and the [icon-REDO-orange] icon is added to the node. +* In the case of a cycle on several functions: The cycles will be pre-selected and will appear in the selection button. + +#### Nodes + +The nodes display the name of the function and the following elements: + +* [led-warning] : The function contains unproven properties, a tooltip gives the quantity. +* [led-negative] : The function contains false properties, a tooltip gives the quantity. +* [icon-REDO-orange] : The function is recursive. +* [icon-DROP.FILLED-#882288][icon-DROP.FILLED-#73BBBB]: The function contains tainted properties. + +#### Edges + +The edges are oriented and can take on different colours depending on the nodes selected. + +* Green: the edge connects 2 selected nodes. +* Red: the edge links a selected node and one of its parents. +* Blue: the edge links a selected node and one of its children. -- GitLab