@informalsystems/quint
Version:
Core tool for the Quint specification language
132 lines • 6.53 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const chai_1 = require("chai");
const mocha_1 = require("mocha");
const textUtils_1 = require("../textUtils");
const idGenerator_1 = require("../../src/idGenerator");
const quintParserFrontend_1 = require("../../src/parsing/quintParserFrontend");
const sourceResolver_1 = require("../../src/parsing/sourceResolver");
const callgraph_1 = require("../../src/static/callgraph");
const IRVisitor_1 = require("../../src/ir/IRVisitor");
const lodash_1 = require("lodash");
(0, mocha_1.describe)('compute call graph', () => {
// Parse Quint code without, stopping after name resolution
function parse3phases(code) {
const idGen = (0, idGenerator_1.newIdGenerator)();
const fakePath = {
normalizedPath: 'fake_path',
toSourceName: () => 'fake_path',
};
// we are calling parse phases directly instead of `parse`,
// as the call graph will be computed at parse phase 4
const resolver = (0, sourceResolver_1.fileSourceResolver)();
const { modules, table, errors } = (0, lodash_1.flow)([
() => (0, quintParserFrontend_1.parsePhase1fromText)(idGen, code, fakePath.normalizedPath),
phase1Data => (0, quintParserFrontend_1.parsePhase2sourceResolution)(idGen, resolver, fakePath, phase1Data),
quintParserFrontend_1.parsePhase3importAndNameResolution,
])();
chai_1.assert.isEmpty(errors);
return [modules, table];
}
function findDef(module, name) {
const d = module.declarations.find(d => (d.kind === 'def' || d.kind === 'const' || d.kind === 'var' || d.kind === 'typedef') && d.name === name);
(0, chai_1.assert)(d, `Definition ${name} not found`);
return d;
}
function findImport(module, pred) {
const d = module.declarations.find(d => d.kind === 'import' && pred(d));
(0, chai_1.assert)(d, `Import not found in ${module.name}`);
return d;
}
function findInstance(module, pred) {
const d = module.declarations.find(d => d.kind === 'instance' && pred(d));
(0, chai_1.assert)(d, `Instance not found in ${module.name}`);
return d;
}
function findExport(module, pred) {
const d = module.declarations.find(d => d.kind === 'export' && pred(d));
(0, chai_1.assert)(d, `Export not found in ${module.name}`);
return d;
}
it('computes a call graph of const, var, and operator definitions', () => {
const code = (0, textUtils_1.dedent)(`module main {
| const N: int
| var w: int
| pure def plus(x, y) = x + y
| pure def double(x) = plus(x, x)
| pure def triple(x) = plus(x, double(x, x))
| val getW = w + N
|}`);
const [modules, table] = parse3phases(code);
const main = modules.find(m => m.name === 'main');
const visitor = new callgraph_1.CallGraphVisitor(table, (0, callgraph_1.mkCallGraphContext)(modules));
(0, IRVisitor_1.walkModule)(visitor, main);
const graph = visitor.graph;
const plus = findDef(main, 'plus');
const double = findDef(main, 'double');
const triple = findDef(main, 'triple');
const w = findDef(main, 'w');
const N = findDef(main, 'N');
const getW = findDef(main, 'getW');
(0, chai_1.expect)(graph.get(plus.id)?.size).to.equal(2);
(0, chai_1.expect)(graph.get(double.id)?.toArray()).to.include.members([plus.id]);
(0, chai_1.expect)(graph.get(triple.id)?.toArray()).to.include.members([plus.id, double.id]);
(0, chai_1.expect)(graph.get(getW.id)?.toArray()).to.include.members([w.id, N.id]);
});
it('computes a "uses" graph of typedefs', () => {
const code = (0, textUtils_1.dedent)(`module main {
| type BagOfApples = Set[int]
| type BoxOfApples = Set[BagOfApples]
| var x: BoxOfApples
|}`);
const [modules, table] = parse3phases(code);
const main = modules.find(m => m.name === 'main');
const visitor = new callgraph_1.CallGraphVisitor(table, (0, callgraph_1.mkCallGraphContext)(modules));
(0, IRVisitor_1.walkModule)(visitor, main);
const graph = visitor.graph;
const bagOfApples = findDef(main, 'BagOfApples');
const boxOfApples = findDef(main, 'BoxOfApples');
const x = findDef(main, 'x');
(0, chai_1.expect)(graph.get(bagOfApples.id)).to.be.undefined;
(0, chai_1.expect)(graph.get(boxOfApples.id)?.toArray()).to.eql([bagOfApples.id]);
(0, chai_1.expect)(graph.get(x.id)?.toArray()).to.eql([boxOfApples.id]);
});
it('computes a "uses" graph of imports and defs', () => {
// the following definitions should always come in this order:
const code = (0, textUtils_1.dedent)(`module A {
| pure def sqr(x) = x * x
|}
|module B {
| const M: int
| pure val doubleM = M + M
|}
|module main {
| import A.*
| pure val myM = sqr(3)
| import B(M = myM) as B1
| pure val quadM = 2 * B1::doubleM
| export B1.*
|}`);
const [modules, table] = parse3phases(code);
const findModule = (name) => modules.find(m => m.name === name);
const [A, B, main] = ['A', 'B', 'main'].map(findModule);
const visitor = new callgraph_1.CallGraphVisitor(table, (0, callgraph_1.mkCallGraphContext)(modules));
(0, IRVisitor_1.walkModule)(visitor, main);
const graph = visitor.graph;
// uncomment to debug the graph structure
//visitor.print(console.log)
const sqr = findDef(A, 'sqr');
const importA = findImport(main, imp => imp.protoName === 'A');
const myM = findDef(main, 'myM');
const importB = findInstance(main, imp => imp.protoName === 'B');
const quadM = findDef(main, 'quadM');
const doubleM = findDef(B, 'doubleM');
const exportB1 = findExport(main, exp => exp.protoName === 'B1');
(0, chai_1.expect)(graph.get(importA.id)?.toArray()).eql([A.id]);
(0, chai_1.expect)(graph.get(myM.id)?.toArray()).to.eql([sqr.id, importA.id]);
(0, chai_1.expect)(graph.get(importB.id)?.toArray()).to.eql([B.id, myM.id]);
(0, chai_1.expect)(graph.get(quadM.id)?.toArray()).to.eql([doubleM.id, importB.id]);
(0, chai_1.expect)(graph.get(exportB1.id)?.toArray()).to.eql([importB.id]);
});
});
//# sourceMappingURL=callgraph.test.js.map