Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ option(JSONCPP_STATIC_WINDOWS_RUNTIME "Use static (MT/MTd) Windows runtime" OFF)
option(BUILD_SHARED_LIBS "Build jsoncpp_lib as a shared library." ON)
option(BUILD_STATIC_LIBS "Build jsoncpp_lib as a static library." ON)
option(BUILD_OBJECT_LIBS "Build jsoncpp_lib as a object library." ON)
option(JSONCPP_WITH_FUZZING "Build the jsoncpp_fuzzer fuzz target (needs a fuzzing engine)" OFF)

# Adhere to GNU filesystem layout conventions
include(GNUInstallDirs)
Expand Down
2 changes: 2 additions & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
add_subdirectory(lib_json)
if(JSONCPP_WITH_TESTS)
add_subdirectory(jsontestrunner)
endif()
if(JSONCPP_WITH_TESTS OR JSONCPP_WITH_FUZZING)
add_subdirectory(test_lib_json)
endif()
59 changes: 59 additions & 0 deletions src/test_lib_json/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# vim: et ts=4 sts=4 sw=4 tw=0

if(JSONCPP_WITH_TESTS)

add_executable(jsoncpp_test
jsontest.cpp
jsontest.h
Expand Down Expand Up @@ -37,3 +39,60 @@ if(JSONCPP_WITH_POST_BUILD_UNITTEST)
COMMAND ${CMAKE_CROSSCOMPILING_EMULATOR} $<TARGET_FILE:jsoncpp_test>
)
endif()

endif() # JSONCPP_WITH_TESTS

# Standalone fuzz target (used by OSS-Fuzz). The JSONCPP_WITH_FUZZING option
# is declared in the top-level CMakeLists.txt, so the fuzz target can also be
# built with -DJSONCPP_WITH_TESTS=OFF.
#
# Requires a fuzzing engine (JSONCPP_WITH_FUZZING=ON fails at configure time
# without one, since the target has no main()): compile everything with the
# engine's coverage instrumentation only (e.g.
# -DCMAKE_CXX_FLAGS="-fsanitize=fuzzer-no-link") and link the engine itself
# into the fuzz target (e.g. -DJSONCPP_FUZZ_ENGINE="-fsanitize=fuzzer"), so
# that the engine's main() is not pulled into compiler checks and other
# executables.
#
# The libprotobuf-mutator based fuzzer (jsoncpp_fuzz_proto.cc, json.proto,
# json_proto_converter.*) is built by OSS-Fuzz separately with LPM; the
Comment thread
arthurscchan marked this conversation as resolved.
Resolved
# sources are kept here so they live with the other jsoncpp fuzzers.
set(JSONCPP_FUZZ_ENGINE "" CACHE STRING
"Fuzzing engine link flags for jsoncpp_fuzzer, e.g. -fsanitize=fuzzer")
if(JSONCPP_WITH_FUZZING)
Comment thread
arthurscchan marked this conversation as resolved.
Resolved
# jsoncpp_fuzzer has no main(): the fuzzing engine provides it, so its
# link flags are required and there is nothing buildable without them.
if(NOT JSONCPP_FUZZ_ENGINE)
message(FATAL_ERROR
"JSONCPP_WITH_FUZZING=ON requires JSONCPP_FUZZ_ENGINE linking "
"flags, e.g. -DJSONCPP_FUZZ_ENGINE=\"-fsanitize=fuzzer\"")
endif()
add_executable(jsoncpp_fuzzer
fuzz.cpp
fuzz.h
)
if(BUILD_SHARED_LIBS)
target_link_libraries(jsoncpp_fuzzer jsoncpp_lib)
elseif(BUILD_STATIC_LIBS)
target_link_libraries(jsoncpp_fuzzer jsoncpp_static)
Comment thread
greptile-apps[bot] marked this conversation as resolved.
Resolved
else()
# Linking to an object library via target_link_libraries needs
# CMake 3.12 or newer.
if(CMAKE_VERSION VERSION_LESS 3.12.0)
message(FATAL_ERROR
"JSONCPP_WITH_FUZZING with object libraries only requires "
"CMake 3.12 or newer")
endif()
target_link_libraries(jsoncpp_fuzzer jsoncpp_object)
endif()
set_target_properties(jsoncpp_fuzzer PROPERTIES
LINK_FLAGS "${JSONCPP_FUZZ_ENGINE}"
)
# Ship the dictionary next to the fuzzer binary.
add_custom_command(TARGET jsoncpp_fuzzer
POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different
${CMAKE_CURRENT_SOURCE_DIR}/fuzz.dict
$<TARGET_FILE_DIR:jsoncpp_fuzzer>/jsoncpp_fuzzer.dict
)
endif()
94 changes: 94 additions & 0 deletions src/test_lib_json/json.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// Copyright 2026 Google LLC and The JsonCpp Authors
// Distributed under MIT license, or public domain if desired and
// recognized in your jurisdiction.
// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE

