fix: make the JSONCPP_USE_SECURE_MEMORY build compile and pass (#1709)
Building with JSONCPP_USE_SECURE_MEMORY=1 fails in three separate ways,
each hidden behind the previous one. No CI job builds this configuration,
which is why they have accumulated.
1. allocator.h calls RtlSecureZeroMemory on the _WIN32 branch without
including <windows.h>, so MSVC fails with error C3861. Removing that
branch lets the portable volatile std::fill_n path handle Windows.
The same fill was zeroing n bytes rather than n * sizeof(T), so for
any T wider than a char, most of the allocation was never wiped.
2. jsontestrunner declares a std::string where the surrounding code uses
Json::String. The two are the same type only in default builds, so the
test runner does not compile under secure memory.
3. CZString's move-assignment released a key with
releasePrefixedStringValue, but CZString keys come from
duplicateStringValue and carry no length prefix. The destructor
already uses the matching releaseStringValue. Under the default
allocator this mismatch is survivable; under SecureAllocator the
suite segfaults.
Verified on MSVC 19.44, x64, C++17. Before: error C3861. With only the
first two fixed: builds, then jsoncpp_test SEGFAULT. With all three:
secure build compiles and 3/3 ctest suites pass. The default build is
unaffected and also 3/3.
Fixes #1399
Co-authored-by: Jordan Bayles <bayles.jordan@gmail.com>
diff --git a/include/json/allocator.h b/include/json/allocator.h
index 459c34c..fa79d97 100644
--- a/include/json/allocator.h
+++ b/include/json/allocator.h
@@ -43,10 +43,9 @@
// unlike memset.
#if defined(HAVE_MEMSET_S)
memset_s(p, n * sizeof(T), 0, n * sizeof(T));
-#elif defined(_WIN32)
- RtlSecureZeroMemory(p, n * sizeof(T));
#else
- std::fill_n(reinterpret_cast<volatile unsigned char*>(p), n, 0);
+ std::fill_n(reinterpret_cast<volatile unsigned char*>(p), n * sizeof(T),
+ static_cast<unsigned char>(0));
#endif
// free using "global operator delete"
diff --git a/src/jsontestrunner/main.cpp b/src/jsontestrunner/main.cpp
index ab6a800..e499276 100644
--- a/src/jsontestrunner/main.cpp
+++ b/src/jsontestrunner/main.cpp
@@ -328,7 +328,7 @@
return modern_return_code;
}
- const std::string filename =
+ const Json::String filename =
opts.path.substr(opts.path.find_last_of("\\/") + 1);
const bool should_run_legacy = (filename.rfind("legacy_", 0) == 0);
if (should_run_legacy) {
diff --git a/src/lib_json/json_value.cpp b/src/lib_json/json_value.cpp
index 5823bd1..6b14c2d 100644
--- a/src/lib_json/json_value.cpp
+++ b/src/lib_json/json_value.cpp
@@ -324,7 +324,9 @@
Value::CZString& Value::CZString::operator=(CZString&& other) noexcept {
if (cstr_ && storage_.policy_ == duplicate) {
- releasePrefixedStringValue(const_cast<char*>(cstr_));
+ // CZString keys come from duplicateStringValue (no length prefix), so
+ // release with the matching non-prefixed variant, as the destructor does.
+ releaseStringValue(const_cast<char*>(cstr_), storage_.length_ + 1U);
}
cstr_ = other.cstr_;
if (other.cstr_) {
diff --git a/src/test_lib_json/main.cpp b/src/test_lib_json/main.cpp
index 2e106bc..e09b87b 100644
--- a/src/test_lib_json/main.cpp
+++ b/src/test_lib_json/main.cpp
@@ -214,8 +214,8 @@
JSONTEST_FIXTURE_LOCAL(ValueTest, checkNormalizeFloatingPointStr) {
struct TestData {
- std::string in;
- std::string out;
+ Json::String in;
+ Json::String out;
} const testData[] = {
{"0.0", "0.0"},
{"0e0", "0e0"},
@@ -295,7 +295,7 @@
JSONTEST_ASSERT(foundId != nullptr);
JSONTEST_ASSERT_EQUAL(Json::Value(1234), *foundId);
- const std::string stringIdKey = "id";
+ const Json::String stringIdKey = "id";
const Json::Value* stringFoundId = object1_.find(stringIdKey);
JSONTEST_ASSERT(stringFoundId != nullptr);
JSONTEST_ASSERT_EQUAL(Json::Value(1234), *stringFoundId);
@@ -305,7 +305,7 @@
object1_.find(unknownIdKey, unknownIdKey + strlen(unknownIdKey));
JSONTEST_ASSERT_EQUAL(nullptr, foundUnknownId);
- const std::string stringUnknownIdKey = "unknown id";
+ const Json::String stringUnknownIdKey = "unknown id";
const Json::Value* stringFoundUnknownId = object1_.find(stringUnknownIdKey);
JSONTEST_ASSERT_EQUAL(nullptr, stringFoundUnknownId);
@@ -357,7 +357,7 @@
const Json::Value* stringFound = object2_.findString("string");
JSONTEST_ASSERT(stringFound != nullptr);
- JSONTEST_ASSERT_EQUAL(std::string{"string"}, *stringFound);
+ JSONTEST_ASSERT_EQUAL(Json::String{"string"}, *stringFound);
JSONTEST_ASSERT(object3_.findString("string") == nullptr);
const Json::Value* arrayFound = object2_.findArray("array");
@@ -2028,9 +2028,9 @@
JSONTEST_FIXTURE_LOCAL(ValueTest, WideString) {
// https://github.com/open-source-parsers/jsoncpp/issues/756
- const std::string uni =
+ const Json::String uni =
reinterpret_cast<const char*>(u8"\u5f0f\uff0c\u8fdb"); // "式,进"
- std::string styled;
+ Json::String styled;
{
Json::Value v;
v["abc"] = uni;
@@ -2039,7 +2039,7 @@
Json::Value root;
{
JSONCPP_STRING errs;
- std::istringstream iss(styled);
+ Json::IStringStream iss(styled);
bool ok = parseFromStream(Json::CharReaderBuilder(), iss, &root, &errs);
JSONTEST_ASSERT(ok);
if (!ok) {
@@ -2894,7 +2894,7 @@
JSONTEST_FIXTURE_LOCAL(StreamWriterTest, escapeControlCharacters) {
auto uEscape = [](unsigned ch) {
static const char h[] = "0123456789abcdef";
- std::string r = "\\u";
+ Json::String r = "\\u";
r += h[(ch >> (3 * 4)) & 0xf];
r += h[(ch >> (2 * 4)) & 0xf];
r += h[(ch >> (1 * 4)) & 0xf];
@@ -2931,8 +2931,8 @@
if (!emitUTF8 && i >= 0x80)
break; // The algorithm would try to parse UTF-8, so stop here.
- std::string raw({static_cast<char>(i)});
- std::string esc = raw;
+ Json::String raw({static_cast<char>(i)});
+ Json::String esc = raw;
if (i < 0x20)
esc = uEscape(i);
if (const char* shEsc = shortEscape(i))
@@ -2944,7 +2944,7 @@
Json::Value root;
root["test"] = raw;
JSONTEST_ASSERT_STRING_EQUAL(
- std::string("{\n\t\"test\" : \"").append(esc).append("\"\n}"),
+ Json::String("{\n\t\"test\" : \"").append(esc).append("\"\n}"),
Json::writeString(b, root))
<< ", emit=" << emitUTF8 << ", i=" << i << ", raw=\"" << raw << "\""
<< ", esc=\"" << esc << "\"";
@@ -3018,7 +3018,7 @@
template <typename Input>
void checkParse(Input&& input,
const std::vector<Json::Reader::StructuredError>& structured,
- const std::string& formatted) {
+ const Json::String& formatted) {
checkParse(input, structured);
JSONTEST_ASSERT_EQUAL(formatted, reader->getFormattedErrorMessages());
}
@@ -3793,7 +3793,7 @@
return [=](const Value& root) { JSONTEST_ASSERT_EQUAL(root, v); };
}
- static ValueCheck objGetAnd(std::string idx, ValueCheck f) {
+ static ValueCheck objGetAnd(Json::String idx, ValueCheck f) {
return [=](const Value& root) { f(root.get(idx, true)); };
}
@@ -4117,16 +4117,16 @@
j["k1"] = "a";
j["k2"] = "b";
- std::vector<std::string> keys;
- std::vector<std::string> values;
+ std::vector<Json::String> keys;
+ std::vector<Json::String> values;
for (const auto& member : j.members()) {
keys.push_back(member.name);
values.push_back(member.value.asString());
}
- JSONTEST_ASSERT((keys == std::vector<std::string>{"k1", "k2"}));
- JSONTEST_ASSERT((values == std::vector<std::string>{"a", "b"}));
+ JSONTEST_ASSERT((keys == std::vector<Json::String>{"k1", "k2"}));
+ JSONTEST_ASSERT((values == std::vector<Json::String>{"a", "b"}));
// Test modification through value reference
for (const auto& member : j.members()) {
@@ -4145,8 +4145,8 @@
values.push_back(member.value.asString());
}
- JSONTEST_ASSERT((keys == std::vector<std::string>{"k1", "k2"}));
- JSONTEST_ASSERT((values == std::vector<std::string>{"c", "c"}));
+ JSONTEST_ASSERT((keys == std::vector<Json::String>{"k1", "k2"}));
+ JSONTEST_ASSERT((values == std::vector<Json::String>{"c", "c"}));
#if __cplusplus >= 201703L
keys.clear();
@@ -4155,8 +4155,8 @@
keys.push_back(k);
values.push_back(v.asString());
}
- JSONTEST_ASSERT((keys == std::vector<std::string>{"k1", "k2"}));
- JSONTEST_ASSERT((values == std::vector<std::string>{"c", "c"}));
+ JSONTEST_ASSERT((keys == std::vector<Json::String>{"k1", "k2"}));
+ JSONTEST_ASSERT((values == std::vector<Json::String>{"c", "c"}));
#endif
}
@@ -4173,25 +4173,25 @@
Json::Value json;
json["k1"] = "a";
json["k2"] = "b";
- std::vector<std::string> values;
+ std::vector<Json::String> values;
for (auto it = json.end(); it != json.begin();) {
--it;
values.push_back(it->asString());
}
- JSONTEST_ASSERT((values == std::vector<std::string>{"b", "a"}));
+ JSONTEST_ASSERT((values == std::vector<Json::String>{"b", "a"}));
}
JSONTEST_FIXTURE_LOCAL(IteratorTest, reverseIterator) {
Json::Value json;
json["k1"] = "a";
json["k2"] = "b";
- std::vector<std::string> values;
+ std::vector<Json::String> values;
using Iter = decltype(json.begin());
auto re = std::reverse_iterator<Iter>(json.begin());
for (auto it = std::reverse_iterator<Iter>(json.end()); it != re; ++it) {
values.push_back(it->asString());
}
- JSONTEST_ASSERT((values == std::vector<std::string>{"b", "a"}));
+ JSONTEST_ASSERT((values == std::vector<Json::String>{"b", "a"}));
}
JSONTEST_FIXTURE_LOCAL(IteratorTest, distance) {