@@ -7,88 +7,130 @@ const solc = require('../index.js');
7
7
const smtchecker = require ( '../smtchecker.js' ) ;
8
8
const smtsolver = require ( '../smtsolver.js' ) ;
9
9
10
- var preamble = 'pragma solidity >=0.0;\n// SPDX-License-Identifier: GPL-3.0\n' ;
10
+ function isSMTCheckerErrorMessage ( message ) {
11
+ return message . includes ( 'BMC: ' ) || message . includes ( 'CHC: ' ) ;
12
+ }
13
+
14
+ function isSMTCheckerError ( error ) {
15
+ return isSMTCheckerErrorMessage ( error . message ) ;
16
+ }
11
17
12
18
function collectErrors ( solOutput ) {
13
- if ( solOutput === undefined ) {
14
- return [ ] ;
19
+ assert ( solOutput !== undefined ) ;
20
+ return solOutput . errors . filter ( error => isSMTCheckerError ( error ) ) ;
21
+ }
22
+
23
+ function collectStringErrors ( solOutput ) {
24
+ assert ( solOutput !== undefined ) ;
25
+ return solOutput . errors . filter ( error => ! error . message . includes ( 'This is a pre-release' ) ) . map ( error => error . message ) ;
26
+ }
27
+
28
+ function stringToSolverStatus ( message ) {
29
+ if ( message . includes ( 'Error trying to invoke' ) ) {
30
+ return 'error' ;
31
+ } else if ( message . includes ( 'might happen' ) ) {
32
+ return 'notsolved' ;
33
+ } else if ( message . includes ( 'happens here' ) ) {
34
+ return 'solved' ;
15
35
}
36
+ assert ( false ) ;
37
+ }
16
38
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 ;
39
+ function errorFromExpectation ( line ) {
40
+ let re = new RegExp ( '// Warning (\\w+): \\((\\d+)-(\\d+)\\): ' ) ;
41
+ let m = line . match ( re ) ;
42
+ assert ( m !== undefined ) ;
43
+ // m = [match, error code, location start, location end, ...]
44
+ assert ( m . length >= 4 ) ;
45
+ errorCode = m [ 1 ] ;
46
+ message = line . split ( ':' ) ;
47
+ // message = [warning, location, bmc/chc, message]
48
+ assert ( message . length >= 4 ) ;
49
+ // message = 'BMC/CHC: message'
50
+ message = message [ 2 ] . replace ( ' ' , '' ) + ':' + message [ 3 ] ;
51
+ return { errorCode : m [ 1 ] , sourceLocation : { start : m [ 2 ] , end : m [ 3 ] } , message : message } ;
52
+ }
53
+
54
+ function errorData ( error ) {
55
+ assert ( isSMTCheckerError ( error ) ) ;
56
+ // The source location represents the property.
57
+ const loc = error . sourceLocation . start + ':' + error . sourceLocation . end ;
58
+ const res = stringToSolverStatus ( error . message ) ;
59
+ return { loc : loc , res : res } ;
60
+ }
61
+
62
+ function buildErrorsDict ( errors ) {
63
+ let d = { } ;
64
+ for ( let i in errors ) {
65
+ const e = errorData ( errors [ i ] ) ;
66
+ const engine = errors [ i ] . message . split ( ':' ) [ 0 ]
67
+ d [ engine + '-' + e . loc ] = e . res ;
68
+ }
69
+ return d ;
70
+ }
71
+
72
+ function expectErrors ( a , b ) {
73
+ const all = { ...a , ...b } ;
74
+ const isSafe = ( d , r ) => ! ( r in d ) ;
75
+ const isUnsafe = ( d , r ) => ( r in d ) && d [ r ] == 'solved' ;
76
+ for ( let p in all ) {
77
+ if ( ( isSafe ( a , p ) && isUnsafe ( b , p ) ) || ( isSafe ( b , p ) && isUnsafe ( a , p ) ) ) {
78
+ return false ;
22
79
}
23
- errors . push ( error . message ) ;
24
80
}
25
- return errors ;
81
+ return true ;
26
82
}
27
83
28
- function expectErrors ( expectations , errors , ignoreCex ) {
29
- if ( errors . length !== expectations . length ) {
84
+ function expectStringErrors ( a , b ) {
85
+ if ( a . length !== b . length ) {
30
86
return false ;
31
87
}
32
-
33
- for ( var i in errors ) {
34
- if ( errors [ i ] . includes ( 'Error trying to invoke SMT solver' ) || expectations [ i ] . includes ( 'Error trying to invoke SMT solver' ) ) {
35
- continue ;
36
- }
37
- // Expectations containing counterexamples might have many '\n' in a single line.
38
- // These are stored escaped in the test format (as '\\n'), whereas the actual error from the compiler has '\n'.
39
- // Therefore we need to replace '\\n' by '\n' in the expectations.
40
- // Function `replace` only replaces the first occurrence, and `replaceAll` is not standard yet.
41
- // Replace all '\\n' by '\n' via split & join.
42
- expectations [ i ] = expectations [ i ] . split ( '\\n' ) . join ( '\n' ) ;
43
- if ( ignoreCex ) {
44
- expectations [ i ] = expectations [ i ] . split ( '\nCounterexample' ) [ 0 ] ;
45
- errors [ i ] = errors [ i ] . split ( '\nCounterexample' ) [ 0 ] ;
46
- }
47
- // `expectations` have "// Warning ... " before the actual message,
48
- // whereas `errors` have only the message.
49
- if ( ! expectations [ i ] . includes ( errors [ i ] ) ) {
88
+ for ( let i = 0 ; i < a . length ; ++ i ) {
89
+ if ( ! a [ i ] . includes ( b [ i ] ) ) {
50
90
return false ;
51
91
}
52
92
}
53
-
54
93
return true ;
55
94
}
56
95
57
96
tape ( 'SMTCheckerCallback' , function ( t ) {
58
97
t . test ( 'Interface via callback' , function ( st ) {
98
+ // This test does not use a solver and is used only to test
99
+ // the callback mechanism.
100
+
59
101
if ( ! semver . gt ( solc . semver ( ) , '0.5.99' ) ) {
60
102
st . skip ( 'SMT callback not implemented by this compiler version.' ) ;
61
103
st . end ( ) ;
62
104
return ;
63
105
}
64
106
65
- var satCallback = function ( query ) {
107
+ const satCallback = function ( query ) {
66
108
return { contents : 'sat\n' } ;
67
109
} ;
68
- var unsatCallback = function ( query ) {
110
+ const unsatCallback = function ( query ) {
69
111
return { contents : 'unsat\n' } ;
70
112
} ;
71
- var errorCallback = function ( query ) {
113
+ const errorCallback = function ( query ) {
72
114
return { error : 'Fake SMT solver error.' } ;
73
115
} ;
74
116
75
- var pragmaSMT = '' ;
76
- var settings = { } ;
117
+ let pragmaSMT = '' ;
118
+ let settings = { } ;
77
119
// `pragma experimental SMTChecker;` was deprecated in 0.8.4
78
120
if ( ! semver . gt ( solc . semver ( ) , '0.8.3' ) ) {
79
121
pragmaSMT = 'pragma experimental SMTChecker;\n' ;
80
122
} else {
81
123
settings = { modelChecker : { engine : 'all' } } ;
82
124
}
83
125
84
- var input = { 'a' : { content : preamble + pragmaSMT + 'contract C { function f(uint x) public pure { assert(x > 0); } }' } } ;
85
- var inputJSON = JSON . stringify ( {
126
+ const input = { 'a' : { content : pragmaSMT + 'pragma solidity >=0.0;\n// SPDX-License-Identifier: GPL-3.0\ncontract C { function f(uint x) public pure { assert(x > 0); } }' } } ;
127
+ const inputJSON = JSON . stringify ( {
86
128
language : 'Solidity' ,
87
129
sources : input ,
88
130
settings : settings
89
131
} ) ;
90
132
91
- var tests ;
133
+ let tests ;
92
134
if ( ! semver . gt ( solc . semver ( ) , '0.6.8' ) ) {
93
135
// Up to version 0.6.8 there were no embedded solvers.
94
136
tests = [
@@ -112,20 +154,28 @@ tape('SMTCheckerCallback', function (t) {
112
154
] ;
113
155
}
114
156
115
- for ( var i in tests ) {
116
- var test = tests [ i ] ;
117
- var output = JSON . parse ( solc . compile (
157
+ for ( let i in tests ) {
158
+ const test = tests [ i ] ;
159
+ const output = JSON . parse ( solc . compile (
118
160
inputJSON ,
119
161
{ smtSolver : test . cb }
120
162
) ) ;
121
- var errors = collectErrors ( output ) ;
122
- st . ok ( expectErrors ( errors , test . expectations ) ) ;
163
+ st . ok ( expectStringErrors ( collectStringErrors ( output ) , test . expectations ) ) ;
123
164
}
124
165
st . end ( ) ;
125
166
} ) ;
126
167
168
+ /*
127
169
t.test('Solidity smtCheckerTests', function (st) {
128
- var testdir = path . resolve ( __dirname , 'smtCheckerTests/' ) ;
170
+ // 0.8.5 added the `solvers` option.
171
+ // For these tests we use the embedded z3.
172
+ if (semver.gt(solc.semver(), '0.8.4')) {
173
+ assert(settings);
174
+ settings.modelChecker.solvers = ['z3'];
175
+ }
176
+
177
+
178
+ const testdir = path.resolve(__dirname, 'smtCheckerTests/');
129
179
if (!fs.existsSync(testdir)) {
130
180
st.skip('SMT checker tests not present.');
131
181
st.end();
@@ -138,16 +188,15 @@ tape('SMTCheckerCallback', function (t) {
138
188
return;
139
189
}
140
190
141
- var sources = [ ] ;
191
+ let sources = [];
142
192
143
193
// BFS to get all test files
144
- var dirs = [ testdir ] ;
145
- var i ;
194
+ let dirs = [testdir];
146
195
while (dirs.length > 0) {
147
- var dir = dirs . shift ( ) ;
148
- var files = fs . readdirSync ( dir ) ;
149
- for ( i in files ) {
150
- var file = path . join ( dir , files [ i ] ) ;
196
+ const dir = dirs.shift();
197
+ const files = fs.readdirSync(dir);
198
+ for (let i in files) {
199
+ const file = path.join(dir, files[i]);
151
200
if (fs.statSync(file).isDirectory()) {
152
201
dirs.push(file);
153
202
} else {
@@ -157,63 +206,52 @@ tape('SMTCheckerCallback', function (t) {
157
206
}
158
207
159
208
// Read tests and collect expectations
160
- var tests = [ ] ;
161
- for ( i in sources ) {
209
+ let tests = [];
210
+ for (let i in sources) {
162
211
st.comment('Collecting ' + sources[i] + '...');
163
- var source = fs . readFileSync ( sources [ i ] , 'utf8' ) ;
212
+ const source = fs.readFileSync(sources[i], 'utf8');
164
213
165
- var engine ;
166
- var option = '// SMTEngine: ' ;
214
+ let engine;
215
+ const option = '// SMTEngine: ';
167
216
if (source.includes(option)) {
168
217
let idx = source.indexOf(option);
169
218
if (source.indexOf(option, idx + 1) !== -1) {
170
219
st.skip('SMTEngine option given multiple times.');
171
- st . end ( ) ;
172
- return ;
220
+ continue;
173
221
}
174
- let re = new RegExp ( option + '(\\w+)' ) ;
222
+ const re = new RegExp(option + '(\\w+)');
175
223
let m = source.match(re);
176
224
assert(m !== undefined);
177
225
assert(m.length >= 2);
178
226
engine = m[1];
179
227
}
180
228
181
- var expected = [ ] ;
182
- var delimiter = '// ----' ;
229
+ let expected = [];
230
+ const delimiter = '// ----';
183
231
if (source.includes(delimiter)) {
184
232
expected = source.substring(source.indexOf('// ----') + 8, source.length).split('\n');
185
- // Sometimes the last expectation line ends with a '\n'
186
- if ( expected . length > 0 && expected [ expected . length - 1 ] === '' ) {
187
- expected . pop ( ) ;
188
- }
233
+ expected = expected.filter(line => isSMTCheckerErrorMessage(line));
234
+ expected = expected.map(line => errorFromExpectation(line));
189
235
}
190
236
tests[sources[i]] = {
191
237
expectations: expected,
192
- solidity : { test : { content : preamble + source } } ,
193
- ignoreCex : source . includes ( '// SMTIgnoreCex: yes' ) ,
238
+ solidity: { test: { content: source } },
194
239
engine: engine
195
240
};
196
241
}
197
242
198
243
// Run all tests
199
- for ( i in tests ) {
200
- var test = tests [ i ] ;
201
-
202
- // Z3's nondeterminism sometimes causes a test to timeout in one context but not in the other,
203
- // so if we see timeout we skip a potentially misleading run.
204
- var findError = ( errorMsg ) => { return errorMsg . includes ( 'Error trying to invoke SMT solver' ) ; } ;
205
- if ( test . expectations . find ( findError ) !== undefined ) {
206
- st . skip ( 'Test contains timeout which may have been caused by nondeterminism.' ) ;
207
- continue ;
208
- }
244
+ for (let i in tests) {
245
+ const test = tests[i];
246
+ console.log("Running " + i);
209
247
210
- var settings = { } ;
248
+ let settings = {};
211
249
// `pragma experimental SMTChecker;` was deprecated in 0.8.4
212
250
if (semver.gt(solc.semver(), '0.8.3')) {
213
- let engine = test . engine !== undefined ? test . engine : 'all' ;
251
+ const engine = test.engine !== undefined ? test.engine : 'all';
214
252
settings = { modelChecker: { engine: engine } };
215
253
}
216
- var output = JSON . parse ( solc . compile (
254
+ const output = JSON.parse(solc.compile(
217
255
JSON.stringify({
218
256
language: 'Solidity',
219
257
sources: test.solidity,
@@ -223,28 +261,15 @@ tape('SMTCheckerCallback', function (t) {
223
261
));
224
262
st.ok(output);
225
263
226
- // Collect obtained error messages
227
- test . errors = collectErrors ( output ) ;
228
-
229
- // These are errors in the SMTLib2Interface encoding.
230
- if ( test . errors . length > 0 && test . errors [ test . errors . length - 1 ] . includes ( 'BMC analysis was not possible' ) ) {
231
- continue ;
232
- }
233
-
234
- // These are due to CHC not being supported via SMTLib2Interface yet.
235
- if ( test . expectations . length !== test . errors . length ) {
236
- continue ;
237
- }
238
-
239
- if ( test . errors . find ( findError ) !== undefined ) {
240
- st . skip ( 'Test contains timeout which may have been caused by nondeterminism.' ) ;
241
- continue ;
242
- }
264
+ const obtained = buildErrorsDict(collectErrors(output));
265
+ const expectations = buildErrorsDict(test.expectations);
243
266
244
- // Compare expected vs obtained errors
245
- st . ok ( expectErrors ( test . expectations , test . errors , test . ignoreCex ) ) ;
267
+ // We only complain about contradictions, since nondeterminism is a bit
268
+ // more likely when running the JS version of the solver.
269
+ st.ok(expectErrors(obtained, expectations));
246
270
}
247
271
248
272
st.end();
249
273
});
274
+ */
250
275
} ) ;
0 commit comments