2018-08-07 15:26:19 +02:00
#!/usr/bin/env python3
#
# Copyright (c) 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.
#
# 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 = [
2020-07-29 15:29:43 +02:00
( " src/batchedlogger.h " , " strprintf(fmt, args...) " ) ,
2018-08-07 15:26:19 +02:00
( " src/dbwrapper.cpp " , " vsnprintf(p, limit - p, format, backup_ap) " ) ,
( " src/index/base.cpp " , " FatalError(const char* fmt, const Args&... args) " ) ,
2021-05-25 12:48:04 +02:00
( " src/index/txindex.cpp " , " FatalError(const char* fmt, const Args&... args) " ) ,
2018-08-07 15:26:19 +02:00
( " src/netbase.cpp " , " LogConnectFailure(bool manual_connection, const char* fmt, const Args&... args) " ) ,
2020-07-29 15:29:43 +02:00
( " src/qt/networkstyle.cpp " , " strprintf(appName, gArgs.GetDevNetName()) " ) ,
( " src/qt/networkstyle.cpp " , " strprintf(titleAddText, gArgs.GetDevNetName()) " ) ,
2022-10-21 16:49:43 +02:00
( " src/rpc/evo.cpp " , " strprintf(it->second, nParamNum) " ) ,
2020-07-29 15:29:43 +02:00
( " src/stacktraces.cpp " , " strprintf(fmtStr, i, si.pc, lstr, fstr) " ) ,
2022-03-24 05:13:51 +01:00
( " src/util/system.cpp " , " strprintf(_(COPYRIGHT_HOLDERS).translated, COPYRIGHT_HOLDERS_SUBSTITUTION) " ) ,
2020-01-09 20:37:09 +01:00
( " src/validationinterface.cpp " , " LogPrint(BCLog::VALIDATION, fmt \" \\ n \" , __VA_ARGS__) " ) ,
2018-08-07 15:26:19 +02:00
( " src/wallet/wallet.h " , " WalletLogPrintf(std::string fmt, Params... parameters) " ) ,
( " src/wallet/wallet.h " , " LogPrintf(( \" %s \" + fmt).c_str(), GetDisplayName(), parameters...) " ) ,
Merge #17261: Make ScriptPubKeyMan an actual interface and the wallet to have multiple
3f373659d732a5b1e5fdc692a45b2b8179f66bec Refactor: Replace SigningProvider pointers with unique_ptrs (Andrew Chow)
3afe53c4039103670cec5f9cace897ead76e20a8 Cleanup: Drop unused GUI learnRelatedScripts method (Andrew Chow)
e2f02aa59e3402048269362ff692d49a6df35cfd Refactor: Copy CWallet signals and print function to LegacyScriptPubKeyMan (Andrew Chow)
c729afd0a3b74a3943e4c359270beaf3e6ff8a7b Box the wallet: Add multiple keyman maps and loops (Andrew Chow)
4977c30d59e88a3e5ee248144bcc023debcd895b refactor: define a UINT256_ONE global constant (Andrew Chow)
415afcccd3e5583defdb76e3a280f48e98983301 HD Split: Avoid redundant upgrades (Andrew Chow)
01b4511206e399981a77976deb15785d18db46ae Make UpgradeKeyMetadata work only on LegacyScriptPubKeyMan (Andrew Chow)
4a7e43e8460127a40a7895519587399feff3b682 Store p2sh scripts in AddAndGetDestinationForScript (Andrew Chow)
501acb5538008d98abe79288b92040bc186b93f3 Always try to sign for all pubkeys in multisig (Andrew Chow)
81610eddbc57c46ae243f45d73e715d509f53a6c List output types in an array in order to be iterated over (Andrew Chow)
eb81fc3ee58d3e88af36d8091b9e4017a8603b3c Refactor: Allow LegacyScriptPubKeyMan to be null (Andrew Chow)
fadc08ad944cad42e805228cdd58e0332f4d7184 Locking: Lock cs_KeyStore instead of cs_wallet in legacy keyman (Andrew Chow)
f5be479694d4dbaf59eef562d80fbeacb3bb7dc1 wallet: Improve CWallet:MarkDestinationsDirty (João Barbosa)
Pull request description:
Continuation of wallet boxes project.
Actually makes ScriptPubKeyMan an interface which LegacyScriptPubkeyMan. Moves around functions and things from CWallet into LegacyScriptPubKeyMan so that they are actually separate things without circular dependencies.
***
Introducing the `ScriptPubKeyMan` (short for ScriptPubKeyManager) for managing scriptPubKeys and their associated scripts and keys. This functionality is moved over from `CWallet`. Instead, `CWallet` will have a pointer to a `ScriptPubKeyMan` for every possible address type, internal and external. It will fetch the correct `ScriptPubKeyMan` as necessary. When fetching new addresses, it chooses the `ScriptPubKeyMan` based on address type and whether it is change. For signing, it takes the script and asks each `ScriptPubKeyMan` for whether that `ScriptPubKeyMan` considers that script `IsMine`, whether it has that script, or whether it is able to produce a signature for it. If so, the `ScriptPubKeyMan` will provide a `SigningProvider` to the caller which will use that in order to sign.
There is currently one `ScriptPubKeyMan` - the `LegacyScriptPubKeyMan`. Each `CWallet` will have only one `LegacyScriptPubKeyMan` with the pointers for all of the address types and change pointing to this `LegacyScriptPubKeyMan`. It is created when the wallet is loaded and all keys and metadata are loaded into it instead of `CWallet`. The `LegacyScriptPubKeyMan` is primarily made up of all of the key and script management that used to be in `CWallet`. For convenience, `CWallet` has a `GetLegacyScriptPubKeyMan` which will return the `LegacyScriptPubKeyMan` or a `nullptr` if it does not have one (not yet implemented, but callers will check for the `nullptr`). For purposes of signing, `LegacyScriptPubKeyMan`'s `GetSigningProvider` will return itself rather than a separate `SigningProvider`. This will be different for future `ScriptPubKeyMan`s.
The `LegacyScriptPubKeyMan` will also handle the importing and exporting of keys and scripts instead of `CWallet`. As such, a number of RPCs have been limited to work only if a `LegacyScriptPubKeyMan` can be retrieved from the wallet. These RPCs are `sethdseed`, `addmultisigaddress`, `importaddress`, `importprivkey`, `importpubkey`, `importmulti`, `dumpprivkey`, and `dumpwallet`. Other RPCs which relied on the wallet for scripts and keys have been modified in order to take the `SigningProvider` retrieved from the `ScriptPubKeyMan` for a given script.
Overall, these changes should not effect how everything actually works and the user should experience no difference between having this change and not having it. As such, no functional tests were changed, and the only unit tests changed were those that were directly accessing `CWallet` functions that have been removed.
This PR is the last step in the [Wallet Structure Changes](https://github.com/bitcoin-core/bitcoin-devwiki/wiki/Wallet-Class-Structure-Changes).
ACKs for top commit:
instagibbs:
re-utACK https://github.com/bitcoin/bitcoin/pull/17261/commits/3f373659d732a5b1e5fdc692a45b2b8179f66bec
Sjors:
re-utACK 3f373659d732a5b1e5fdc692a45b2b8179f66bec (it still compiles on macOS after https://github.com/bitcoin/bitcoin/pull/17261#discussion_r370377070)
meshcollider:
Tested re-ACK 3f373659d732a5b1e5fdc692a45b2b8179f66bec
Tree-SHA512: f8e2b8d9efa750b617691e8702d217ec4c33569ec2554a060141d9eb9b9a3a5323e4216938e2485c44625d7a6e0925d40dea1362b3af9857cf08860c2f344716
2019-10-07 20:11:34 +02:00
( " src/wallet/scriptpubkeyman.h " , " WalletLogPrintf(std::string fmt, Params... parameters) " ) ,
( " src/wallet/scriptpubkeyman.h " , " LogPrintf(( \" %s \" + fmt).c_str(), m_storage.GetDisplayName(), parameters...) " ) ,
2018-09-12 17:21:01 +02:00
( " src/logging.h " , " LogPrintf(const char* fmt, const Args&... args) " ) ,
2022-08-08 18:05:21 +02:00
( " src/wallet/scriptpubkeyman.h " , " WalletLogPrintf(const std::string& fmt, const Params&... parameters) " ) ,
2018-08-07 15:26:19 +02:00
]
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
"""
2021-08-27 21:03:02 +02:00
assert type ( function_name ) is str and type ( source_code ) is str and function_name
2018-08-07 15:26:19 +02:00
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 \t foo /* bar */ foo " )
' foo foo foo '
"""
2021-08-27 21:03:02 +02:00
assert type ( s ) is str
2018-08-07 15:26:19 +02:00
s = s . replace ( " \n " , " " )
s = s . replace ( " \t " , " " )
2021-09-10 02:45:16 +02:00
s = re . sub ( r " / \ *.*? \ */ " , " " , s )
2018-08-07 15:26:19 +02:00
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] '
"""
2021-08-27 21:03:02 +02:00
assert type ( s ) is str
2018-08-07 15:26:19 +02:00
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 \\ \\ " '
"""
2021-08-27 21:03:02 +02:00
assert type ( s ) is str
2018-08-07 15:26:19 +02:00
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 " ' , ' ) ' ]
2018-08-13 13:07:23 +02:00
>> > 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 ' , ' ) ' ]
2018-08-07 15:26:19 +02:00
"""
2021-08-27 21:03:02 +02:00
assert type ( function_name ) is str and type ( function_call ) is str and function_name
2018-08-07 15:26:19 +02:00
remaining = normalize ( escape ( function_call ) )
expected_function_call = " {} ( " . format ( function_name )
2021-08-27 21:03:02 +02:00
assert remaining . startswith ( expected_function_call )
2018-08-07 15:26:19 +02:00
parts = [ expected_function_call ]
remaining = remaining [ len ( expected_function_call ) : ]
open_parentheses = 1
2018-08-13 13:07:23 +02:00
open_template_arguments = 0
2018-08-07 15:26:19 +02:00
in_string = False
parts . append ( " " )
2018-08-13 13:07:23 +02:00
for i , char in enumerate ( remaining ) :
2018-08-07 15:26:19 +02:00
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
2018-08-13 13:07:23 +02:00
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
2018-08-07 15:26:19 +02:00
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 ' )
' '
"""
2021-08-27 21:03:02 +02:00
assert type ( argument ) is str
2018-08-07 15:26:19 +02:00
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
"""
2021-08-27 21:03:02 +02:00
assert type ( format_string ) is str
2018-12-22 17:13:27 +01:00
format_string = format_string . replace ( ' %% ' , ' X ' )
2018-08-07 15:26:19 +02:00
n = 0
in_specifier = False
for i , char in enumerate ( format_string ) :
2018-12-22 17:13:27 +01:00
if char == " % " :
2018-08-07 15:26:19 +02:00
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 )
2019-03-05 15:40:19 +01:00
parser . add_argument ( " file " , nargs = " * " , help = " C++ source code file (e.g. foo.cpp) " )
2018-08-07 15:26:19 +02:00
args = parser . parse_args ( )
2024-11-23 16:02:36 +01:00
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 )
2018-08-07 15:26:19 +02:00
if __name__ == " __main__ " :
main ( )