// The proto definition for JSON format has been written based on
// http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf

syntax = "proto2";

package json_proto;

message JsonParseAPI {
required int32 settings = 1;
required JsonObject object_value = 2;
}

message JsonMember {
required string name = 1;
required JsonValue value = 2;
}

message JsonObject {
Comment thread
arthurscchan marked this conversation as resolved.
Resolved
// An object holds any number of members (including none), so that mutations
// can generate multi-member objects and duplicate keys (which exercise the
// rejectDupKeys parser setting).
repeated JsonMember member = 1;
}

message JsonValue {
oneof value {
// Json value types:

// null: null, will be used when 'oneof' contains nothing

// object: another json object of any type
JsonObject object_value = 1;

// array: an array of values
ArrayValue array_value = 2;

// number: can be an integer, a float, an exponent
NumberValue number_value = 3;

// string: unicode string
StringValue string_value = 4;

// boolean: true or talse
BooleanValue boolean_value = 5;
}
}

message ArrayValue {
repeated JsonValue value = 1;
}

message NumberInteger {
required int64 value = 1;
}

message NumberFloat {
required double value = 1;
}

message NumberExponent {
required int32 base = 1;
required int32 exponent = 2;
required bool use_uppercase = 3;
}

message NumberExponentFrac {
required float base = 1;
required int32 exponent = 2;
required bool use_uppercase = 3;
}

message NumberValue {
required NumberInteger integer_value = 1;

// integer_value is used when oneof field below has nothing.
oneof value {
NumberFloat float_value = 2;
NumberExponent exponent_value = 3;
NumberExponentFrac exponent_frac_value = 4;
}
}

message StringValue {
required string value = 1;
}

message BooleanValue {
required bool value = 1;
}
175 changes: 175 additions & 0 deletions src/test_lib_json/json_proto_converter.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
// Copyright 2026 Google LLC and The JsonCpp Authors
// Distributed under MIT license, or public domain if desired and
// recognized in your jurisdiction.
// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE

#include "json_proto_converter.h"

#include <cmath>
#include <iomanip>
#include <limits>
#include <locale>

