Buglist check script supports json paths

This commit is contained in:
Leonardo Alt 2018-08-21 16:09:53 +02:00
parent c57a60833d
commit 9927964d21
5 changed files with 200 additions and 54 deletions

View File

@ -195,6 +195,12 @@ jobs:
TERM: xterm
steps:
- checkout
- run:
name: JS deps
command: |
npm install download
npm install JSONPath
npm install mktemp
- run:
name: Test buglist
command: ./test/buglistTests.js

View File

@ -5,7 +5,8 @@
"description": "If a struct is used in an event, the address of the struct is logged instead of the actual data.",
"introduced": "0.4.17",
"fixed": "0.5.0",
"severity": "very low"
"severity": "very low",
"check": {"ast-compact-json-path": "$..[?(@.nodeType === 'EventDefinition')]..[?(@.nodeType === 'UserDefinedTypeName' && @.typeDescriptions.typeString.startsWith('struct'))]"}
},
{
"name": "NestedArrayFunctionCallDecoder",

View File

@ -57,13 +57,18 @@ conditions
means that the optimizer has to be switched on to enable the bug.
If no conditions are given, assume that the bug is present.
check
This field contains JavaScript regular expressions that are to be matched
against the source code ("source-regex") to find out if the
smart contract contains the bug or not. If there is no match,
then the bug is very likely not present. If there is a match,
the bug might be present. For improved accuracy, the regular
expression should be applied to the source code after stripping
This field contains different checks that report whether the smart contract
contains the bug or not. The first type of check are Javascript regular
expressions that are to be matched against the source code ("source-regex")
if the bug is present. If there is no match, then the bug is very likely
not present. If there is a match, the bug might be present. For improved
accuracy, the checks should be applied to the source code after stripping
comments.
The second type of check are patterns to be checked on the compact AST of
the Solidity program ("ast-compact-json-path"). The specified search query
is a `JsonPath <https://github.com/json-path/JsonPath>`_ expression.
If at least one path of the Solidity AST matches the query, the bug is
likely present.
.. literalinclude:: bugs.json
:language: js

View File

@ -2,6 +2,11 @@
"use strict";
var util = require('util')
var exec = util.promisify(require('child_process').exec)
var mktemp = require('mktemp');
var download = require('download')
var JSONPath = require('JSONPath')
var fs = require('fs')
var bugs = JSON.parse(fs.readFileSync(__dirname + '/../docs/bugs.json', 'utf8'))
@ -19,6 +24,10 @@ var tests = fs.readFileSync(__dirname + '/buglist_test_vectors.md', 'utf8')
var testVectorParser = /\s*#\s+(\S+)\s+## buggy\n([^#]*)## fine\n([^#]*)/g
runTests()
async function runTests()
{
var result;
while ((result = testVectorParser.exec(tests)) !== null)
{
@ -27,19 +36,99 @@ while ((result = testVectorParser.exec(tests)) !== null)
var fine = result[3].split('\n--\n')
console.log("Testing " + name + " with " + buggy.length + " buggy and " + fine.length + " fine instances")
var regex = RegExp(bugsByName[name].check['regex-source'])
try {
await checkRegex(name, buggy, fine)
await checkJSONPath(name, buggy, fine)
} catch (err) {
console.error("Error: " + err)
}
}
}
function checkRegex(name, buggy, fine)
{
return new Promise(function(resolve, reject) {
var regexStr = bugsByName[name].check['regex-source']
if (regexStr !== undefined)
{
var regex = RegExp(regexStr)
for (var i in buggy)
{
if (!regex.exec(buggy[i]))
{
throw "Bug " + name + ": Buggy source does not match: " + buggy[i]
reject("Bug " + name + ": Buggy source does not match: " + buggy[i])
}
}
for (var i in fine)
{
if (regex.exec(fine[i]))
{
throw "Bug " + name + ": Non-buggy source matches: " + fine[i]
reject("Bug " + name + ": Non-buggy source matches: " + fine[i])
}
}
}
resolve()
})
}
async function checkJSONPath(name, buggy, fine)
{
var jsonPath = bugsByName[name].check['ast-compact-json-path']
if (jsonPath !== undefined)
{
var url = "http://github.com/ethereum/solidity/releases/download/v" + bugsByName[name].introduced + "/solc-static-linux"
try {
var tmpdir = await mktemp.createDir('XXXXX')
var binary = tmpdir + "/solc-static-linux"
await download(url, tmpdir)
exec("chmod +x " + binary)
for (var i in buggy)
{
var result = await checkJsonPathTest(buggy[i], tmpdir, binary, jsonPath, i)
if (!result)
throw "Bug " + name + ": Buggy source does not contain path: " + buggy[i]
}
for (var i in fine)
{
var result = await checkJsonPathTest(fine[i], tmpdir, binary, jsonPath, i + buggy.length)
if (result)
throw "Bug " + name + ": Non-buggy source contains path: " + fine[i]
}
exec("rm -r " + tmpdir)
} catch (err) {
throw err
}
}
}
function checkJsonPathTest(code, tmpdir, binary, query, idx) {
return new Promise(function(resolve, reject) {
var solFile = tmpdir + "/jsonPath" + idx + ".sol"
var astFile = tmpdir + "/ast" + idx + ".json"
writeFilePromise(solFile, code)
.then(() => {
return exec(binary + " --ast-compact-json " + solFile + " > " + astFile)
})
.then(() => {
var jsonRE = /(\{[\s\S]*\})/
var ast = JSON.parse(jsonRE.exec(fs.readFileSync(astFile, 'utf8'))[0])
var result = JSONPath({json: ast, path: query})
if (result.length > 0)
resolve(true)
else
resolve(false)
})
.catch((err) => {
reject(err)
})
})
}
function writeFilePromise(filename, data) {
return new Promise(function(resolve, reject) {
fs.writeFile(filename, data, 'utf8', function(err) {
if (err) reject(err)
else resolve(data)
})
})
}

View File

@ -67,3 +67,48 @@ function f() m(uint[2][2]) { }
--
function f() returns (uint, uint) { uint[2][2] memory x; }
# EventStructWrongData
## buggy
pragma experimental ABIEncoderV2;
contract C
{
struct S { uint x; }
event E(S);
event F(S);
enum A { B, C }
event G(A);
function f(S s);
}
--
pragma experimental ABIEncoderV2;
contract C
{
struct S { uint x; }
event E(S indexed);
event F(uint, S, bool);
}
## fine
pragma experimental ABIEncoderV2;
contract C
{
struct S { uint x; }
enum A { B, C }
event G(A);
}
--
pragma experimental ABIEncoderV2;
contract C
{
struct S { uint x; }
function f(S s);
S s1;
}