aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--static/panes/cfg-view.interfaces.ts20
-rw-r--r--static/panes/cfg-view.ts351
-rw-r--r--static/panes/pane.ts5
3 files changed, 175 insertions, 201 deletions
diff --git a/static/panes/cfg-view.interfaces.ts b/static/panes/cfg-view.interfaces.ts
index 9f37b2411..5683354f2 100644
--- a/static/panes/cfg-view.interfaces.ts
+++ b/static/panes/cfg-view.interfaces.ts
@@ -22,15 +22,17 @@
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
-export interface CfgState {
- id: number;
- editorid?: number;
- treeid?: number;
+import {PaneState} from './pane.interfaces';
+import * as vis from 'vis-network';
+
+export interface CfgOptions {
+ physics?: boolean;
+ navigation?: boolean;
+}
+
+export interface CfgState extends PaneState {
selectedFn?: string;
- pos: any; // vis.Network.Position
+ pos: vis.Position;
scale: number;
- options?: {
- physics?: boolean;
- navigation?: boolean;
- };
+ options?: CfgOptions;
}
diff --git a/static/panes/cfg-view.ts b/static/panes/cfg-view.ts
index 12cb89ae3..a54c447d7 100644
--- a/static/panes/cfg-view.ts
+++ b/static/panes/cfg-view.ts
@@ -24,17 +24,17 @@
import * as vis from 'vis-network';
import _ from 'underscore';
-import {Toggles} from '../widgets/toggles';
import {ga} from '../analytics';
+import {Toggles} from '../widgets/toggles';
import TomSelect from 'tom-select';
import {Container} from 'golden-layout';
-import {CfgState} from './cfg-view.interfaces';
-import {PaneRenaming} from '../widgets/pane-renaming';
+import {CfgOptions, CfgState} from './cfg-view.interfaces';
import {Hub} from '../hub';
+import {Pane} from './pane';
interface NodeInfo {
edges: string[];
- dagEdges: number[];
+ dagEdges: string[];
index: string;
id: number;
level: number;
@@ -42,10 +42,7 @@ interface NodeInfo {
inCount: number;
}
-export class Cfg {
- container: Container;
- eventHub: any;
- domRoot: JQuery;
+export class Cfg extends Pane<CfgState> {
defaultCfgOutput: object;
llvmCfgPlaceholder: object;
binaryModeSupport: object;
@@ -53,31 +50,21 @@ export class Cfg {
savedScale: any;
needsMove: boolean;
currentFunc: string;
- functions: {[key: string]: any};
- networkOpts: any;
- cfgVisualiser: any;
- compilerId: number;
- _compilerName = '';
- _editorid?: number;
- _treeid?: number;
+ functions: Record<string, vis.Data>;
+ networkOpts: vis.Options;
+ cfgVisualiser: vis.Network;
_binaryFilter: boolean;
functionPicker: TomSelect;
- supportsCfg = false;
toggles: Toggles;
toggleNavigationButton: JQuery;
toggleNavigationTitle: string;
togglePhysicsButton: JQuery;
togglePhysicsTitle: string;
- topBar: JQuery;
- paneName: string;
- paneRenaming: PaneRenaming;
+ options: Required<CfgOptions>;
constructor(hub: Hub, container: Container, state: CfgState) {
- this.container = container;
- this.eventHub = hub.createEventHub();
- this.domRoot = container.getElement();
- this.domRoot.html($('#cfg').html());
- this.defaultCfgOutput = {nodes: [{id: 0, shape: 'box', label: 'No Output'}], edges: []};
+ super(hub, container, state);
+
this.llvmCfgPlaceholder = {
nodes: [
{
@@ -98,9 +85,7 @@ export class Cfg {
],
edges: [],
};
- // Note that this might be outdated if no functions were present when creating the link, but that's handled
- // by selectize
- state.options = state.options || {};
+
this.savedPos = state.pos;
this.savedScale = state.scale;
this.needsMove = this.savedPos && this.savedScale;
@@ -108,90 +93,43 @@ export class Cfg {
this.currentFunc = state.selectedFn || '';
this.functions = {};
- this.networkOpts = {
- autoResize: true,
- locale: 'en',
- edges: {
- arrows: {to: {enabled: true}},
- smooth: {
- enabled: true,
- type: 'dynamic',
- roundness: 1,
- },
- physics: true,
- },
- nodes: {
- font: {face: 'Consolas, "Liberation Mono", Courier, monospace', align: 'left'},
- },
- layout: {
- hierarchical: {
- enabled: true,
- direction: 'UD',
- nodeSpacing: 100,
- levelSeparation: 150,
- },
- },
- physics: {
- enabled: !!state.options.physics,
- hierarchicalRepulsion: {
- nodeDistance: 160,
- },
- },
- interaction: {
- navigationButtons: !!state.options.navigation,
- keyboard: {
- enabled: true,
- speed: {x: 10, y: 10, zoom: 0.03},
- bindToWindow: false,
- },
- },
- };
-
- this.cfgVisualiser = new vis.Network(
- this.domRoot.find('.graph-placeholder')[0],
- this.defaultCfgOutput,
- this.networkOpts
- );
-
- this.initButtons(state);
-
- this.compilerId = state.id;
- this._editorid = state.editorid;
- this._treeid = state.treeid;
this._binaryFilter = false;
const pickerEl = this.domRoot.find('.function-picker')[0] as HTMLInputElement;
- this.functionPicker = new TomSelect(
- pickerEl,
- {
- sortField: 'name',
- valueField: 'name',
- labelField: 'name',
- searchField: ['name'],
- dropdownParent: 'body',
- plugins: ['input_autogrow'],
- onChange: (val: string) => {
+ this.functionPicker = new TomSelect(pickerEl, {
+ sortField: 'name',
+ valueField: 'name',
+ labelField: 'name',
+ searchField: ['name'],
+ dropdownParent: 'body',
+ plugins: ['input_autogrow'],
+ onChange: (e: any) => {
+ // TomSelect says it's an Event, but we receive strings
+ const val = e as string;
+ if (val in this.functions) {
const selectedFn = this.functions[val];
- if (selectedFn) {
- this.currentFunc = val;
- this.showCfgResults({
- nodes: selectedFn.nodes,
- edges: selectedFn.edges,
- });
+ this.currentFunc = val;
+ this.showCfgResults({
+ nodes: selectedFn.nodes,
+ edges: selectedFn.edges,
+ });
+ if (selectedFn.nodes && selectedFn.nodes.length > 0) {
this.cfgVisualiser.selectNodes([selectedFn.nodes[0].id]);
- this.resize();
- this.saveState();
}
- },
- } as any
- // The current version of tom-select has a very restrictive type definition
- // that forces to pass the whole options object. This is a workaround to make it type check
- );
-
- this.paneRenaming = new PaneRenaming(this, state);
+ this.resize();
+ this.updateState();
+ }
+ },
+ });
- this.initCallbacks();
this.updateButtons();
+ }
+
+ override getInitialHTML(): string {
+ return $('#cfg').html();
+ }
+
+ override registerOpeningAnalyticsEvent(): void {
ga.proxy('send', {
hitType: 'event',
eventCategory: 'OpenViewPane',
@@ -199,24 +137,27 @@ export class Cfg {
});
}
- onCompileResult(compilerId: number, compiler: any, result: any) {
- if (this.compilerId === compilerId) {
+ override onCompileResult(compilerId: number, compiler: any, result: any) {
+ if (this.compilerInfo.compilerId === compilerId) {
let functionNames: string[] = [];
- if (this.supportsCfg && !$.isEmptyObject(result.cfg)) {
+ if (compiler.supportsCfg && !$.isEmptyObject(result.cfg)) {
this.functions = result.cfg;
functionNames = Object.keys(this.functions);
if (functionNames.indexOf(this.currentFunc) === -1) {
this.currentFunc = functionNames[0];
}
+ const selectedFn = this.functions[this.currentFunc];
this.showCfgResults({
- nodes: this.functions[this.currentFunc].nodes,
- edges: this.functions[this.currentFunc].edges,
+ nodes: selectedFn.nodes,
+ edges: selectedFn.edges,
});
- this.cfgVisualiser.selectNodes([this.functions[this.currentFunc].nodes[0].id]);
+ if (selectedFn.nodes && selectedFn.nodes.length > 0) {
+ this.cfgVisualiser.selectNodes([selectedFn.nodes[0].id]);
+ }
} else {
// We don't reset the current function here as we would lose the saved one if this happened at the beginning
// (Hint: It *does* happen)
- if (result.compilationOptions.indexOf('-emit-llvm') === -1) {
+ if (!result.compilationOptions?.includes('-emit-llvm')) {
this.showCfgResults(this._binaryFilter ? this.binaryModeSupport : this.defaultCfgOutput);
} else {
this.showCfgResults(this._binaryFilter ? this.binaryModeSupport : this.llvmCfgPlaceholder);
@@ -236,25 +177,80 @@ export class Cfg {
functionNames.length ? this.currentFunc : 'The input does not contain any function',
true
);
- this.saveState();
+ this.updateState();
}
}
- onCompiler(compilerId: number, compiler: any) {
- if (compilerId === this.compilerId) {
- this._compilerName = compiler ? compiler.name : '';
- this.supportsCfg = compiler.supportsCfg;
+
+ override registerDynamicElements(state: CfgState) {
+ this.defaultCfgOutput = {nodes: [{id: 0, shape: 'box', label: 'No Output'}], edges: []};
+ // Note that this might be outdated if no functions were present when creating the link, but that's handled
+ // by selectize
+ this.options = {
+ navigation: state.options?.navigation ?? false,
+ physics: state.options?.physics ?? false,
+ };
+
+ this.networkOpts = {
+ autoResize: true,
+ locale: 'en',
+ edges: {
+ arrows: {to: {enabled: true}},
+ smooth: {
+ enabled: true,
+ type: 'dynamic',
+ roundness: 1,
+ },
+ physics: true,
+ },
+ nodes: {
+ font: {face: 'Consolas, "Liberation Mono", Courier, monospace', align: 'left'},
+ },
+ layout: {
+ hierarchical: {
+ enabled: true,
+ direction: 'UD',
+ nodeSpacing: 100,
+ levelSeparation: 150,
+ },
+ },
+ physics: {
+ enabled: this.options.physics,
+ hierarchicalRepulsion: {
+ nodeDistance: 160,
+ },
+ },
+ interaction: {
+ navigationButtons: this.options.navigation,
+ keyboard: {
+ enabled: true,
+ speed: {x: 10, y: 10, zoom: 0.03},
+ bindToWindow: false,
+ },
+ },
+ };
+
+ this.cfgVisualiser = new vis.Network(
+ this.domRoot.find('.graph-placeholder')[0],
+ this.defaultCfgOutput,
+ this.networkOpts
+ );
+ }
+
+ override onCompiler(compilerId: number, compiler: any) {
+ if (compilerId === this.compilerInfo.compilerId) {
+ this.compilerInfo.compilerName = compiler ? compiler.name : '';
this.updateTitle();
}
}
onFiltersChange(compilerId: number, filters: any) {
- if (this.compilerId === compilerId) {
+ if (this.compilerInfo.compilerId === compilerId) {
this._binaryFilter = filters.binary;
}
}
- initButtons(state: any) {
- this.toggles = new Toggles(this.domRoot.find('.options'), state.options);
+ override registerButtons(state: CfgState) {
+ this.toggles = new Toggles(this.domRoot.find('.options'), this.options);
this.toggleNavigationButton = this.domRoot.find('.toggle-navigation');
this.toggleNavigationTitle = this.toggleNavigationButton.prop('title') as string;
@@ -265,23 +261,15 @@ export class Cfg {
this.topBar = this.domRoot.find('.top-bar');
}
- initCallbacks() {
- this.cfgVisualiser.on('dragEnd', this.saveState.bind(this));
- this.cfgVisualiser.on('zoom', this.saveState.bind(this));
+ override registerCallbacks() {
+ this.cfgVisualiser.on('dragEnd', this.updateState.bind(this));
+ this.cfgVisualiser.on('zoom', this.updateState.bind(this));
- this.paneRenaming.on('renamePane', this.saveState.bind(this));
-
- this.eventHub.on('compilerClose', this.onCompilerClose, this);
- this.eventHub.on('compileResult', this.onCompileResult, this);
- this.eventHub.on('compiler', this.onCompiler, this);
this.eventHub.on('filtersChange', this.onFiltersChange, this);
- this.container.on('destroy', this.close, this);
- this.container.on('resize', this.resize, this);
- this.container.on('shown', this.resize, this);
- this.eventHub.emit('cfgViewOpened', this.compilerId);
- this.eventHub.emit('requestFilters', this.compilerId);
- this.eventHub.emit('requestCompiler', this.compilerId);
+ this.eventHub.emit('cfgViewOpened', this.compilerInfo.compilerId);
+ this.eventHub.emit('requestFilters', this.compilerInfo.compilerId);
+ this.eventHub.emit('requestCompiler', this.compilerInfo.compilerId);
this.togglePhysicsButton.on('click', () => {
this.networkOpts.physics.enabled = this.togglePhysicsButton.hasClass('active');
@@ -301,7 +289,7 @@ export class Cfg {
});
this.toggles.on('change', () => {
this.updateButtons();
- this.saveState();
+ this.updateState();
});
}
@@ -313,35 +301,17 @@ export class Cfg {
formatButtonTitle(this.toggleNavigationButton, this.toggleNavigationTitle);
}
- resize() {
- if (this.cfgVisualiser.canvas) {
- const height = (this.domRoot.height() as number) - (this.topBar.outerHeight(true) ?? 0);
- this.cfgVisualiser.setSize('100%', height.toString());
- this.cfgVisualiser.redraw();
- }
+ override resize() {
+ const height = (this.domRoot.height() as number) - (this.topBar.outerHeight(true) ?? 0);
+ this.cfgVisualiser.setSize('100%', height.toString());
+ this.cfgVisualiser.redraw();
}
- getDefaultPaneName() {
+ override getDefaultPaneName() {
return 'Graph Viewer';
}
- getPaneTag() {
- if (this._editorid) {
- return `${this._compilerName} (Editor #${this._editorid}, Compiler #${this.compilerId})`;
- } else {
- return `${this._compilerName} (Tree #${this._treeid}, Compiler #${this.compilerId})`;
- }
- }
-
- getPaneName() {
- return this.paneName ? this.paneName : this.getDefaultPaneName() + ' ' + this.getPaneTag();
- }
-
- updateTitle() {
- this.container.setTitle(_.escape(this.getPaneName()));
- }
-
- assignLevels(data: any) {
+ assignLevels(data: vis.Data) {
const nodes: NodeInfo[] = [];
const idToIdx: string[] = [];
for (const i in data.nodes) {
@@ -357,17 +327,18 @@ export class Cfg {
inCount: 0,
});
}
- const isEdgeValid = (edge: any) => edge.from in idToIdx && edge.to in idToIdx;
- data.edges.forEach((edge: any) => {
- if (isEdgeValid(edge)) {
+ const isEdgeValid = (edge: vis.Edge) => edge.from && edge.to && edge.from in idToIdx && edge.to in idToIdx;
+ for (const edge of data.edges as vis.Edge[]) {
+ if (edge.from && edge.to && isEdgeValid(edge)) {
nodes[idToIdx[edge.from]].edges.push(idToIdx[edge.to]);
}
- });
+ }
- const dfs = (node: any) => {
+ const dfs = (node: NodeInfo) => {
// choose which edges will be back-edges
node.state = 1;
- node.edges.forEach((targetIndex: number) => {
+
+ node.edges.forEach(targetIndex => {
const target = nodes[targetIndex];
if (target.state !== 1) {
if (target.state === 0) {
@@ -379,8 +350,8 @@ export class Cfg {
});
node.state = 2;
};
- const markLevels = (node: any) => {
- node.dagEdges.forEach((targetIndex: number) => {
+ const markLevels = (node: NodeInfo) => {
+ node.dagEdges.forEach(targetIndex => {
const target = nodes[targetIndex];
target.level = Math.max(target.level, node.level + 1);
if (--target.inCount === 0) {
@@ -388,18 +359,21 @@ export class Cfg {
}
});
};
- nodes.forEach((node: any) => {
+ nodes.forEach(node => {
if (node.state === 0) {
dfs(node);
node.level = 1;
markLevels(node);
}
});
- nodes.forEach((node: any) => {
- data.nodes[node.index]['level'] = node.level;
- });
- data.edges.forEach((edge: any) => {
- if (isEdgeValid(edge)) {
+ if (data.nodes) {
+ for (const node of nodes) {
+ data.nodes[node.index]['level'] = node.level;
+ }
+ }
+
+ for (const edge of data.edges as vis.Edge[]) {
+ if (edge.from && edge.to && isEdgeValid(edge)) {
const nodeA = nodes[idToIdx[edge.from]];
const nodeB = nodes[idToIdx[edge.to]];
if (nodeA.level >= nodeB.level) {
@@ -412,13 +386,14 @@ export class Cfg {
} else {
edge.physics = false;
}
- });
+ }
}
- showCfgResults(data: any) {
+ showCfgResults(data: vis.Data) {
this.assignLevels(data);
this.cfgVisualiser.setData(data);
- /* FIXME: This does not work. It's here because I suspected that not having content in the constructor was
+ /* FIXME: This does not work.
+ * It's here because I suspected that not having content in the constructor was
* breaking the move, but it does not seem like it
*/
if (this.needsMove) {
@@ -431,47 +406,39 @@ export class Cfg {
}
}
- onCompilerClose(compilerId: number) {
- if (this.compilerId === compilerId) {
+ override onCompilerClose(compilerId: number) {
+ if (this.compilerInfo.compilerId === compilerId) {
// We can't immediately close as an outer loop somewhere in GoldenLayout is iterating over
// the hierarchy. We can't modify while it's being iterated over.
this.close();
- _.defer((self: Cfg) => {
- self.container.close();
+ _.defer(() => {
+ this.container.close();
}, this);
}
}
- close() {
+ override close() {
this.eventHub.unsubscribe();
- this.eventHub.emit('cfgViewClosed', this.compilerId);
+ this.eventHub.emit('cfgViewClosed', this.compilerInfo.compilerId);
this.cfgVisualiser.destroy();
}
- saveState() {
- this.container.setState(this.currentState());
- }
-
getEffectiveOptions() {
return this.toggles.get();
}
- currentState(): CfgState {
- const state = {
- id: this.compilerId,
- editorid: this._editorid,
- treeid: this._treeid,
+ override getCurrentState(): CfgState {
+ return {
+ ...super.getCurrentState(),
selectedFn: this.currentFunc,
pos: this.cfgVisualiser.getViewPosition(),
scale: this.cfgVisualiser.getScale(),
options: this.getEffectiveOptions(),
};
- this.paneRenaming.addState(state);
- return state;
}
adaptStructure(names: string[]) {
- return _.map(names, name => {
+ return names.map(name => {
return {name};
});
}
diff --git a/static/panes/pane.ts b/static/panes/pane.ts
index 794252adf..b0fa9c0d2 100644
--- a/static/panes/pane.ts
+++ b/static/panes/pane.ts
@@ -84,6 +84,8 @@ export abstract class Pane<S> {
this.paneRenaming = new PaneRenaming(this, state);
+ this.registerDynamicElements(state);
+
this.registerButtons(state);
this.registerStandardCallbacks();
this.registerCallbacks();
@@ -116,6 +118,9 @@ export abstract class Pane<S> {
*/
abstract registerOpeningAnalyticsEvent(): void;
+ /** Optional overridable code for initializing necessary elements before rest of registers **/
+ registerDynamicElements(state: S): void {}
+
/** Optionally overridable code for initializing pane buttons */
registerButtons(state: S): void {}