Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
9f98878
commit a6b6df2
Showing
31 changed files
with
1,895 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,138 @@ | ||
#!/usr/bin/env python3 | ||
# Copyright (c) 2019 The Bitcoin Core developers | ||
# Distributed under the MIT software license, see the accompanying | ||
# file COPYING or http://www.opensource.org/licenses/mit-license.php. | ||
"""Run fuzz test targets. | ||
""" | ||
|
||
import argparse | ||
import configparser | ||
import os | ||
import sys | ||
import subprocess | ||
import logging | ||
|
||
|
||
def main(): | ||
parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter) | ||
parser.add_argument( | ||
"-l", | ||
"--loglevel", | ||
dest="loglevel", | ||
default="INFO", | ||
help="log events at this level and higher to the console. Can be set to DEBUG, INFO, WARNING, ERROR or CRITICAL. Passing --loglevel DEBUG will output all logs to console.", | ||
) | ||
parser.add_argument( | ||
'--export_coverage', | ||
action='store_true', | ||
help='If true, export coverage information to files in the seed corpus', | ||
) | ||
parser.add_argument( | ||
'seed_dir', | ||
help='The seed corpus to run on (must contain subfolders for each fuzz target).', | ||
) | ||
parser.add_argument( | ||
'target', | ||
nargs='*', | ||
help='The target(s) to run. Default is to run all targets.', | ||
) | ||
|
||
args = parser.parse_args() | ||
|
||
# Set up logging | ||
logging.basicConfig( | ||
format='%(message)s', | ||
level=int(args.loglevel) if args.loglevel.isdigit() else args.loglevel.upper(), | ||
) | ||
|
||
# Read config generated by configure. | ||
config = configparser.ConfigParser() | ||
configfile = os.path.abspath(os.path.dirname(__file__)) + "/../config.ini" | ||
config.read_file(open(configfile, encoding="utf8")) | ||
|
||
if not config["components"].getboolean("ENABLE_FUZZ"): | ||
logging.error("Must have fuzz targets built") | ||
sys.exit(1) | ||
|
||
# Build list of tests | ||
test_list_all = parse_test_list(makefile=os.path.join(config["environment"]["SRCDIR"], 'src', 'Makefile.test.include')) | ||
|
||
if not test_list_all: | ||
logging.error("No fuzz targets found") | ||
sys.exit(1) | ||
|
||
logging.info("Fuzz targets found: {}".format(test_list_all)) | ||
|
||
args.target = args.target or test_list_all # By default run all | ||
test_list_error = list(set(args.target).difference(set(test_list_all))) | ||
if test_list_error: | ||
logging.error("Unknown fuzz targets selected: {}".format(test_list_error)) | ||
test_list_selection = list(set(test_list_all).intersection(set(args.target))) | ||
if not test_list_selection: | ||
logging.error("No fuzz targets selected") | ||
logging.info("Fuzz targets selected: {}".format(test_list_selection)) | ||
|
||
try: | ||
help_output = subprocess.run( | ||
args=[ | ||
os.path.join(config["environment"]["BUILDDIR"], 'src', 'test', 'fuzz', test_list_selection[0]), | ||
'-help=1', | ||
], | ||
timeout=1, | ||
check=True, | ||
stderr=subprocess.PIPE, | ||
universal_newlines=True, | ||
).stderr | ||
if "libFuzzer" not in help_output: | ||
logging.error("Must be built with libFuzzer") | ||
sys.exit(1) | ||
except subprocess.TimeoutExpired: | ||
logging.error("subprocess timed out: Currently only libFuzzer is supported") | ||
sys.exit(1) | ||
|
||
run_once( | ||
corpus=args.seed_dir, | ||
test_list=test_list_selection, | ||
build_dir=config["environment"]["BUILDDIR"], | ||
export_coverage=args.export_coverage, | ||
) | ||
|
||
|
||
def run_once(*, corpus, test_list, build_dir, export_coverage): | ||
for t in test_list: | ||
args = [ | ||
os.path.join(build_dir, 'src', 'test', 'fuzz', t), | ||
'-runs=1', | ||
os.path.join(corpus, t), | ||
] | ||
logging.debug('Run {} with args {}'.format(t, args)) | ||
output = subprocess.run(args, check=True, stderr=subprocess.PIPE, universal_newlines=True).stderr | ||
logging.debug('Output: {}'.format(output)) | ||
if not export_coverage: | ||
continue | ||
for l in output.splitlines(): | ||
if 'INITED' in l: | ||
with open(os.path.join(corpus, t + '_coverage'), 'w', encoding='utf-8') as cov_file: | ||
cov_file.write(l) | ||
break | ||
|
||
|
||
def parse_test_list(makefile): | ||
with open(makefile, encoding='utf-8') as makefile_test: | ||
test_list_all = [] | ||
read_targets = False | ||
for line in makefile_test.readlines(): | ||
line = line.strip().replace('test/fuzz/', '').replace(' \\', '') | ||
if read_targets: | ||
if not line: | ||
break | ||
test_list_all.append(line) | ||
continue | ||
|
||
if line == 'FUZZ_TARGETS =': | ||
read_targets = True | ||
return test_list_all | ||
|
||
|
||
if __name__ == '__main__': | ||
main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
This folder contains lint scripts. | ||
|
||
check-doc.py | ||
============ | ||
Check for missing documentation of command line options. | ||
|
||
commit-script-check.sh | ||
====================== | ||
Verification of [scripted diffs](/doc/developer-notes.md#scripted-diffs). | ||
|
||
git-subtree-check.sh | ||
==================== | ||
Run this script from the root of the repository to verify that a subtree matches the contents of | ||
the commit it claims to have been updated to. | ||
|
||
To use, make sure that you have fetched the upstream repository branch in which the subtree is | ||
maintained: | ||
* for `src/secp256k1`: https://github.com/bitcoin-core/secp256k1.git (branch master) | ||
* for `src/leveldb`: https://github.com/bitcoin-core/leveldb.git (branch bitcoin-fork) | ||
* for `src/univalue`: https://github.com/bitcoin-core/univalue.git (branch master) | ||
* for `src/crypto/ctaes`: https://github.com/bitcoin-core/ctaes.git (branch master) | ||
|
||
Usage: `git-subtree-check.sh DIR (COMMIT)` | ||
|
||
`COMMIT` may be omitted, in which case `HEAD` is used. | ||
|
||
lint-all.sh | ||
=========== | ||
Calls other scripts with the `lint-` prefix. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
#!/usr/bin/env python3 | ||
# Copyright (c) 2015-2018 The Bitcoin Core developers | ||
# Distributed under the MIT software license, see the accompanying | ||
# file COPYING or http://www.opensource.org/licenses/mit-license.php. | ||
|
||
''' | ||
This checks if all command line args are documented. | ||
Return value is 0 to indicate no error. | ||
Author: @MarcoFalke | ||
''' | ||
|
||
from subprocess import check_output | ||
import re | ||
import sys | ||
|
||
FOLDER_GREP = 'src' | ||
FOLDER_TEST = 'src/test/' | ||
REGEX_ARG = '(?:ForceSet|SoftSet|Get|Is)(?:Bool)?Args?(?:Set)?\("(-[^"]+)"' | ||
REGEX_DOC = 'AddArg\("(-[^"=]+?)(?:=|")' | ||
CMD_ROOT_DIR = '`git rev-parse --show-toplevel`/{}'.format(FOLDER_GREP) | ||
CMD_GREP_ARGS = r"git grep --perl-regexp '{}' -- {} ':(exclude){}'".format(REGEX_ARG, CMD_ROOT_DIR, FOLDER_TEST) | ||
CMD_GREP_DOCS = r"git grep --perl-regexp '{}' {}".format(REGEX_DOC, CMD_ROOT_DIR) | ||
# list unsupported, deprecated and duplicate args as they need no documentation | ||
SET_DOC_OPTIONAL = set(['-h', '-help', '-dbcrashratio', '-forcecompactdb']) | ||
|
||
|
||
def main(): | ||
if sys.version_info >= (3, 6): | ||
used = check_output(CMD_GREP_ARGS, shell=True, universal_newlines=True, encoding='utf8') | ||
docd = check_output(CMD_GREP_DOCS, shell=True, universal_newlines=True, encoding='utf8') | ||
else: | ||
used = check_output(CMD_GREP_ARGS, shell=True).decode('utf8').strip() | ||
docd = check_output(CMD_GREP_DOCS, shell=True).decode('utf8').strip() | ||
|
||
args_used = set(re.findall(re.compile(REGEX_ARG), used)) | ||
args_docd = set(re.findall(re.compile(REGEX_DOC), docd)).union(SET_DOC_OPTIONAL) | ||
args_need_doc = args_used.difference(args_docd) | ||
args_unknown = args_docd.difference(args_used) | ||
|
||
print("Args used : {}".format(len(args_used))) | ||
print("Args documented : {}".format(len(args_docd))) | ||
print("Args undocumented: {}".format(len(args_need_doc))) | ||
print(args_need_doc) | ||
print("Args unknown : {}".format(len(args_unknown))) | ||
print(args_unknown) | ||
|
||
sys.exit(len(args_need_doc)) | ||
|
||
|
||
if __name__ == "__main__": | ||
main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,162 @@ | ||
#!/usr/bin/env python3 | ||
# Copyright (c) 2017-2018 The Bitcoin Core developers | ||
# Distributed under the MIT software license, see the accompanying | ||
# file COPYING or http://www.opensource.org/licenses/mit-license.php. | ||
"""Check RPC argument consistency.""" | ||
|
||
from collections import defaultdict | ||
import os | ||
import re | ||
import sys | ||
|
||
# Source files (relative to root) to scan for dispatch tables | ||
SOURCES = [ | ||
"src/rpc/server.cpp", | ||
"src/rpc/blockchain.cpp", | ||
"src/rpc/mining.cpp", | ||
"src/rpc/misc.cpp", | ||
"src/rpc/net.cpp", | ||
"src/rpc/rawtransaction.cpp", | ||
"src/wallet/rpcwallet.cpp", | ||
] | ||
# Source file (relative to root) containing conversion mapping | ||
SOURCE_CLIENT = 'src/rpc/client.cpp' | ||
# Argument names that should be ignored in consistency checks | ||
IGNORE_DUMMY_ARGS = {'dummy', 'arg0', 'arg1', 'arg2', 'arg3', 'arg4', 'arg5', 'arg6', 'arg7', 'arg8', 'arg9'} | ||
|
||
class RPCCommand: | ||
def __init__(self, name, args): | ||
self.name = name | ||
self.args = args | ||
|
||
class RPCArgument: | ||
def __init__(self, names, idx): | ||
self.names = names | ||
self.idx = idx | ||
self.convert = False | ||
|
||
def parse_string(s): | ||
assert s[0] == '"' | ||
assert s[-1] == '"' | ||
return s[1:-1] | ||
|
||
def process_commands(fname): | ||
"""Find and parse dispatch table in implementation file `fname`.""" | ||
cmds = [] | ||
in_rpcs = False | ||
with open(fname, "r", encoding="utf8") as f: | ||
for line in f: | ||
line = line.rstrip() | ||
if not in_rpcs: | ||
if re.match("static const CRPCCommand .*\[\] =", line): | ||
in_rpcs = True | ||
else: | ||
if line.startswith('};'): | ||
in_rpcs = False | ||
elif '{' in line and '"' in line: | ||
m = re.search('{ *("[^"]*"), *("[^"]*"), *&([^,]*), *{([^}]*)} *},', line) | ||
assert m, 'No match to table expression: %s' % line | ||
name = parse_string(m.group(2)) | ||
args_str = m.group(4).strip() | ||
if args_str: | ||
args = [RPCArgument(parse_string(x.strip()).split('|'), idx) for idx, x in enumerate(args_str.split(','))] | ||
else: | ||
args = [] | ||
cmds.append(RPCCommand(name, args)) | ||
assert not in_rpcs and cmds, "Something went wrong with parsing the C++ file: update the regexps" | ||
return cmds | ||
|
||
def process_mapping(fname): | ||
"""Find and parse conversion table in implementation file `fname`.""" | ||
cmds = [] | ||
in_rpcs = False | ||
with open(fname, "r", encoding="utf8") as f: | ||
for line in f: | ||
line = line.rstrip() | ||
if not in_rpcs: | ||
if line == 'static const CRPCConvertParam vRPCConvertParams[] =': | ||
in_rpcs = True | ||
else: | ||
if line.startswith('};'): | ||
in_rpcs = False | ||
elif '{' in line and '"' in line: | ||
m = re.search('{ *("[^"]*"), *([0-9]+) *, *("[^"]*") *},', line) | ||
assert m, 'No match to table expression: %s' % line | ||
name = parse_string(m.group(1)) | ||
idx = int(m.group(2)) | ||
argname = parse_string(m.group(3)) | ||
cmds.append((name, idx, argname)) | ||
assert not in_rpcs and cmds | ||
return cmds | ||
|
||
def main(): | ||
if len(sys.argv) != 2: | ||
print('Usage: {} ROOT-DIR'.format(sys.argv[0]), file=sys.stderr) | ||
sys.exit(1) | ||
|
||
root = sys.argv[1] | ||
|
||
# Get all commands from dispatch tables | ||
cmds = [] | ||
for fname in SOURCES: | ||
cmds += process_commands(os.path.join(root, fname)) | ||
|
||
cmds_by_name = {} | ||
for cmd in cmds: | ||
cmds_by_name[cmd.name] = cmd | ||
|
||
# Get current convert mapping for client | ||
client = SOURCE_CLIENT | ||
mapping = set(process_mapping(os.path.join(root, client))) | ||
|
||
print('* Checking consistency between dispatch tables and vRPCConvertParams') | ||
|
||
# Check mapping consistency | ||
errors = 0 | ||
for (cmdname, argidx, argname) in mapping: | ||
try: | ||
rargnames = cmds_by_name[cmdname].args[argidx].names | ||
except IndexError: | ||
print('ERROR: %s argument %i (named %s in vRPCConvertParams) is not defined in dispatch table' % (cmdname, argidx, argname)) | ||
errors += 1 | ||
continue | ||
if argname not in rargnames: | ||
print('ERROR: %s argument %i is named %s in vRPCConvertParams but %s in dispatch table' % (cmdname, argidx, argname, rargnames), file=sys.stderr) | ||
errors += 1 | ||
|
||
# Check for conflicts in vRPCConvertParams conversion | ||
# All aliases for an argument must either be present in the | ||
# conversion table, or not. Anything in between means an oversight | ||
# and some aliases won't work. | ||
for cmd in cmds: | ||
for arg in cmd.args: | ||
convert = [((cmd.name, arg.idx, argname) in mapping) for argname in arg.names] | ||
if any(convert) != all(convert): | ||
print('ERROR: %s argument %s has conflicts in vRPCConvertParams conversion specifier %s' % (cmd.name, arg.names, convert)) | ||
errors += 1 | ||
arg.convert = all(convert) | ||
|
||
# Check for conversion difference by argument name. | ||
# It is preferable for API consistency that arguments with the same name | ||
# have the same conversion, so bin by argument name. | ||
all_methods_by_argname = defaultdict(list) | ||
converts_by_argname = defaultdict(list) | ||
for cmd in cmds: | ||
for arg in cmd.args: | ||
for argname in arg.names: | ||
all_methods_by_argname[argname].append(cmd.name) | ||
converts_by_argname[argname].append(arg.convert) | ||
|
||
for argname, convert in converts_by_argname.items(): | ||
if all(convert) != any(convert): | ||
if argname in IGNORE_DUMMY_ARGS: | ||
# these are testing or dummy, don't warn for them | ||
continue | ||
print('WARNING: conversion mismatch for argument named %s (%s)' % | ||
(argname, list(zip(all_methods_by_argname[argname], converts_by_argname[argname])))) | ||
|
||
sys.exit(errors > 0) | ||
|
||
|
||
if __name__ == '__main__': | ||
main() |
Oops, something went wrong.