Skip to content

Commit 980275c

Browse files
author
Leonardo
authored
Merge pull request #382 from ethereum/smt_callback
Add callback kind to solc
2 parents 77224d1 + 9ba7038 commit 980275c

File tree

6 files changed

+243
-15
lines changed

6 files changed

+243
-15
lines changed

smtchecker.js

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,18 @@ function handleSMTQueries (inputJSON, outputJSON, solver) {
2525
return inputJSON;
2626
}
2727

28+
function smtCallback (solver) {
29+
return function (query) {
30+
try {
31+
var result = solver(query);
32+
return { contents: result };
33+
} catch (err) {
34+
return { error: err };
35+
}
36+
};
37+
}
38+
2839
module.exports = {
29-
handleSMTQueries: handleSMTQueries
40+
handleSMTQueries: handleSMTQueries,
41+
smtCallback: smtCallback
3042
};

smtsolver.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ function solve (query) {
4646
!solverOutput.startsWith('unsat') &&
4747
!solverOutput.startsWith('unknown')
4848
) {
49-
throw new Error('Failed solve SMT query. ' + e.toString());
49+
throw new Error('Failed to solve SMT query. ' + e.toString());
5050
}
5151
}
5252
// Trigger early manual cleanup
@@ -55,5 +55,6 @@ function solve (query) {
5555
}
5656

5757
module.exports = {
58-
smtSolver: solve
58+
smtSolver: solve,
59+
availableSolvers: solvers.length
5960
};

test/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ const semver = require('semver');
33
require('./linker.js');
44
require('./translate.js');
55
require('./compiler.js');
6+
require('./smtcallback.js');
67
require('./smtchecker.js');
78
require('./abi.js');
89

test/smtCheckerTests/smoke.sol

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
pragma experimental SMTChecker;
2+
contract C {
3+
function f() public pure {
4+
}
5+
}