namespace json_proto {

// Writes str as a quoted, JSON-escaped string, so that generated quotes,
// backslashes and control characters cannot break the surrounding structure.
void JsonProtoConverter::AppendEscapedString(const std::string& str) {
static const char kHexDigits[] = "0123456789abcdef";
data_ << '"';
for (char c : str) {
switch (c) {
case '"':
data_ << "\\\"";
break;
case '\\':
data_ << "\\\\";
break;
case '\b':
data_ << "\\b";
break;
case '\f':
data_ << "\\f";
break;
case '\n':
data_ << "\\n";
break;
case '\r':
data_ << "\\r";
break;
case '\t':
data_ << "\\t";
break;
default:
if (static_cast<unsigned char>(c) < 0x20) {
data_ << "\\u00" << kHexDigits[(c >> 4) & 0xF]
<< kHexDigits[c & 0xF];
} else {
data_ << c;
}
break;
}
}
data_ << '"';
}

void JsonProtoConverter::AppendArray(const ArrayValue& array_value) {
data_ << '[';
bool need_comma = false;
for (const auto& value : array_value.value()) {
// Trailing comma inside of an array makes JSON invalid, avoid adding that.
if (need_comma)
data_ << ',';
else
need_comma = true;

AppendValue(value);
}
data_ << ']';
}

// Writes value as a decimal number without an exponent: scientific notation
// would add a stray exponent to the generated number (e.g. "1e+20e3"), while
// the stream's default formatting would truncate small values
// ("0.0001004" -> "0.000100"), collapsing distinct mutations. Fixed notation
// with 50 digits after the decimal point round-trips every finite float
// (subnormal floats go down to ~1e-45). Non-finite values have no decimal
// form; write 0 instead ("inf"/"nan" text would not be a valid JSON token) so
// the rest of the generated structure remains parseable.
void JsonProtoConverter::AppendPlainFloat(float value) {
if (!std::isfinite(value)) {
data_ << '0';
return;
}
// Format in a scratch stream so the fixed/precision state does not leak
// into data_, and pin the classic ("C") locale so the decimal point is '.'.
std::ostringstream out;
out.imbue(std::locale::classic());
out << std::fixed << std::setprecision(50) << value;
std::string digits = out.str();
// Trim redundant trailing zeros ("0.0001004000..." -> "0.0001004"), keeping
// one digit after the decimal point so the token stays a float ("1.0").
const std::size_t last_significant = digits.find_last_not_of('0');
if (last_significant != std::string::npos) {
digits.erase(last_significant + (digits[last_significant] == '.' ? 2 : 1));
}
data_ << digits;
}

void JsonProtoConverter::AppendNumber(const NumberValue& number_value) {
if (number_value.has_float_value()) {
const double value = number_value.float_value().value();
// Keep "inf"/"nan" out of the output; they are not valid JSON numbers.
// Print with max_digits10 significant digits (the stream default of 6
// would collapse distinct generated doubles onto the same text). The
// result may carry an exponent, which is fine: no exponent gets appended
// to this value afterwards.
if (!std::isfinite(value)) {
data_ << '0';
} else {
std::ostringstream out;
out.imbue(std::locale::classic());
out << std::setprecision(std::numeric_limits<double>::max_digits10)
<< value;
data_ << out.str();
}
} else if (number_value.has_exponent_value()) {
auto value = number_value.exponent_value();
data_ << value.base();
data_ << (value.use_uppercase() ? 'E' : 'e');
data_ << value.exponent();
} else if (number_value.has_exponent_frac_value()) {
auto value = number_value.exponent_frac_value();
Comment thread
arthurscchan marked this conversation as resolved.
Resolved
AppendPlainFloat(value.base());
data_ << (value.use_uppercase() ? 'E' : 'e');
data_ << value.exponent();
} else {
data_ << number_value.integer_value().value();
}
}

void JsonProtoConverter::AppendObject(const JsonObject& json_object) {
data_ << '{';
bool need_comma = false;
for (const JsonMember& member : json_object.member()) {
// Trailing comma inside of an object makes JSON invalid, avoid adding it.
if (need_comma)
data_ << ',';
else
need_comma = true;

AppendEscapedString(member.name());
data_ << ':';
AppendValue(member.value());
}
data_ << '}';
}

void JsonProtoConverter::AppendValue(const JsonValue& json_value) {
if (json_value.has_object_value()) {
AppendObject(json_value.object_value());
} else if (json_value.has_array_value()) {
AppendArray(json_value.array_value());
} else if (json_value.has_number_value()) {
AppendNumber(json_value.number_value());
} else if (json_value.has_string_value()) {
AppendEscapedString(json_value.string_value().value());
} else if (json_value.has_boolean_value()) {
data_ << (json_value.boolean_value().value() ? "true" : "false");
} else {
data_ << "null";
}
}

std::string JsonProtoConverter::Convert(const JsonObject& json_object) {
AppendObject(json_object);
return data_.str();
}

std::string JsonProtoConverter::Convert(
const json_proto::ArrayValue& json_array) {
AppendArray(json_array);
return data_.str();
}

} // namespace json_proto
34 changes: 34 additions & 0 deletions src/test_lib_json/json_proto_converter.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Copyright 2026 Google LLC and The JsonCpp Authors
// Distributed under MIT license, or public domain if desired and
// recognized in your jurisdiction.
// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE

#ifndef JSON_PROTO_CONVERTER_H_
#define JSON_PROTO_CONVERTER_H_

#include <sstream>
#include <string>

#include "json.pb.h"

namespace json_proto {

class JsonProtoConverter {
public:
std::string Convert(const json_proto::JsonObject&);
std::string Convert(const json_proto::ArrayValue&);

private:
std::stringstream data_;

void AppendArray(const json_proto::ArrayValue&);
void AppendEscapedString(const std::string& str);
void AppendNumber(const json_proto::NumberValue&);
void AppendPlainFloat(float value);
void AppendObject(const json_proto::JsonObject&);
void AppendValue(const json_proto::JsonValue&);
};

} // namespace json_proto

#endif // JSON_PROTO_CONVERTER_H_
Loading