fuzz coverage

Coverage Report

Created: 2025-09-17 22:41

/Users/eugenesiegel/btc/bitcoin/src/common/settings.cpp
Line
Count
Source (jump to first uncovered line)
1
// Copyright (c) 2019-2022 The Bitcoin Core developers
2
// Distributed under the MIT software license, see the accompanying
3
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4
5
#include <common/settings.h>
6
7
#include <bitcoin-build-config.h> // IWYU pragma: keep
8
9
#include <tinyformat.h>
10
#include <univalue.h>
11
#include <util/fs.h>
12
13
#include <algorithm>
14
#include <fstream>
15
#include <iterator>
16
#include <map>
17
#include <string>
18
#include <utility>
19
#include <vector>
20
21
namespace common {
22
namespace {
23
24
enum class Source {
25
   FORCED,
26
   COMMAND_LINE,
27
   RW_SETTINGS,
28
   CONFIG_FILE_NETWORK_SECTION,
29
   CONFIG_FILE_DEFAULT_SECTION
30
};
31
32
// Json object key for the auto-generated warning comment
33
const std::string SETTINGS_WARN_MSG_KEY{"_warning_"};
34
35
//! Merge settings from multiple sources in precedence order:
36
//! Forced config > command line > read-write settings file > config file network-specific section > config file default section
37
//!
38
//! This function is provided with a callback function fn that contains
39
//! specific logic for how to merge the sources.
40
template <typename Fn>
41
static void MergeSettings(const Settings& settings, const std::string& section, const std::string& name, Fn&& fn)
42
6.40M
{
43
    // Merge in the forced settings
44
6.40M
    if (auto* value = FindKey(settings.forced_settings, name)) {
45
311k
        fn(SettingsSpan(*value), Source::FORCED);
46
311k
    }
47
    // Merge in the command-line options
48
6.40M
    if (auto* values = FindKey(settings.command_line_options, name)) {
49
505k
        fn(SettingsSpan(*values), Source::COMMAND_LINE);
50
505k
    }
51
    // Merge in the read-write settings
52
6.40M
    if (const SettingsValue* value = FindKey(settings.rw_settings, name)) {
53
0
        fn(SettingsSpan(*value), Source::RW_SETTINGS);
54
0
    }
55
    // Merge in the network-specific section of the config file
56
6.40M
    if (!section.empty()) {
57
5.70M
        if (auto* map = FindKey(settings.ro_config, section)) {
58
0
            if (auto* values = FindKey(*map, name)) {
59
0
                fn(SettingsSpan(*values), Source::CONFIG_FILE_NETWORK_SECTION);
60
0
            }
61
0
        }
62
5.70M
    }
63
    // Merge in the default section of the config file
64
6.40M
    if (auto* map = FindKey(settings.ro_config, "")) {
65
0
        if (auto* values = FindKey(*map, name)) {
66
0
            fn(SettingsSpan(*values), Source::CONFIG_FILE_DEFAULT_SECTION);
67
0
        }
68
0
    }
69
6.40M
}
settings.cpp:_ZN6common12_GLOBAL__N_113MergeSettingsIZNS_10GetSettingERKNS_8SettingsERKNSt3__112basic_stringIcNS5_11char_traitsIcEENS5_9allocatorIcEEEESD_bbbE3$_0EEvS4_SD_SD_OT_
Line
Count
Source
42
5.44M
{
43
    // Merge in the forced settings
44
5.44M
    if (auto* value = FindKey(settings.forced_settings, name)) {
45
311k
        fn(SettingsSpan(*value), Source::FORCED);
46
311k
    }
47
    // Merge in the command-line options
48
5.44M
    if (auto* values = FindKey(settings.command_line_options, name)) {
49
388k
        fn(SettingsSpan(*values), Source::COMMAND_LINE);
50
388k
    }
51
    // Merge in the read-write settings
52
5.44M
    if (const SettingsValue* value = FindKey(settings.rw_settings, name)) {
53
0
        fn(SettingsSpan(*value), Source::RW_SETTINGS);
54
0
    }
55
    // Merge in the network-specific section of the config file
56
5.44M
    if (!section.empty()) {
57
4.74M
        if (auto* map = FindKey(settings.ro_config, section)) {
58
0
            if (auto* values = FindKey(*map, name)) {
59
0
                fn(SettingsSpan(*values), Source::CONFIG_FILE_NETWORK_SECTION);
60
0
            }
61
0
        }
62
4.74M
    }
63
    // Merge in the default section of the config file
64
5.44M
    if (auto* map = FindKey(settings.ro_config, "")) {
65
0
        if (auto* values = FindKey(*map, name)) {
66
0
            fn(SettingsSpan(*values), Source::CONFIG_FILE_DEFAULT_SECTION);
67
0
        }
68
0
    }
69
5.44M
}
settings.cpp:_ZN6common12_GLOBAL__N_113MergeSettingsIZNS_15GetSettingsListERKNS_8SettingsERKNSt3__112basic_stringIcNS5_11char_traitsIcEENS5_9allocatorIcEEEESD_bE3$_0EEvS4_SD_SD_OT_
Line
Count
Source
42
652k
{
43
    // Merge in the forced settings
44
652k
    if (auto* value = FindKey(settings.forced_settings, name)) {
45
0
        fn(SettingsSpan(*value), Source::FORCED);
46
0
    }
47
    // Merge in the command-line options
48
652k
    if (auto* values = FindKey(settings.command_line_options, name)) {
49
116k
        fn(SettingsSpan(*values), Source::COMMAND_LINE);
50
116k
    }
51
    // Merge in the read-write settings
52
652k
    if (const SettingsValue* value = FindKey(settings.rw_settings, name)) {
53
0
        fn(SettingsSpan(*value), Source::RW_SETTINGS);
54
0
    }
55
    // Merge in the network-specific section of the config file
56
652k
    if (!section.empty()) {
57
652k
        if (auto* map = FindKey(settings.ro_config, section)) {
58
0
            if (auto* values = FindKey(*map, name)) {
59
0
                fn(SettingsSpan(*values), Source::CONFIG_FILE_NETWORK_SECTION);
60
0
            }
61
0
        }
62
652k
    }
63
    // Merge in the default section of the config file
64
652k
    if (auto* map = FindKey(settings.ro_config, "")) {
65
0
        if (auto* values = FindKey(*map, name)) {
66
0
            fn(SettingsSpan(*values), Source::CONFIG_FILE_DEFAULT_SECTION);
67
0
        }
68
0
    }
69
652k
}
settings.cpp:_ZN6common12_GLOBAL__N_113MergeSettingsIZNS_28OnlyHasDefaultSectionSettingERKNS_8SettingsERKNSt3__112basic_stringIcNS5_11char_traitsIcEENS5_9allocatorIcEEEESD_E3$_0EEvS4_SD_SD_OT_
Line
Count
Source
42
311k
{
43
    // Merge in the forced settings
44
311k
    if (auto* value = FindKey(settings.forced_settings, name)) {
45
0
        fn(SettingsSpan(*value), Source::FORCED);
46
0
    }
47
    // Merge in the command-line options
48
311k
    if (auto* values = FindKey(settings.command_line_options, name)) {
49
0
        fn(SettingsSpan(*values), Source::COMMAND_LINE);
50
0
    }
51
    // Merge in the read-write settings
52
311k
    if (const SettingsValue* value = FindKey(settings.rw_settings, name)) {
53
0
        fn(SettingsSpan(*value), Source::RW_SETTINGS);
54
0
    }
55
    // Merge in the network-specific section of the config file
56
311k
    if (!section.empty()) {
57
311k
        if (auto* map = FindKey(settings.ro_config, section)) {
58
0
            if (auto* values = FindKey(*map, name)) {
59
0
                fn(SettingsSpan(*values), Source::CONFIG_FILE_NETWORK_SECTION);
60
0
            }
61
0
        }
62
311k
    }
63
    // Merge in the default section of the config file
64
311k
    if (auto* map = FindKey(settings.ro_config, "")) {
65
0
        if (auto* values = FindKey(*map, name)) {
66
0
            fn(SettingsSpan(*values), Source::CONFIG_FILE_DEFAULT_SECTION);
67
0
        }
68
0
    }
69
311k
}
70
} // namespace
71
72
bool ReadSettings(const fs::path& path, std::map<std::string, SettingsValue>& values, std::vector<std::string>& errors)
73
38.8k
{
74
38.8k
    values.clear();
75
38.8k
    errors.clear();
76
77
    // Ok for file to not exist
78
38.8k
    if (!fs::exists(path)) 
return true0
;
79
80
38.8k
    std::ifstream file;
81
38.8k
    file.open(path);
82
38.8k
    if (!file.is_open()) {
83
0
      errors.emplace_back(strprintf("%s. Please check permissions.", fs::PathToString(path)));
Line
Count
Source
1172
0
#define strprintf tfm::format
84
0
      return false;
85
0
    }
86
87
38.8k
    SettingsValue in;
88
38.8k
    if (!in.read(std::string{std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>()})) {
89
0
        errors.emplace_back(strprintf("Settings file %s does not contain valid JSON. This is probably caused by disk corruption or a crash, "
Line
Count
Source
1172
0
#define strprintf tfm::format
90
0
                                      "and can be fixed by removing the file, which will reset settings to default values.",
91
0
                                      fs::PathToString(path)));
92
0
        return false;
93
0
    }
94
95
38.8k
    if (file.fail()) {
96
0
        errors.emplace_back(strprintf("Failed reading settings file %s", fs::PathToString(path)));
Line
Count
Source
1172
0
#define strprintf tfm::format
97
0
        return false;
98
0
    }
99
38.8k
    file.close(); // Done with file descriptor. Release while copying data.
100
101
38.8k
    if (!in.isObject()) {
102
0
        errors.emplace_back(strprintf("Found non-object value %s in settings file %s", in.write(), fs::PathToString(path)));
Line
Count
Source
1172
0
#define strprintf tfm::format
103
0
        return false;
104
0
    }
105
106
38.8k
    const std::vector<std::string>& in_keys = in.getKeys();
107
38.8k
    const std::vector<SettingsValue>& in_values = in.getValues();
108
116k
    for (size_t i = 0; i < in_keys.size(); 
++i77.7k
) {
109
77.7k
        auto inserted = values.emplace(in_keys[i], in_values[i]);
110
77.7k
        if (!inserted.second) {
111
0
            errors.emplace_back(strprintf("Found duplicate key %s in settings file %s", in_keys[i], fs::PathToString(path)));
Line
Count
Source
1172
0
#define strprintf tfm::format
112
0
            values.clear();
113
0
            break;
114
0
        }
115
77.7k
    }
116
117
    // Remove auto-generated warning comment from the accessible settings.
118
38.8k
    values.erase(SETTINGS_WARN_MSG_KEY);
119
120
38.8k
    return errors.empty();
121
38.8k
}
122
123
bool WriteSettings(const fs::path& path,
124
    const std::map<std::string, SettingsValue>& values,
125
    std::vector<std::string>& errors)
126
0
{
127
0
    SettingsValue out(SettingsValue::VOBJ);
128
    // Add auto-generated warning comment
129
0
    out.pushKV(SETTINGS_WARN_MSG_KEY, strprintf("This file is automatically generated and updated by %s. Please do not edit this file while the node "
Line
Count
Source
1172
0
#define strprintf tfm::format
130
0
                                                "is running, as any changes might be ignored or overwritten.", CLIENT_NAME));
Line
Count
Source
98
0
#define CLIENT_NAME "Bitcoin Core"
131
    // Push settings values
132
0
    for (const auto& value : values) {
133
0
        out.pushKVEnd(value.first, value.second);
134
0
    }
135
0
    std::ofstream file;
136
0
    file.open(path);
137
0
    if (file.fail()) {
138
0
        errors.emplace_back(strprintf("Error: Unable to open settings file %s for writing", fs::PathToString(path)));
Line
Count
Source
1172
0
#define strprintf tfm::format
139
0
        return false;
140
0
    }
141
0
    file << out.write(/* prettyIndent= */ 4, /* indentLevel= */ 1) << std::endl;
142
0
    file.close();
143
0
    return true;
144
0
}
145
146
SettingsValue GetSetting(const Settings& settings,
147
    const std::string& section,
148
    const std::string& name,
149
    bool ignore_default_section_config,
150
    bool ignore_nonpersistent,
151
    bool get_chain_type)
152
5.44M
{
153
5.44M
    SettingsValue result;
154
5.44M
    bool done = false; // Done merging any more settings sources.
155
5.44M
    MergeSettings(settings, section, name, [&](SettingsSpan span, Source source) {
156
        // Weird behavior preserved for backwards compatibility: Apply negated
157
        // setting even if non-negated setting would be ignored. A negated
158
        // value in the default section is applied to network specific options,
159
        // even though normal non-negated values there would be ignored.
160
700k
        const bool never_ignore_negated_setting = span.last_negated();
161
162
        // Weird behavior preserved for backwards compatibility: Take first
163
        // assigned value instead of last. In general, later settings take
164
        // precedence over early settings, but for backwards compatibility in
165
        // the config file the precedence is reversed for all settings except
166
        // chain type settings.
167
700k
        const bool reverse_precedence =
168
700k
            (source == Source::CONFIG_FILE_NETWORK_SECTION || source == Source::CONFIG_FILE_DEFAULT_SECTION) &&
169
700k
            
!get_chain_type0
;
170
171
        // Weird behavior preserved for backwards compatibility: Negated
172
        // -regtest and -testnet arguments which you would expect to override
173
        // values set in the configuration file are currently accepted but
174
        // silently ignored. It would be better to apply these just like other
175
        // negated values, or at least warn they are ignored.
176
700k
        const bool skip_negated_command_line = get_chain_type;
177
178
700k
        if (done) 
return0
;
179
180
        // Ignore settings in default config section if requested.
181
700k
        if (ignore_default_section_config && 
source == Source::CONFIG_FILE_DEFAULT_SECTION0
&&
182
700k
            
!never_ignore_negated_setting0
) {
183
0
            return;
184
0
        }
185
186
        // Ignore nonpersistent settings if requested.
187
700k
        if (ignore_nonpersistent && 
(0
source == Source::COMMAND_LINE0
||
source == Source::FORCED0
))
return0
;
188
189
        // Skip negated command line settings.
190
700k
        if (skip_negated_command_line && 
span.last_negated()0
)
return0
;
191
192
700k
        if (!span.empty()) {
193
622k
            result = reverse_precedence ? 
span.begin()[0]0
: span.end()[-1];
194
622k
            done = true;
195
622k
        } else 
if (77.7k
span.last_negated()77.7k
) {
196
77.7k
            result = false;
197
77.7k
            done = true;
198
77.7k
        }
199
700k
    });
200
5.44M
    return result;
201
5.44M
}
202
203
std::vector<SettingsValue> GetSettingsList(const Settings& settings,
204
    const std::string& section,
205
    const std::string& name,
206
    bool ignore_default_section_config)
207
652k
{
208
652k
    std::vector<SettingsValue> result;
209
652k
    bool done = false; // Done merging any more settings sources.
210
652k
    bool prev_negated_empty = false;
211
652k
    MergeSettings(settings, section, name, [&](SettingsSpan span, Source source) {
212
        // Weird behavior preserved for backwards compatibility: Apply config
213
        // file settings even if negated on command line. Negating a setting on
214
        // command line will ignore earlier settings on the command line and
215
        // ignore settings in the config file, unless the negated command line
216
        // value is followed by non-negated value, in which case config file
217
        // settings will be brought back from the dead (but earlier command
218
        // line settings will still be ignored).
219
116k
        const bool add_zombie_config_values =
220
116k
            (source == Source::CONFIG_FILE_NETWORK_SECTION || source == Source::CONFIG_FILE_DEFAULT_SECTION) &&
221
116k
            
!prev_negated_empty0
;
222
223
        // Ignore settings in default config section if requested.
224
116k
        if (ignore_default_section_config && 
source == Source::CONFIG_FILE_DEFAULT_SECTION0
)
return0
;
225
226
        // Add new settings to the result if isn't already complete, or if the
227
        // values are zombies.
228
116k
        if (!done || 
add_zombie_config_values0
) {
229
116k
            for (const auto& value : span) {
230
116k
                if (value.isArray()) {
231
0
                    result.insert(result.end(), value.getValues().begin(), value.getValues().end());
232
116k
                } else {
233
116k
                    result.push_back(value);
234
116k
                }
235
116k
            }
236
116k
        }
237
238
        // If a setting was negated, or if a setting was forced, set
239
        // done to true to ignore any later lower priority settings.
240
116k
        done |= span.negated() > 0 || 
source == Source::FORCED77.7k
;
241
242
        // Update the negated and empty state used for the zombie values check.
243
116k
        prev_negated_empty |= span.last_negated() && 
result.empty()38.8k
;
244
116k
    });
245
652k
    return result;
246
652k
}
247
248
bool OnlyHasDefaultSectionSetting(const Settings& settings, const std::string& section, const std::string& name)
249
311k
{
250
311k
    bool has_default_section_setting = false;
251
311k
    bool has_other_setting = false;
252
311k
    MergeSettings(settings, section, name, [&](SettingsSpan span, Source source) {
253
0
        if (span.empty()) return;
254
0
        else if (source == Source::CONFIG_FILE_DEFAULT_SECTION) has_default_section_setting = true;
255
0
        else has_other_setting = true;
256
0
    });
257
    // If a value is set in the default section and not explicitly overwritten by the
258
    // user on the command line or in a different section, then we want to enable
259
    // warnings about the value being ignored.
260
311k
    return has_default_section_setting && 
!has_other_setting0
;
261
311k
}
262
263
505k
SettingsSpan::SettingsSpan(const std::vector<SettingsValue>& vec) noexcept : SettingsSpan(vec.data(), vec.size()) {}
Unexecuted instantiation: _ZN6common12SettingsSpanC2ERKNSt3__16vectorI8UniValueNS1_9allocatorIS3_EEEE
_ZN6common12SettingsSpanC1ERKNSt3__16vectorI8UniValueNS1_9allocatorIS3_EEEE
Line
Count
Source
263
505k
SettingsSpan::SettingsSpan(const std::vector<SettingsValue>& vec) noexcept : SettingsSpan(vec.data(), vec.size()) {}
264
116k
const SettingsValue* SettingsSpan::begin() const { return data + negated(); }
265
739k
const SettingsValue* SettingsSpan::end() const { return data + size; }
266
700k
bool SettingsSpan::empty() const { return size == 0 || last_negated(); }
267
1.59M
bool SettingsSpan::last_negated() const { return size > 0 && data[size - 1].isFalse(); }
268
size_t SettingsSpan::negated() const
269
233k
{
270
466k
    for (size_t i = size; i > 0; 
--i233k
) {
271
311k
        if (data[i - 1].isFalse()) 
return i77.7k
; // Return number of negated values (position of last false value)
272
311k
    }
273
155k
    return 0;
274
233k
}
275
276
} // namespace common