Skip to content
Open
Show file tree
Hide file tree
Changes from 6 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
15 changes: 15 additions & 0 deletions contrib/compile.cc
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#include <sourcemeta/blaze/compiler.h>
#include <sourcemeta/core/json.h>
#include <sourcemeta/core/jsonpointer.h>
#include <sourcemeta/core/jsonschema.h>
#include <sourcemeta/core/options.h>

#include <chrono> // std::chrono
Expand Down Expand Up @@ -131,6 +132,14 @@ auto main(int argc, char **argv) noexcept -> int {
sourcemeta::core::read_json(std::filesystem::path{positional.front()})};
std::cerr << "Input: " << positional.front() << "\n";

// The compiler assumes valid schema input (object or boolean) and
// asserts otherwise. Guard here at the CLI boundary to ensure
// graceful failure instead of aborting on malformed inputs.
if (!sourcemeta::core::is_schema(document)) {
std::cerr << "error: the input is not a valid JSON Schema\n";
return EXIT_FAILURE;
}

if (options.contains("path")) {
const auto pointer{sourcemeta::core::to_pointer(
std::string{options.at("path").front()})};
Expand All @@ -143,6 +152,12 @@ auto main(int argc, char **argv) noexcept -> int {

auto extracted{*result};
document = std::move(extracted);

if (!sourcemeta::core::is_schema(document)) {
std::cerr << "error: the value at the given path is not a valid "
"JSON Schema\n";
return EXIT_FAILURE;
}
}

const auto compile_start{std::chrono::high_resolution_clock::now()};
Expand Down
67 changes: 67 additions & 0 deletions contrib/run_corpus.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
#!/bin/sh
# Helper script to run the compile binary against the corpus files.
# Usage: ./contrib/run_corpus.sh <path-to-compile-binary>
#
# This is an optional utility for local testing and fuzzing preparation.
# It is NOT part of the CI test suite.
#
# Expected behavior:
# - valid/ and stress/ files: should succeed (exit 0)
# - All other categories: should fail gracefully (exit non-zero), never crash

set -e

COMPILE="${1:?Usage: $0 <path-to-compile-binary>}"
CORPUS_DIR="$(cd "$(dirname "$0")/../test/corpus/compile" && pwd)"

if [ ! -x "$COMPILE" ]; then
echo "error: compile binary not found or not executable: $COMPILE" >&2
exit 1
fi

pass=0
fail=0
crash=0

for category in valid invalid_json invalid_schema unknown_dialect edge_cases stress; do
dir="$CORPUS_DIR/$category"
[ -d "$dir" ] || continue
for f in "$dir"/*.json; do
[ -f "$f" ] || continue
name="${category}/$(basename "$f")"

rc=0
"$COMPILE" "$f" > /dev/null 2>&1 || rc=$?

if [ $rc -ge 128 ]; then
echo "CRASH $name (signal $((rc - 128)))"
crash=$((crash + 1))
elif [ "$category" = "valid" ] || [ "$category" = "stress" ]; then
if [ $rc -eq 0 ]; then
echo "PASS $name"
pass=$((pass + 1))
else
echo "FAIL $name (expected success, got exit $rc)"
fail=$((fail + 1))
fi
else
if [ $rc -ne 0 ]; then
echo "PASS $name (graceful error, exit $rc)"
pass=$((pass + 1))
else
echo "NOTE $name (unexpected success, exit 0)"
pass=$((pass + 1))
fi
fi
done
done

echo ""
echo "Results: $pass passed, $fail failed, $crash crashed"

if [ $crash -gt 0 ]; then
echo "ERROR: $crash files caused a crash!" >&2
exit 1
fi

exit 0
56 changes: 56 additions & 0 deletions test/corpus/compile/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Compile Corpus

Seed corpus for fuzzing the `contrib/compile` binary with
[AFL++](https://aflplus.plus/) or similar tools. Each file is a small,
purposeful input that exercises a specific compiler path.

This corpus is designed for fuzzing and robustness testing, not as a
formal conformance test suite.

## Categories

| Directory | Purpose |
|--------------------|--------------------------------------------------|
| `valid/` | Correct schemas across supported dialects |
| `invalid_json/` | Malformed JSON rejected by the parser |
| `invalid_schema/` | Valid JSON that violates JSON Schema constraints |
| `unknown_dialect/` | Schemas with unsupported `$schema` values |
| `edge_cases/` | Unusual but possibly valid inputs and boundaries |
| `stress/` | Large schemas to test performance and scale |

## Known Issues (Discovered via Corpus)

The following inputs trigger assertion failures (SIGABRT) inside the
compiler. They are kept in the corpus intentionally to document the
crashes and to serve as regression inputs once fixed.

| File | Input | Issue |
|------|-------|-------|
| `invalid_schema/additional_properties_as_number.json` | `"additionalProperties": 42` | Asserts on non-boolean/non-schema value |
| `invalid_schema/items_as_string.json` | `"items": "invalid"` | Asserts on non-schema items value |
| `invalid_schema/max_length_negative.json` | `"maxLength": -1` | Asserts on negative string constraint |
| `invalid_schema/multiple_of_negative.json` | `"multipleOf": -5` | Asserts on negative multipleOf |
| `edge_cases/empty_keyword_values.json` | `"allOf": [], "anyOf": []` | Asserts on empty applicator arrays |

Expected: graceful error (exception + exit 1). Actual: `assert()` failure.

## Fuzzing with AFL++

```sh
afl-fuzz -i test/corpus/compile/ -o findings/ -- \
./build/contrib/sourcemeta_blaze_contrib_compile @@
```

## Helper Script

An optional helper script is available at `contrib/run_corpus.sh` to run
the full corpus locally and detect crashes:

```sh
./contrib/run_corpus.sh ./build/contrib/sourcemeta_blaze_contrib_compile
```

## Adding New Files

Each file should be **small** and have a **single clear purpose**. Name files
descriptively (e.g., `type_as_integer.json`, `deeply_nested_allof.json`).
1 change: 1 addition & 0 deletions test/corpus/compile/edge_cases/array_root.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[]
1 change: 1 addition & 0 deletions test/corpus/compile/edge_cases/boolean_false.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
false
1 change: 1 addition & 0 deletions test/corpus/compile/edge_cases/boolean_true.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
true
4 changes: 4 additions & 0 deletions test/corpus/compile/edge_cases/dangling_ref.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$ref": "#/$defs/nonexistent"
}
4 changes: 4 additions & 0 deletions test/corpus/compile/edge_cases/deeply_nested_allof.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"allOf": [{"allOf": [{"allOf": [{"allOf": [{"allOf": [{"allOf": [{"allOf": [{"allOf": [{"allOf": [{"allOf": [{"type": "string"}]}]}]}]}]}]}]}]}]}]
}
8 changes: 8 additions & 0 deletions test/corpus/compile/edge_cases/duplicate_keys.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"name": { "type": "string" },
"name": { "type": "integer" }
}
}
4 changes: 4 additions & 0 deletions test/corpus/compile/edge_cases/empty_enum.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"enum": []
}
9 changes: 9 additions & 0 deletions test/corpus/compile/edge_cases/empty_keyword_values.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {},
"required": [],
"allOf": [],
"anyOf": [],
"oneOf": []
}
1 change: 1 addition & 0 deletions test/corpus/compile/edge_cases/empty_object.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
8 changes: 8 additions & 0 deletions test/corpus/compile/edge_cases/empty_property_name.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"": { "type": "string" }
},
"required": [""]
}
6 changes: 6 additions & 0 deletions test/corpus/compile/edge_cases/extreme_numeric_bounds.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "number",
"minimum": 1e308,
"maximum": 1e308
}
4 changes: 4 additions & 0 deletions test/corpus/compile/edge_cases/invalid_regex_pattern.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"pattern": "[invalid(regex"
}
1 change: 1 addition & 0 deletions test/corpus/compile/edge_cases/number_root.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
42
7 changes: 7 additions & 0 deletions test/corpus/compile/edge_cases/self_referencing.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$ref": "#/$defs/self",
"$defs": {
"self": { "$ref": "#/$defs/self" }
}
}
9 changes: 9 additions & 0 deletions test/corpus/compile/edge_cases/unicode_keys.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"héllo": { "type": "string" },
"日本語": { "type": "string" },
"🎉": { "type": "boolean" }
}
}
7 changes: 7 additions & 0 deletions test/corpus/compile/edge_cases/unknown_keywords.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"unknown_keyword": true,
"x-custom-extension": { "foo": "bar" },
"notAKeyword": [1, 2, 3]
}
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{
5 changes: 5 additions & 0 deletions test/corpus/compile/invalid_json/missing_comma.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"type": "string",
"minLength": 1
"maxLength": 10
}
1 change: 1 addition & 0 deletions test/corpus/compile/invalid_json/multiple_roots.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"type": "string"} {"type": "number"}
1 change: 1 addition & 0 deletions test/corpus/compile/invalid_json/plain_text.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
hello world this is not json
1 change: 1 addition & 0 deletions test/corpus/compile/invalid_json/trailing_comma.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"type": "string",}
1 change: 1 addition & 0 deletions test/corpus/compile/invalid_json/truncated.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"type": "stri
1 change: 1 addition & 0 deletions test/corpus/compile/invalid_json/unquoted_key.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{type: "string"}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"additionalProperties": 42
}
4 changes: 4 additions & 0 deletions test/corpus/compile/invalid_schema/items_as_string.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"items": "invalid"
}
4 changes: 4 additions & 0 deletions test/corpus/compile/invalid_schema/max_length_negative.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"maxLength": -1
}
4 changes: 4 additions & 0 deletions test/corpus/compile/invalid_schema/multiple_of_negative.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"multipleOf": -5
}
4 changes: 4 additions & 0 deletions test/corpus/compile/invalid_schema/multiple_of_zero.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"multipleOf": 0
}
4 changes: 4 additions & 0 deletions test/corpus/compile/invalid_schema/ref_as_number.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$ref": 42
}
4 changes: 4 additions & 0 deletions test/corpus/compile/invalid_schema/type_as_integer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": 123
}
Loading
Loading