test/smtcallback.js

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
const tape = require('tape');
2+
const fs = require('fs');
3+
const path = require('path');
4+
const semver = require('semver');
5+
const solc = require('../index.js');
6+
const smtchecker = require('../smtchecker.js');
7+
const smtsolver = require('../smtsolver.js');
8+
9+
var pragmaSol = 'pragma solidity >=0.0;\n';
10+
var pragmaSMT = 'pragma experimental SMTChecker;\n';
11+
12+
function collectErrors (solOutput) {
13+
if (solOutput === undefined) {
14+
return [];
15+
}
16+
17+
var errors = [];
18+
for (var i in solOutput.errors) {
19+
var error = solOutput.errors[i];
20+
if (error.message.includes('This is a pre-release compiler version')) {
21+
continue;
22+
}
23+
errors.push(error.message);
24+
}
25+
return errors;
26+
}
27+
28+
function expectErrors (errors, expectations) {
29+
if (errors.length !== expectations.length) {
30+
return false;
31+
}
32+
33+
for (var i in errors) {
34+
if (!errors[i].includes(expectations[i])) {
35+
return false;
36+
}
37+
}
38+
39+
return true;
40+
}
41+
42+
tape('SMTCheckerCallback', function (t) {
43+
t.test('Interface via callback', function (st) {
44+
if (!semver.gt(solc.semver(), '0.5.99')) {
45+
st.skip('SMT callback not implemented by this compiler version.');
46+
st.end();
47+
return;
48+
}
49+
50+
var satCallback = function (query) {
51+
return { contents: 'sat\n' };
52+
};
53+
var unsatCallback = function (query) {
54+
return { contents: 'unsat\n' };
55+
};
56+
var errorCallback = function (query) {
57+
return { error: 'Fake SMT solver error.' };
58+
};
59+
60+
var input = { 'a': { content: pragmaSol + pragmaSMT + 'contract C { function f(uint x) public pure { assert(x > 0); } }' } };
61+
var inputJSON = JSON.stringify({
62+
language: 'Solidity',
63+
sources: input
64+
});
65+
var tests = [
66+
{ cb: satCallback, expectations: ['Assertion violation happens here'] },
67+
{ cb: unsatCallback, expectations: [] },
68+
{ cb: errorCallback, expectations: ['BMC analysis was not possible'] }
69+
];
70+
for (var i in tests) {
71+
var test = tests[i];
72+
var output = JSON.parse(solc.compile(
73+
inputJSON,
74+
{ smtSolver: test.cb }
75+
));
76+
var errors = collectErrors(output);
77+
st.ok(expectErrors(errors, test.expectations));
78+
}
79+
st.end();
80+
});
81+
82+
t.test('Solidity smtCheckerTests', function (st) {
83+
var testdir = path.resolve(__dirname, 'smtCheckerTests/');
84+
if (!fs.existsSync(testdir)) {
85+
st.skip('SMT checker tests not present.');
86+
st.end();
87+
return;
88+
}
89+
90+
if (smtsolver.availableSolvers === 0) {
91+
st.skip('No SMT solver available.');
92+
st.end();
93+
return;
94+
}
95+
96+
var sources = [];
97+
98+
// BFS to get all test files
99+
var dirs = [testdir];
100+
var i;
101+
while (dirs.length > 0) {
102+
var dir = dirs.shift();
103+
var files = fs.readdirSync(dir);
104+
for (i in files) {
105+
var file = path.join(dir, files[i]);
106+
if (fs.statSync(file).isDirectory()) {
107+
dirs.push(file);
108+
} else {
109+
sources.push(file);
110+
}
111+
}
112+
}
113+
114+
// Read tests and collect expectations
115+
var tests = [];
116+
for (i in sources) {
117+
st.comment('Collecting ' + sources[i] + '...');
118+
var source = fs.readFileSync(sources[i], 'utf8');
119+
var expected = [];
120+
var delimiter = '// ----';
121+
if (source.includes(delimiter)) {
122+
expected = source.substring(source.indexOf('// ----') + 8, source.length).split('\n');
123+
// Sometimes the last expectation line ends with a '\n'
124+
if (expected.length > 0 && expected[expected.length - 1] === '') {
125+
expected.pop();
126+
}
127+
}
128+
tests[sources[i]] = {
129+
expectations: expected,
130+
solidity: { test: { content: pragmaSol + source } }
131+
};
132+
}
133+
134+
// Run all tests
135+
for (i in tests) {
136+
var test = tests[i];
137+
var output = JSON.parse(solc.compile(
138+
JSON.stringify({
139+
language: 'Solidity',
140+
sources: test.solidity
141+
}),
142+
{ smtSolver: smtchecker.smtCallback(smtsolver.smtSolver) }
143+
));
144+
st.ok(output);
145+
146+
// Collect obtained error messages
147+
test.errors = collectErrors(output);
148+
149+
// These are errors in the SMTLib2Interface encoding.
150+
if (test.errors.length > 0 && test.errors[test.errors.length - 1].includes('BMC analysis was not possible')) {
151+
continue;
152+
}
153+
154+
// These are due to CHC not being supported via SMTLib2Interface yet.
155+
if (test.expectations.length !== test.errors.length) {
156+
continue;
157+
}
158+
159+
// Compare expected vs obtained errors
160+
st.ok(expectErrors(test.expectations, test.errors));
161+
}
162+
163+
st.end();
164+
});
165+
});

wrapper.js

Lines changed: 56 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ var translate = require('./translate.js');
33
var requireFromString = require('require-from-string');
44
var https = require('https');
55
var MemoryStream = require('memorystream');
6+
var semver = require('semver');
67

