Files
weaselbot df693ef4c9
CI / pre-commit (pull_request) Successful in 51s
CI / build (-DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++, clang-arm64, ubuntu-latest-arm64, true) (pull_request) Successful in 1m3s
CI / build (-DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++, gcc-arm64, ubuntu-latest-arm64, false) (pull_request) Successful in 1m3s
CI / build (-DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++, clang-amd64, ubuntu-latest-amd64, true) (pull_request) Successful in 1m35s
CI / build (-DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++, gcc-amd64, ubuntu-latest-amd64, false) (pull_request) Successful in 1m24s
schemagen: reject scalar root values when root schema is object/array
The generated RootBuilder crashed (undefined behavior on std::vector::back())
when a JSON document's root value was a scalar or null while the schema
declared a non-nullable object or array root. The stack starts empty for
object/array roots, but cbStringData, cbNumberData, and cbBool called
stack_.back() without checking for an empty stack.

Add an empty-stack guard to the three scalar callbacks so they reject
instead of crashing. cbNull already handles the empty-stack case.

Regression tests added for:
- non-nullable object root rejecting null, boolean, number, and string roots
- nullable object root rejecting scalar roots
- nullable array root rejecting scalar roots

Closes #16
2026-06-24 14:30:36 -04:00

199 lines
5.8 KiB
C++

// Regression test for issue #13: nullable root types.
#include <cassert>
#include <cstdio>
#include <string>
#include "nullable_array.h"
#include "nullable_object.h"
#include "nullable_string.h"
static int failures = 0;
#define CHECK(cond) \
do { \
if (!(cond)) { \
printf("FAIL %s:%d: %s\n", __FILE__, __LINE__, #cond); \
++failures; \
} \
} while (0)
static WeaselJsonStatus parseStrided(nullable_object::RootBuilder &b,
std::string in) {
for (size_t i = 0; i < in.size(); ++i) {
char c = in[i];
WeaselJsonStatus s = b.feed(&c, 1);
if (s != WeaselJson_AGAIN)
return s;
}
return b.finish();
}
static WeaselJsonStatus parseStrided(nullable_string::RootBuilder &b,
std::string in) {
for (size_t i = 0; i < in.size(); ++i) {
char c = in[i];
WeaselJsonStatus s = b.feed(&c, 1);
if (s != WeaselJson_AGAIN)
return s;
}
return b.finish();
}
static WeaselJsonStatus parseStrided(nullable_array::RootBuilder &b,
std::string in) {
for (size_t i = 0; i < in.size(); ++i) {
char c = in[i];
WeaselJsonStatus s = b.feed(&c, 1);
if (s != WeaselJson_AGAIN)
return s;
}
return b.finish();
}
static void expectReject(nullable_object::RootBuilder &b, std::string in,
const char *what) {
WeaselJsonStatus s = parseStrided(b, in);
if (s == WeaselJson_REJECT) {
printf("ok reject: %s\n", what);
} else {
printf("FAIL expected reject (%s) got status %d for: %s\n", what, s,
in.c_str());
++failures;
}
}
static void expectReject(nullable_array::RootBuilder &b, std::string in,
const char *what) {
WeaselJsonStatus s = parseStrided(b, in);
if (s == WeaselJson_REJECT) {
printf("ok reject: %s\n", what);
} else {
printf("FAIL expected reject (%s) got status %d for: %s\n", what, s,
in.c_str());
++failures;
}
}
int main() {
// ---- nullable root object: valid document ----
{
nullable_object::RootBuilder b;
WeaselJsonStatus s = parseStrided(b, R"({"x":"hello"})");
CHECK(s == WeaselJson_OK);
if (s == WeaselJson_OK) {
nullable_object::Root r = b.take();
CHECK(r.has_value());
CHECK(r->x == "hello");
printf("ok nullable root object accepts object\n");
}
}
// ---- nullable root object: null document ----
{
nullable_object::RootBuilder b;
WeaselJsonStatus s = parseStrided(b, "null");
CHECK(s == WeaselJson_OK);
if (s == WeaselJson_OK) {
nullable_object::Root r = b.take();
CHECK(!r.has_value());
printf("ok nullable root object accepts null\n");
}
}
// ---- nullable root object: schema checks still run ----
{
nullable_object::RootBuilder b;
expectReject(b, R"({"x":"hello","extra":1})",
"unknown key in strict nullable root object");
}
{
nullable_object::RootBuilder b;
expectReject(b, R"({})", "missing required field in nullable root object");
}
// ---- nullable root string: valid value ----
{
nullable_string::RootBuilder b;
WeaselJsonStatus s = parseStrided(b, R"("hello")");
CHECK(s == WeaselJson_OK);
if (s == WeaselJson_OK) {
nullable_string::Root r = b.take();
CHECK(r.has_value());
CHECK(*r == "hello");
printf("ok nullable root string accepts string\n");
}
}
// ---- nullable root string: null value ----
{
nullable_string::RootBuilder b;
WeaselJsonStatus s = parseStrided(b, "null");
CHECK(s == WeaselJson_OK);
if (s == WeaselJson_OK) {
nullable_string::Root r = b.take();
CHECK(!r.has_value());
printf("ok nullable root string accepts null\n");
}
}
// ---- nullable root array: valid value ----
{
nullable_array::RootBuilder b;
WeaselJsonStatus s = parseStrided(b, "[1,2,3]");
CHECK(s == WeaselJson_OK);
if (s == WeaselJson_OK) {
nullable_array::Root r = b.take();
CHECK(r.has_value());
CHECK(r->size() == 3);
CHECK((*r)[0] == 1 && (*r)[1] == 2 && (*r)[2] == 3);
printf("ok nullable root array accepts array\n");
}
}
// ---- nullable root array: null value ----
{
nullable_array::RootBuilder b;
WeaselJsonStatus s = parseStrided(b, "null");
CHECK(s == WeaselJson_OK);
if (s == WeaselJson_OK) {
nullable_array::Root r = b.take();
CHECK(!r.has_value());
printf("ok nullable root array accepts null\n");
}
}
// ---- nullable root object: scalar values rejected ----
{
nullable_object::RootBuilder b;
expectReject(b, "true", "boolean where nullable object expected (root)");
}
{
nullable_object::RootBuilder b;
expectReject(b, "123", "number where nullable object expected (root)");
}
{
nullable_object::RootBuilder b;
expectReject(b, R"("hi")", "string where nullable object expected (root)");
}
// ---- nullable root array: scalar values rejected ----
{
nullable_array::RootBuilder b;
expectReject(b, "true", "boolean where nullable array expected (root)");
}
{
nullable_array::RootBuilder b;
expectReject(b, "123", "number where nullable array expected (root)");
}
{
nullable_array::RootBuilder b;
expectReject(b, R"("hi")", "string where nullable array expected (root)");
}
if (failures == 0) {
printf("\nALL TESTS PASSED\n");
return 0;
}
printf("\n%d FAILURE(S)\n", failures);
return 1;
}