Skip to content

Commit d4742c2

Browse files
authored
Fix MSAN issue in #1626 (#1654)
* Fix MSAN issue in #1626 This patch fixes an MSAN issue by changing CZString initialization. * add test * expand tests * fix build * Export CZString with JSON_API to fix Windows DLL linker errors
1 parent 94aee4f commit d4742c2

3 files changed

Lines changed: 95 additions & 16 deletions

File tree

include/json/value.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@
5050
#include <string>
5151
#include <vector>
5252

53+
// Forward declaration for testing.
54+
struct ValueTest;
55+
5356
#ifdef JSONCPP_HAS_STRING_VIEW
5457
#include <string_view>
5558
#endif
@@ -201,6 +204,7 @@ class JSON_API StaticString {
201204
*/
202205
class JSON_API Value {
203206
friend class ValueIteratorBase;
207+
friend struct ::ValueTest;
204208

205209
public:
206210
using Members = std::vector<String>;
@@ -266,7 +270,7 @@ class JSON_API Value {
266270
private:
267271
#endif
268272
#ifndef JSONCPP_DOC_EXCLUDE_IMPLEMENTATION
269-
class CZString {
273+
class JSON_API CZString {
270274
public:
271275
enum DuplicationPolicy { noDuplication = 0, duplicate, duplicateOnCopy };
272276
CZString(ArrayIndex index);

src/lib_json/json_value.cpp

Lines changed: 32 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -253,20 +253,29 @@ Value::CZString::CZString(const CZString& other) {
253253
cstr_ = (other.storage_.policy_ != noDuplication && other.cstr_ != nullptr
254254
? duplicateStringValue(other.cstr_, other.storage_.length_)
255255
: other.cstr_);
256-
storage_.policy_ =
257-
static_cast<unsigned>(
258-
other.cstr_
259-
? (static_cast<DuplicationPolicy>(other.storage_.policy_) ==
260-
noDuplication
261-
? noDuplication
262-
: duplicate)
263-
: static_cast<DuplicationPolicy>(other.storage_.policy_)) &
264-
3U;
265-
storage_.length_ = other.storage_.length_;
266-
}
267-
268-
Value::CZString::CZString(CZString&& other) noexcept
269-
: cstr_(other.cstr_), index_(other.index_) {
256+
if (other.cstr_) {
257+
storage_.policy_ =
258+
static_cast<unsigned>(
259+
other.cstr_
260+
? (static_cast<DuplicationPolicy>(other.storage_.policy_) ==
261+
noDuplication
262+
? noDuplication
263+
: duplicate)
264+
: static_cast<DuplicationPolicy>(other.storage_.policy_)) &
265+
3U;
266+
storage_.length_ = other.storage_.length_;
267+
} else {
268+
index_ = other.index_;
269+
}
270+
}
271+
272+
Value::CZString::CZString(CZString&& other) noexcept : cstr_(other.cstr_) {
273+
if (other.cstr_) {
274+
storage_.policy_ = other.storage_.policy_;
275+
storage_.length_ = other.storage_.length_;
276+
} else {
277+
index_ = other.index_;
278+
}
270279
other.cstr_ = nullptr;
271280
}
272281

@@ -292,8 +301,16 @@ Value::CZString& Value::CZString::operator=(const CZString& other) {
292301
}
293302

294303
Value::CZString& Value::CZString::operator=(CZString&& other) noexcept {
304+
if (cstr_ && storage_.policy_ == duplicate) {
305+
releasePrefixedStringValue(const_cast<char*>(cstr_));
306+
}
295307
cstr_ = other.cstr_;
296-
index_ = other.index_;
308+
if (other.cstr_) {
309+
storage_.policy_ = other.storage_.policy_;
310+
storage_.length_ = other.storage_.length_;
311+
} else {
312+
index_ = other.index_;
313+
}
297314
other.cstr_ = nullptr;
298315
return *this;
299316
}

src/test_lib_json/main.cpp

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,8 @@ struct ValueTest : JsonTest::TestCase {
150150
/// Normalize the representation of floating-point number by stripped leading
151151
/// 0 in exponent.
152152
static Json::String normalizeFloatingPointStr(const Json::String& s);
153+
154+
void runCZStringTests();
153155
};
154156

155157
Json::String ValueTest::normalizeFloatingPointStr(const Json::String& s) {
@@ -167,6 +169,44 @@ Json::String ValueTest::normalizeFloatingPointStr(const Json::String& s) {
167169
return normalized + exponent;
168170
}
169171

172+
void ValueTest::runCZStringTests() {
173+
// 1. Copy Constructor (Index)
174+
Json::Value::CZString idx1(123);
175+
Json::Value::CZString idx2(idx1);
176+
JSONTEST_ASSERT_EQUAL(idx2.index(), 123);
177+
178+
// 2. Move Constructor (Index)
179+
Json::Value::CZString idx3(std::move(idx1));
180+
JSONTEST_ASSERT_EQUAL(idx3.index(), 123);
181+
182+
// 3. Move Assignment (Index)
183+
Json::Value::CZString idx4(456);
184+
idx4 = std::move(idx3);
185+
JSONTEST_ASSERT_EQUAL(idx4.index(), 123);
186+
187+
// 4. Copy Constructor (String)
188+
Json::Value::CZString str1("param", 5,
189+
Json::Value::CZString::duplicateOnCopy);
190+
Json::Value::CZString str2((str1)); // copy makes it duplicate (owning)
191+
JSONTEST_ASSERT_STRING_EQUAL(str2.data(), "param");
192+
193+
// 5. Move Constructor (String)
194+
// Move from Owning string (str2)
195+
Json::Value::CZString str3(std::move(str2));
196+
JSONTEST_ASSERT_STRING_EQUAL(str3.data(), "param");
197+
198+
// 6. Move Assignment (String)
199+
Json::Value::CZString str4("other", 5,
200+
Json::Value::CZString::duplicateOnCopy);
201+
Json::Value::CZString str5((str4)); // owning "other"
202+
// Move-assign owning "param" (str3) into owning "other" (str5)
203+
// This verifies we don't leak "other" (if fixed) and correctly take "param"
204+
str5 = std::move(str3);
205+
JSONTEST_ASSERT_STRING_EQUAL(str5.data(), "param");
206+
}
207+
208+
JSONTEST_FIXTURE_LOCAL(ValueTest, CZStringCoverage) { runCZStringTests(); }
209+
170210
JSONTEST_FIXTURE_LOCAL(ValueTest, checkNormalizeFloatingPointStr) {
171211
struct TestData {
172212
std::string in;
@@ -449,6 +489,24 @@ JSONTEST_FIXTURE_LOCAL(ValueTest, resizeArray) {
449489
}
450490
}
451491

492+
JSONTEST_FIXTURE_LOCAL(ValueTest, copyMoveArray) {
493+
Json::Value array;
494+
array.append("item1");
495+
array.append("item2");
496+
497+
// Test Copy Constructor (covers CZString(const CZString&) with index)
498+
Json::Value copy(array);
499+
JSONTEST_ASSERT_EQUAL(copy.size(), 2);
500+
JSONTEST_ASSERT_EQUAL(Json::Value("item1"), copy[0]);
501+
JSONTEST_ASSERT_EQUAL(Json::Value("item2"), copy[1]);
502+
503+
// Test Move Constructor (covers CZString(CZString&&) with index)
504+
Json::Value moved(std::move(copy));
505+
JSONTEST_ASSERT_EQUAL(moved.size(), 2);
506+
JSONTEST_ASSERT_EQUAL(Json::Value("item1"), moved[0]);
507+
JSONTEST_ASSERT_EQUAL(Json::Value("item2"), moved[1]);
508+
}
509+
452510
JSONTEST_FIXTURE_LOCAL(ValueTest, resizePopulatesAllMissingElements) {
453511
Json::ArrayIndex n = 10;
454512
Json::Value v;

0 commit comments

Comments
 (0)