diff --git a/CMakeLists.txt b/CMakeLists.txt index 4eb4499a2..bc8575fd1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,7 +12,7 @@ # CMake versions greater than the JSONCPP_NEWEST_VALIDATED_POLICIES_VERSION policies will # continue to generate policy warnings "CMake Warning (dev)...Policy CMP0XXX is not set:" # -set(JSONCPP_OLDEST_VALIDATED_POLICIES_VERSION "3.8.0") +set(JSONCPP_OLDEST_VALIDATED_POLICIES_VERSION "3.10.0") set(JSONCPP_NEWEST_VALIDATED_POLICIES_VERSION "3.13.2") cmake_minimum_required(VERSION ${JSONCPP_OLDEST_VALIDATED_POLICIES_VERSION}) if("${CMAKE_VERSION}" VERSION_LESS "${JSONCPP_NEWEST_VALIDATED_POLICIES_VERSION}") @@ -40,12 +40,6 @@ foreach(pold "") # Currently Empty endif() endforeach() -# Build the library with C++11 standard support, independent from other including -# software which may use a different CXX_STANDARD or CMAKE_CXX_STANDARD. -set(CMAKE_CXX_STANDARD 11) -set(CMAKE_CXX_EXTENSIONS OFF) -set(CMAKE_CXX_STANDARD_REQUIRED ON) - # Ensure that CMAKE_BUILD_TYPE has a value specified for single configuration generators. if(NOT DEFINED CMAKE_BUILD_TYPE AND NOT DEFINED CMAKE_CONFIGURATION_TYPES) set(CMAKE_BUILD_TYPE Release CACHE STRING diff --git a/include/json/reader.h b/include/json/reader.h index d745378fc..7aa227188 100644 --- a/include/json/reader.h +++ b/include/json/reader.h @@ -81,7 +81,10 @@ class JSON_API Reader { * document. * * \param beginDoc Pointer on the beginning of the UTF-8 encoded - * string of the document to read. + * string of the document to read. The pointed-to + * buffer must outlive this Reader if error + * methods (e.g. getFormattedErrorMessages()) are + * called after parse() returns. * \param endDoc Pointer on the end of the UTF-8 encoded string * of the document to read. Must be >= beginDoc. * \param[out] root Contains the root value of the document if it diff --git a/include/json/value.h b/include/json/value.h index 5f6544329..f32f45609 100644 --- a/include/json/value.h +++ b/include/json/value.h @@ -39,9 +39,11 @@ #endif #endif +#ifndef JSONCPP_HAS_STRING_VIEW #if __cplusplus >= 201703L #define JSONCPP_HAS_STRING_VIEW 1 #endif +#endif #include #include @@ -50,6 +52,9 @@ #include #include +// Forward declaration for testing. +struct ValueTest; + #ifdef JSONCPP_HAS_STRING_VIEW #include #endif @@ -201,6 +206,7 @@ class JSON_API StaticString { */ class JSON_API Value { friend class ValueIteratorBase; + friend struct ::ValueTest; public: using Members = std::vector; @@ -266,7 +272,7 @@ class JSON_API Value { private: #endif #ifndef JSONCPP_DOC_EXCLUDE_IMPLEMENTATION - class CZString { + class JSON_API CZString { public: enum DuplicationPolicy { noDuplication = 0, duplicate, duplicateOnCopy }; CZString(ArrayIndex index); diff --git a/src/lib_json/CMakeLists.txt b/src/lib_json/CMakeLists.txt index 86722ac8d..03e933552 100644 --- a/src/lib_json/CMakeLists.txt +++ b/src/lib_json/CMakeLists.txt @@ -7,6 +7,7 @@ include(CheckIncludeFileCXX) include(CheckTypeSize) include(CheckStructHasMember) include(CheckCXXSymbolExists) +include(CheckCXXSourceCompiles) check_include_file_cxx(clocale HAVE_CLOCALE) check_cxx_symbol_exists(localeconv clocale HAVE_LOCALECONV) @@ -25,6 +26,11 @@ if(NOT (HAVE_CLOCALE AND HAVE_LCONV_SIZE AND HAVE_DECIMAL_POINT AND HAVE_LOCALEC endif() endif() +check_cxx_source_compiles( + "#include + int main() { std::string_view sv; return 0; }" + JSONCPP_HAS_STRING_VIEW) + set(JSONCPP_INCLUDE_DIR ../../include) set(PUBLIC_HEADERS @@ -107,12 +113,6 @@ list(APPEND REQUIRED_FEATURES if(BUILD_SHARED_LIBS) - if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.12.0) - add_compile_definitions(JSON_DLL_BUILD) - else() - add_definitions(-DJSON_DLL_BUILD) - endif() - set(SHARED_LIB ${PROJECT_NAME}_lib) add_library(${SHARED_LIB} SHARED ${PUBLIC_HEADERS} ${JSONCPP_SOURCES}) set_target_properties(${SHARED_LIB} PROPERTIES @@ -122,6 +122,8 @@ if(BUILD_SHARED_LIBS) POSITION_INDEPENDENT_CODE ${BUILD_SHARED_LIBS} ) + target_compile_definitions(${SHARED_LIB} PRIVATE JSON_DLL_BUILD) + # Set library's runtime search path on OSX if(APPLE) set_target_properties(${SHARED_LIB} PROPERTIES INSTALL_RPATH "@loader_path/.") @@ -129,6 +131,10 @@ if(BUILD_SHARED_LIBS) target_compile_features(${SHARED_LIB} PUBLIC ${REQUIRED_FEATURES}) + if(JSONCPP_HAS_STRING_VIEW) + target_compile_definitions(${SHARED_LIB} PUBLIC JSONCPP_HAS_STRING_VIEW=1) + endif() + target_include_directories(${SHARED_LIB} PUBLIC $ $ @@ -162,6 +168,10 @@ if(BUILD_STATIC_LIBS) target_compile_features(${STATIC_LIB} PUBLIC ${REQUIRED_FEATURES}) + if(JSONCPP_HAS_STRING_VIEW) + target_compile_definitions(${STATIC_LIB} PUBLIC JSONCPP_HAS_STRING_VIEW=1) + endif() + target_include_directories(${STATIC_LIB} PUBLIC $ $ @@ -188,6 +198,10 @@ if(BUILD_OBJECT_LIBS) target_compile_features(${OBJECT_LIB} PUBLIC ${REQUIRED_FEATURES}) + if(JSONCPP_HAS_STRING_VIEW) + target_compile_definitions(${OBJECT_LIB} PUBLIC JSONCPP_HAS_STRING_VIEW=1) + endif() + target_include_directories(${OBJECT_LIB} PUBLIC $ $ diff --git a/src/lib_json/json_reader.cpp b/src/lib_json/json_reader.cpp index 0697132b0..83743f73b 100644 --- a/src/lib_json/json_reader.cpp +++ b/src/lib_json/json_reader.cpp @@ -88,15 +88,10 @@ bool Reader::parse(const std::string& document, Value& root, } bool Reader::parse(std::istream& is, Value& root, bool collectComments) { - // std::istream_iterator begin(is); - // std::istream_iterator end; - // Those would allow streamed input from a file, if parse() were a - // template function. - - // Since String is reference-counted, this at least does not - // create an extra copy. - String doc(std::istreambuf_iterator(is), {}); - return parse(doc.data(), doc.data() + doc.size(), root, collectComments); + document_.assign(std::istreambuf_iterator(is), + std::istreambuf_iterator()); + return parse(document_.data(), document_.data() + document_.size(), root, + collectComments); } bool Reader::parse(const char* beginDoc, const char* endDoc, Value& root, @@ -583,6 +578,7 @@ bool Reader::decodeDouble(Token& token) { bool Reader::decodeDouble(Token& token, Value& decoded) { double value = 0; IStringStream is(String(token.start_, token.end_)); + is.imbue(std::locale::classic()); if (!(is >> value)) { if (value == std::numeric_limits::max()) value = std::numeric_limits::infinity(); @@ -654,6 +650,8 @@ bool Reader::decodeString(Token& token, String& decoded) { return addError("Bad escape sequence in string", token, current); } } else { + if (static_cast(c) < 0x20) + return addError("Control character in string", token, current - 1); decoded += c; } } @@ -1617,6 +1615,7 @@ bool OurReader::decodeDouble(Token& token) { bool OurReader::decodeDouble(Token& token, Value& decoded) { double value = 0; IStringStream is(String(token.start_, token.end_)); + is.imbue(std::locale::classic()); if (!(is >> value)) { if (value == std::numeric_limits::max()) value = std::numeric_limits::infinity(); @@ -1688,6 +1687,8 @@ bool OurReader::decodeString(Token& token, String& decoded) { return addError("Bad escape sequence in string", token, current); } } else { + if (static_cast(c) < 0x20) + return addError("Control character in string", token, current - 1); decoded += c; } } diff --git a/src/lib_json/json_value.cpp b/src/lib_json/json_value.cpp index a875d28b2..74f77896f 100644 --- a/src/lib_json/json_value.cpp +++ b/src/lib_json/json_value.cpp @@ -253,20 +253,29 @@ Value::CZString::CZString(const CZString& other) { cstr_ = (other.storage_.policy_ != noDuplication && other.cstr_ != nullptr ? duplicateStringValue(other.cstr_, other.storage_.length_) : other.cstr_); - storage_.policy_ = - static_cast( - other.cstr_ - ? (static_cast(other.storage_.policy_) == - noDuplication - ? noDuplication - : duplicate) - : static_cast(other.storage_.policy_)) & - 3U; - storage_.length_ = other.storage_.length_; -} - -Value::CZString::CZString(CZString&& other) noexcept - : cstr_(other.cstr_), index_(other.index_) { + if (other.cstr_) { + storage_.policy_ = + static_cast( + other.cstr_ + ? (static_cast(other.storage_.policy_) == + noDuplication + ? noDuplication + : duplicate) + : static_cast(other.storage_.policy_)) & + 3U; + storage_.length_ = other.storage_.length_; + } else { + index_ = other.index_; + } +} + +Value::CZString::CZString(CZString&& other) noexcept : cstr_(other.cstr_) { + if (other.cstr_) { + storage_.policy_ = other.storage_.policy_; + storage_.length_ = other.storage_.length_; + } else { + index_ = other.index_; + } other.cstr_ = nullptr; } @@ -292,8 +301,16 @@ Value::CZString& Value::CZString::operator=(const CZString& other) { } Value::CZString& Value::CZString::operator=(CZString&& other) noexcept { + if (cstr_ && storage_.policy_ == duplicate) { + releasePrefixedStringValue(const_cast(cstr_)); + } cstr_ = other.cstr_; - index_ = other.index_; + if (other.cstr_) { + storage_.policy_ = other.storage_.policy_; + storage_.length_ = other.storage_.length_; + } else { + index_ = other.index_; + } other.cstr_ = nullptr; return *this; } diff --git a/src/test_lib_json/main.cpp b/src/test_lib_json/main.cpp index f19ca2fb4..d6938c1d0 100644 --- a/src/test_lib_json/main.cpp +++ b/src/test_lib_json/main.cpp @@ -150,6 +150,8 @@ struct ValueTest : JsonTest::TestCase { /// Normalize the representation of floating-point number by stripped leading /// 0 in exponent. static Json::String normalizeFloatingPointStr(const Json::String& s); + + void runCZStringTests(); }; Json::String ValueTest::normalizeFloatingPointStr(const Json::String& s) { @@ -167,6 +169,44 @@ Json::String ValueTest::normalizeFloatingPointStr(const Json::String& s) { return normalized + exponent; } +void ValueTest::runCZStringTests() { + // 1. Copy Constructor (Index) + Json::Value::CZString idx1(123); + Json::Value::CZString idx2(idx1); + JSONTEST_ASSERT_EQUAL(idx2.index(), 123); + + // 2. Move Constructor (Index) + Json::Value::CZString idx3(std::move(idx1)); + JSONTEST_ASSERT_EQUAL(idx3.index(), 123); + + // 3. Move Assignment (Index) + Json::Value::CZString idx4(456); + idx4 = std::move(idx3); + JSONTEST_ASSERT_EQUAL(idx4.index(), 123); + + // 4. Copy Constructor (String) + Json::Value::CZString str1("param", 5, + Json::Value::CZString::duplicateOnCopy); + Json::Value::CZString str2((str1)); // copy makes it duplicate (owning) + JSONTEST_ASSERT_STRING_EQUAL(str2.data(), "param"); + + // 5. Move Constructor (String) + // Move from Owning string (str2) + Json::Value::CZString str3(std::move(str2)); + JSONTEST_ASSERT_STRING_EQUAL(str3.data(), "param"); + + // 6. Move Assignment (String) + Json::Value::CZString str4("other", 5, + Json::Value::CZString::duplicateOnCopy); + Json::Value::CZString str5((str4)); // owning "other" + // Move-assign owning "param" (str3) into owning "other" (str5) + // This verifies we don't leak "other" (if fixed) and correctly take "param" + str5 = std::move(str3); + JSONTEST_ASSERT_STRING_EQUAL(str5.data(), "param"); +} + +JSONTEST_FIXTURE_LOCAL(ValueTest, CZStringCoverage) { runCZStringTests(); } + JSONTEST_FIXTURE_LOCAL(ValueTest, checkNormalizeFloatingPointStr) { struct TestData { std::string in; @@ -449,6 +489,24 @@ JSONTEST_FIXTURE_LOCAL(ValueTest, resizeArray) { } } +JSONTEST_FIXTURE_LOCAL(ValueTest, copyMoveArray) { + Json::Value array; + array.append("item1"); + array.append("item2"); + + // Test Copy Constructor (covers CZString(const CZString&) with index) + Json::Value copy(array); + JSONTEST_ASSERT_EQUAL(copy.size(), 2); + JSONTEST_ASSERT_EQUAL(Json::Value("item1"), copy[0]); + JSONTEST_ASSERT_EQUAL(Json::Value("item2"), copy[1]); + + // Test Move Constructor (covers CZString(CZString&&) with index) + Json::Value moved(std::move(copy)); + JSONTEST_ASSERT_EQUAL(moved.size(), 2); + JSONTEST_ASSERT_EQUAL(Json::Value("item1"), moved[0]); + JSONTEST_ASSERT_EQUAL(Json::Value("item2"), moved[1]); +} + JSONTEST_FIXTURE_LOCAL(ValueTest, resizePopulatesAllMissingElements) { Json::ArrayIndex n = 10; Json::Value v; diff --git a/test/data/fail_test_control_char_01.json b/test/data/fail_test_control_char_01.json new file mode 100644 index 000000000..1f6835573 --- /dev/null +++ b/test/data/fail_test_control_char_01.json @@ -0,0 +1 @@ +"" \ No newline at end of file