This moves CWallet members and methods dealing with keys to a new LegacyScriptPubKeyMan class, and updates calling code to reference the new class instead of CWallet. Most of the changes are simple text replacements and variable substitutions easily verified with: git log -p -n1 -U0 --word-diff-regex=. The only nontrivial chunk of code added is the new LegacyScriptPubKeyMan class declaration, but this code isn't new and is just selectively copied and moved from the previous CWallet class declaration. This can be verified with: git log -p -n1 --color-moved=dimmed_zebra src/wallet/scriptpubkeyman.h src/wallet/wallet.h or git diff HEAD~1:src/wallet/wallet.h HEAD:src/wallet/scriptpubkeyman.h This commit does not change behavior.
292 lines
13 KiB
Python
Executable file
292 lines
13 KiB
Python
Executable file
#!/usr/bin/env python3
|
|
#
|
|
# Copyright (c) 2018-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.
|
|
#
|
|
# Lint format strings: This program checks that the number of arguments passed
|
|
# to a variadic format string function matches the number of format specifiers
|
|
# in the format string.
|
|
|
|
import argparse
|
|
import re
|
|
import sys
|
|
|
|
FALSE_POSITIVES = [
|
|
("src/dbwrapper.cpp", "vsnprintf(p, limit - p, format, backup_ap)"),
|
|
("src/index/base.cpp", "FatalError(const char* fmt, const Args&... args)"),
|
|
("src/netbase.cpp", "LogConnectFailure(bool manual_connection, const char* fmt, const Args&... args)"),
|
|
("src/util/system.cpp", "strprintf(_(COPYRIGHT_HOLDERS).translated, COPYRIGHT_HOLDERS_SUBSTITUTION)"),
|
|
("src/wallet/wallet.h", "WalletLogPrintf(std::string fmt, Params... parameters)"),
|
|
("src/wallet/wallet.h", "LogPrintf((\"%s \" + fmt).c_str(), GetDisplayName(), parameters...)"),
|
|
("src/logging.h", "LogPrintf(const char* fmt, const Args&... args)"),
|
|
("src/wallet/scriptpubkeyman.h", "WalletLogPrintf(const std::string& fmt, const Params&... parameters)"),
|
|
("src/wallet/scriptpubkeyman.cpp", "WalletLogPrintf(fmt, parameters...)"),
|
|
("src/wallet/scriptpubkeyman.cpp", "WalletLogPrintf(const std::string& fmt, const Params&... parameters)"),
|
|
]
|
|
|
|
|
|
def parse_function_calls(function_name, source_code):
|
|
"""Return an array with all calls to function function_name in string source_code.
|
|
Preprocessor directives and C++ style comments ("//") in source_code are removed.
|
|
|
|
>>> len(parse_function_calls("foo", "foo();bar();foo();bar();"))
|
|
2
|
|
>>> parse_function_calls("foo", "foo(1);bar(1);foo(2);bar(2);")[0].startswith("foo(1);")
|
|
True
|
|
>>> parse_function_calls("foo", "foo(1);bar(1);foo(2);bar(2);")[1].startswith("foo(2);")
|
|
True
|
|
>>> len(parse_function_calls("foo", "foo();bar();// foo();bar();"))
|
|
1
|
|
>>> len(parse_function_calls("foo", "#define FOO foo();"))
|
|
0
|
|
"""
|
|
assert type(function_name) is str and type(source_code) is str and function_name
|
|
lines = [re.sub("// .*", " ", line).strip()
|
|
for line in source_code.split("\n")
|
|
if not line.strip().startswith("#")]
|
|
return re.findall(r"[^a-zA-Z_](?=({}\(.*).*)".format(function_name), " " + " ".join(lines))
|
|
|
|
|
|
def normalize(s):
|
|
"""Return a normalized version of string s with newlines, tabs and C style comments ("/* ... */")
|
|
replaced with spaces. Multiple spaces are replaced with a single space.
|
|
|
|
>>> normalize(" /* nothing */ foo\tfoo /* bar */ foo ")
|
|
'foo foo foo'
|
|
"""
|
|
assert type(s) is str
|
|
s = s.replace("\n", " ")
|
|
s = s.replace("\t", " ")
|
|
s = re.sub(r"/\*.*?\*/", " ", s)
|
|
s = re.sub(" {2,}", " ", s)
|
|
return s.strip()
|
|
|
|
|
|
ESCAPE_MAP = {
|
|
r"\n": "[escaped-newline]",
|
|
r"\t": "[escaped-tab]",
|
|
r'\"': "[escaped-quote]",
|
|
}
|
|
|
|
|
|
def escape(s):
|
|
"""Return the escaped version of string s with "\\\"", "\\n" and "\\t" escaped as
|
|
"[escaped-backslash]", "[escaped-newline]" and "[escaped-tab]".
|
|
|
|
>>> unescape(escape("foo")) == "foo"
|
|
True
|
|
>>> escape(r'foo \\t foo \\n foo \\\\ foo \\ foo \\"bar\\"')
|
|
'foo [escaped-tab] foo [escaped-newline] foo \\\\\\\\ foo \\\\ foo [escaped-quote]bar[escaped-quote]'
|
|
"""
|
|
assert type(s) is str
|
|
for raw_value, escaped_value in ESCAPE_MAP.items():
|
|
s = s.replace(raw_value, escaped_value)
|
|
return s
|
|
|
|
|
|
def unescape(s):
|
|
"""Return the unescaped version of escaped string s.
|
|
Reverses the replacements made in function escape(s).
|
|
|
|
>>> unescape(escape("bar"))
|
|
'bar'
|
|
>>> unescape("foo [escaped-tab] foo [escaped-newline] foo \\\\\\\\ foo \\\\ foo [escaped-quote]bar[escaped-quote]")
|
|
'foo \\\\t foo \\\\n foo \\\\\\\\ foo \\\\ foo \\\\"bar\\\\"'
|
|
"""
|
|
assert type(s) is str
|
|
for raw_value, escaped_value in ESCAPE_MAP.items():
|
|
s = s.replace(escaped_value, raw_value)
|
|
return s
|
|
|
|
|
|
def parse_function_call_and_arguments(function_name, function_call):
|
|
"""Split string function_call into an array of strings consisting of:
|
|
* the string function_call followed by "("
|
|
* the function call argument #1
|
|
* ...
|
|
* the function call argument #n
|
|
* a trailing ");"
|
|
|
|
The strings returned are in escaped form. See escape(...).
|
|
|
|
>>> parse_function_call_and_arguments("foo", 'foo("%s", "foo");')
|
|
['foo(', '"%s",', ' "foo"', ')']
|
|
>>> parse_function_call_and_arguments("foo", 'foo("%s", "foo");')
|
|
['foo(', '"%s",', ' "foo"', ')']
|
|
>>> parse_function_call_and_arguments("foo", 'foo("%s %s", "foo", "bar");')
|
|
['foo(', '"%s %s",', ' "foo",', ' "bar"', ')']
|
|
>>> parse_function_call_and_arguments("fooprintf", 'fooprintf("%050d", i);')
|
|
['fooprintf(', '"%050d",', ' i', ')']
|
|
>>> parse_function_call_and_arguments("foo", 'foo(bar(foobar(barfoo("foo"))), foobar); barfoo')
|
|
['foo(', 'bar(foobar(barfoo("foo"))),', ' foobar', ')']
|
|
>>> parse_function_call_and_arguments("foo", "foo()")
|
|
['foo(', '', ')']
|
|
>>> parse_function_call_and_arguments("foo", "foo(123)")
|
|
['foo(', '123', ')']
|
|
>>> parse_function_call_and_arguments("foo", 'foo("foo")')
|
|
['foo(', '"foo"', ')']
|
|
>>> parse_function_call_and_arguments("strprintf", 'strprintf("%s (%d)", std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>,wchar_t>().to_bytes(buf), err);')
|
|
['strprintf(', '"%s (%d)",', ' std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>,wchar_t>().to_bytes(buf),', ' err', ')']
|
|
>>> parse_function_call_and_arguments("strprintf", 'strprintf("%s (%d)", foo<wchar_t>().to_bytes(buf), err);')
|
|
['strprintf(', '"%s (%d)",', ' foo<wchar_t>().to_bytes(buf),', ' err', ')']
|
|
>>> parse_function_call_and_arguments("strprintf", 'strprintf("%s (%d)", foo().to_bytes(buf), err);')
|
|
['strprintf(', '"%s (%d)",', ' foo().to_bytes(buf),', ' err', ')']
|
|
>>> parse_function_call_and_arguments("strprintf", 'strprintf("%s (%d)", foo << 1, err);')
|
|
['strprintf(', '"%s (%d)",', ' foo << 1,', ' err', ')']
|
|
>>> parse_function_call_and_arguments("strprintf", 'strprintf("%s (%d)", foo<bar>() >> 1, err);')
|
|
['strprintf(', '"%s (%d)",', ' foo<bar>() >> 1,', ' err', ')']
|
|
>>> parse_function_call_and_arguments("strprintf", 'strprintf("%s (%d)", foo < 1 ? bar : foobar, err);')
|
|
['strprintf(', '"%s (%d)",', ' foo < 1 ? bar : foobar,', ' err', ')']
|
|
>>> parse_function_call_and_arguments("strprintf", 'strprintf("%s (%d)", foo < 1, err);')
|
|
['strprintf(', '"%s (%d)",', ' foo < 1,', ' err', ')']
|
|
>>> parse_function_call_and_arguments("strprintf", 'strprintf("%s (%d)", foo > 1 ? bar : foobar, err);')
|
|
['strprintf(', '"%s (%d)",', ' foo > 1 ? bar : foobar,', ' err', ')']
|
|
>>> parse_function_call_and_arguments("strprintf", 'strprintf("%s (%d)", foo > 1, err);')
|
|
['strprintf(', '"%s (%d)",', ' foo > 1,', ' err', ')']
|
|
>>> parse_function_call_and_arguments("strprintf", 'strprintf("%s (%d)", foo <= 1, err);')
|
|
['strprintf(', '"%s (%d)",', ' foo <= 1,', ' err', ')']
|
|
>>> parse_function_call_and_arguments("strprintf", 'strprintf("%s (%d)", foo <= bar<1, 2>(1, 2), err);')
|
|
['strprintf(', '"%s (%d)",', ' foo <= bar<1, 2>(1, 2),', ' err', ')']
|
|
>>> parse_function_call_and_arguments("strprintf", 'strprintf("%s (%d)", foo>foo<1,2>(1,2)?bar:foobar,err)');
|
|
['strprintf(', '"%s (%d)",', ' foo>foo<1,2>(1,2)?bar:foobar,', 'err', ')']
|
|
>>> parse_function_call_and_arguments("strprintf", 'strprintf("%s (%d)", foo>foo<1,2>(1,2),err)');
|
|
['strprintf(', '"%s (%d)",', ' foo>foo<1,2>(1,2),', 'err', ')']
|
|
"""
|
|
assert type(function_name) is str and type(function_call) is str and function_name
|
|
remaining = normalize(escape(function_call))
|
|
expected_function_call = "{}(".format(function_name)
|
|
assert remaining.startswith(expected_function_call)
|
|
parts = [expected_function_call]
|
|
remaining = remaining[len(expected_function_call):]
|
|
open_parentheses = 1
|
|
open_template_arguments = 0
|
|
in_string = False
|
|
parts.append("")
|
|
for i, char in enumerate(remaining):
|
|
parts.append(parts.pop() + char)
|
|
if char == "\"":
|
|
in_string = not in_string
|
|
continue
|
|
if in_string:
|
|
continue
|
|
if char == "(":
|
|
open_parentheses += 1
|
|
continue
|
|
if char == ")":
|
|
open_parentheses -= 1
|
|
if open_parentheses > 1:
|
|
continue
|
|
if open_parentheses == 0:
|
|
parts.append(parts.pop()[:-1])
|
|
parts.append(char)
|
|
break
|
|
prev_char = remaining[i - 1] if i - 1 >= 0 else None
|
|
next_char = remaining[i + 1] if i + 1 <= len(remaining) - 1 else None
|
|
if char == "<" and next_char not in [" ", "<", "="] and prev_char not in [" ", "<"]:
|
|
open_template_arguments += 1
|
|
continue
|
|
if char == ">" and next_char not in [" ", ">", "="] and prev_char not in [" ", ">"] and open_template_arguments > 0:
|
|
open_template_arguments -= 1
|
|
if open_template_arguments > 0:
|
|
continue
|
|
if char == ",":
|
|
parts.append("")
|
|
return parts
|
|
|
|
|
|
def parse_string_content(argument):
|
|
"""Return the text within quotes in string argument.
|
|
|
|
>>> parse_string_content('1 "foo %d bar" 2')
|
|
'foo %d bar'
|
|
>>> parse_string_content('1 foobar 2')
|
|
''
|
|
>>> parse_string_content('1 "bar" 2')
|
|
'bar'
|
|
>>> parse_string_content('1 "foo" 2 "bar" 3')
|
|
'foobar'
|
|
>>> parse_string_content('1 "foo" 2 " " "bar" 3')
|
|
'foo bar'
|
|
>>> parse_string_content('""')
|
|
''
|
|
>>> parse_string_content('')
|
|
''
|
|
>>> parse_string_content('1 2 3')
|
|
''
|
|
"""
|
|
assert type(argument) is str
|
|
string_content = ""
|
|
in_string = False
|
|
for char in normalize(escape(argument)):
|
|
if char == "\"":
|
|
in_string = not in_string
|
|
elif in_string:
|
|
string_content += char
|
|
return string_content
|
|
|
|
|
|
def count_format_specifiers(format_string):
|
|
"""Return the number of format specifiers in string format_string.
|
|
|
|
>>> count_format_specifiers("foo bar foo")
|
|
0
|
|
>>> count_format_specifiers("foo %d bar foo")
|
|
1
|
|
>>> count_format_specifiers("foo %d bar %i foo")
|
|
2
|
|
>>> count_format_specifiers("foo %d bar %i foo %% foo")
|
|
2
|
|
>>> count_format_specifiers("foo %d bar %i foo %% foo %d foo")
|
|
3
|
|
>>> count_format_specifiers("foo %d bar %i foo %% foo %*d foo")
|
|
4
|
|
"""
|
|
assert type(format_string) is str
|
|
format_string = format_string.replace('%%', 'X')
|
|
n = 0
|
|
in_specifier = False
|
|
for i, char in enumerate(format_string):
|
|
if char == "%":
|
|
in_specifier = True
|
|
n += 1
|
|
elif char in "aAcdeEfFgGinopsuxX":
|
|
in_specifier = False
|
|
elif in_specifier and char == "*":
|
|
n += 1
|
|
return n
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description="This program checks that the number of arguments passed "
|
|
"to a variadic format string function matches the number of format "
|
|
"specifiers in the format string.")
|
|
parser.add_argument("--skip-arguments", type=int, help="number of arguments before the format string "
|
|
"argument (e.g. 1 in the case of fprintf)", default=0)
|
|
parser.add_argument("function_name", help="function name (e.g. fprintf)", default=None)
|
|
parser.add_argument("file", nargs="*", help="C++ source code file (e.g. foo.cpp)")
|
|
args = parser.parse_args()
|
|
exit_code = 0
|
|
for filename in args.file:
|
|
with open(filename, "r", encoding="utf-8") as f:
|
|
for function_call_str in parse_function_calls(args.function_name, f.read()):
|
|
parts = parse_function_call_and_arguments(args.function_name, function_call_str)
|
|
relevant_function_call_str = unescape("".join(parts))[:512]
|
|
if (f.name, relevant_function_call_str) in FALSE_POSITIVES:
|
|
continue
|
|
if len(parts) < 3 + args.skip_arguments:
|
|
exit_code = 1
|
|
print("{}: Could not parse function call string \"{}(...)\": {}".format(f.name, args.function_name, relevant_function_call_str))
|
|
continue
|
|
argument_count = len(parts) - 3 - args.skip_arguments
|
|
format_str = parse_string_content(parts[1 + args.skip_arguments])
|
|
format_specifier_count = count_format_specifiers(format_str)
|
|
if format_specifier_count != argument_count:
|
|
exit_code = 1
|
|
print("{}: Expected {} argument(s) after format string but found {} argument(s): {}".format(f.name, format_specifier_count, argument_count, relevant_function_call_str))
|
|
continue
|
|
sys.exit(exit_code)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|