78
function setupMethods (soljson) {
89
var version;
@@ -37,8 +38,21 @@ function setupMethods (soljson) {
3738

3839
var wrapCallback = function (callback) {
3940
assert(typeof callback === 'function', 'Invalid callback specified.');
40-
return function (path, contents, error) {
41-
var result = callback(soljson.Pointer_stringify(path));
41+
return function (data, contents, error) {
42+
var result = callback(soljson.Pointer_stringify(data));
43+
if (typeof result.contents === 'string') {
44+
copyString(result.contents, contents);
45+
}
46+
if (typeof result.error === 'string') {
47+
copyString(result.error, error);
48+
}
49+
};
50+
};
51+
52+
var wrapCallbackWithKind = function (callback) {
53+
assert(typeof callback === 'function', 'Invalid callback specified.');
54+
return function (kind, data, contents, error) {
55+
var result = callback(soljson.Pointer_stringify(kind), soljson.Pointer_stringify(data));
4256
if (typeof result.contents === 'string') {
4357
copyString(result.contents, contents);
4458
}
@@ -49,25 +63,55 @@ function setupMethods (soljson) {
4963
};
5064

5165
// This calls compile() with args || cb
52-
var runWithReadCallback = function (readCallback, compile, args) {
53-
if (readCallback) {
54-
assert(typeof readCallback === 'object', 'Invalid callback object specified.');
55-
readCallback = readCallback.import;
66+
var runWithCallbacks = function (callbacks, compile, args) {
67+
if (callbacks) {
68+
assert(typeof callbacks === 'object', 'Invalid callback object specified.');
69+
} else {
70+
callbacks = {};
5671
}
5772

73+
var readCallback = callbacks.import;
5874
if (readCallback === undefined) {
59-
readCallback = function (path) {
75+
readCallback = function (data) {
6076
return {
6177
error: 'File import callback not supported'
6278
};
6379
};
6480
}
6581

82+
var singleCallback;
83+
if (semver.gt(versionToSemver(), '0.5.99')) {
84+
// After 0.6.x multiple kind of callbacks are supported.
85+
var smtSolverCallback = callbacks.smtSolver;
86+
if (smtSolverCallback === undefined) {
87+
smtSolverCallback = function (data) {
88+
return {
89+
error: 'SMT solver callback not supported'
90+
};
91+
};
92+
}
93+
94+
singleCallback = function (kind, data) {
95+
if (kind === 'source') {
96+
return readCallback(data);
97+
} else if (kind === 'smt-query') {
98+
return smtSolverCallback(data);
99+
} else {
100+
assert(false, 'Invalid callback kind specified.');
101+
}
102+
};
103+
104+
singleCallback = wrapCallbackWithKind(singleCallback);
105+
} else {
106+
// Old Solidity version only supported imports.
107+
singleCallback = wrapCallback(readCallback);
108+
}
109+
66110
// This is to support multiple versions of Emscripten.
67111
var addFunction = soljson.addFunction || soljson.Runtime.addFunction;
68112
var removeFunction = soljson.removeFunction || soljson.Runtime.removeFunction;
69113

70-
var cb = addFunction(wrapCallback(readCallback));
114+
var cb = addFunction(singleCallback);
71115
var output;
72116
try {
73117
args.push(cb);
@@ -94,21 +138,21 @@ function setupMethods (soljson) {
94138
if ('_compileJSONCallback' in soljson) {
95139
var compileInternal = soljson.cwrap('compileJSONCallback', 'string', ['string', 'number', 'number']);
96140
compileJSONCallback = function (input, optimize, readCallback) {
97-
return runWithReadCallback(readCallback, compileInternal, [ input, optimize ]);
141+
return runWithCallbacks(readCallback, compileInternal, [ input, optimize ]);
98142
};
99143
}
100144

101145
var compileStandard = null;
102146
if ('_compileStandard' in soljson) {
103147
var compileStandardInternal = soljson.cwrap('compileStandard', 'string', ['string', 'number']);
104148
compileStandard = function (input, readCallback) {
105-
return runWithReadCallback(readCallback, compileStandardInternal, [ input ]);
149+
return runWithCallbacks(readCallback, compileStandardInternal, [ input ]);
106150
};
107151
}
108152
if ('_solidity_compile' in soljson) {
109153
var solidityCompile = soljson.cwrap('solidity_compile', 'string', ['string', 'number']);
110-
compileStandard = function (input, readCallback) {
111-
return runWithReadCallback(readCallback, solidityCompile, [ input ]);
154+
compileStandard = function (input, callbacks) {
155+
return runWithCallbacks(callbacks, solidityCompile, [ input ]);
112156
};
113157
}
114158

0 commit comments

Comments
 (0)