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
11 changes: 11 additions & 0 deletions .clang-format
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
BasedOnStyle: LLVM
Language: Cpp
ColumnLimit: 100
UseTab: ForIndentation
TabWidth: 8
IndentWidth: 8
BreakBeforeBraces: Linux
IndentCaseLabels: false
AllowShortFunctionsOnASingleLine: Empty
SortIncludes: false
PointerAlignment: Right
7 changes: 7 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 15 additions & 0 deletions crates/sidex-gen-c/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[package]
name = "sidex-gen-c"
description = "Sidex code generation support for C."
version = "0.1.0"
edition.workspace = true
authors.workspace = true
license.workspace = true
repository.workspace = true
homepage.workspace = true

[dependencies]
sidex-gen.workspace = true

[lints]
workspace = true
8 changes: 8 additions & 0 deletions crates/sidex-gen-c/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Sidex: C Code Generation

This crate is part of [Sidex](https://oss.silitics.com/sidex/). It contains
the C code generation backend and the small portable C runtime used by
generated C bindings.

The backend is intentionally generic and independent of any product or RTOS.
The C runtime is C99 and avoids dynamic allocation.
51 changes: 51 additions & 0 deletions crates/sidex-gen-c/fuzz/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# Sidex C JSON Runtime Fuzzing

This directory contains a libFuzzer target for the portable C JSON runtime.

Build it with Clang:

```sh
mkdir -p target/sidex-gen-c-fuzz
clang \
-std=c99 -g -O1 \
-fsanitize=fuzzer,address,undefined \
-I crates/sidex-gen-c/runtime \
crates/sidex-gen-c/runtime/sidex_types.c \
crates/sidex-gen-c/runtime/sidex_json_reader.c \
crates/sidex-gen-c/runtime/sidex_json_value.c \
crates/sidex-gen-c/runtime/sidex_json_writer.c \
crates/sidex-gen-c/fuzz/json_runtime_fuzzer.c \
-o target/sidex-gen-c-fuzz/json_runtime_fuzzer
```

Run it with the seed corpus:

```sh
rm -rf target/sidex-gen-c-fuzz/json_runtime_corpus
mkdir -p target/sidex-gen-c-fuzz/json_runtime_corpus
cp crates/sidex-gen-c/fuzz/corpus/json_runtime/* \
target/sidex-gen-c-fuzz/json_runtime_corpus/
target/sidex-gen-c-fuzz/json_runtime_fuzzer \
target/sidex-gen-c-fuzz/json_runtime_corpus \
-max_len=16384 \
-rss_limit_mb=512
```

If Clang is not installed locally, the same commands can be run through Nix:

```sh
nix --extra-experimental-features 'nix-command flakes' shell nixpkgs#clang nixpkgs#clang-tools -c clang \
-std=c99 -g -O1 \
-fsanitize=fuzzer,address,undefined \
-I crates/sidex-gen-c/runtime \
crates/sidex-gen-c/runtime/sidex_types.c \
crates/sidex-gen-c/runtime/sidex_json_reader.c \
crates/sidex-gen-c/runtime/sidex_json_value.c \
crates/sidex-gen-c/runtime/sidex_json_writer.c \
crates/sidex-gen-c/fuzz/json_runtime_fuzzer.c \
-o target/sidex-gen-c-fuzz/json_runtime_fuzzer
```

The fuzzer treats malformed JSON, arena exhaustion, and output-buffer exhaustion
as expected non-crashing outcomes. Successful decodes are encoded, parsed again,
and encoded a second time; the two encoded byte strings must match.
1 change: 1 addition & 0 deletions crates/sidex-gen-c/fuzz/corpus/json_runtime/edge.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"empty_object":{},"empty_array":[],"numbers":[0,1,-1,9007199254740991,1.0,1e+9]}
1 change: 1 addition & 0 deletions crates/sidex-gen-c/fuzz/corpus/json_runtime/nested.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"items":[{"id":1,"tags":["a","b"]},{"id":2,"tags":[]}],"meta":{"ok":true}}
1 change: 1 addition & 0 deletions crates/sidex-gen-c/fuzz/corpus/json_runtime/null.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
null
1 change: 1 addition & 0 deletions crates/sidex-gen-c/fuzz/corpus/json_runtime/scalars.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[true,false,null,0,-1,12.5e-3,"text","line\nbreak"]
1 change: 1 addition & 0 deletions crates/sidex-gen-c/fuzz/corpus/json_runtime/unicode.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"raw":"ß🚀","escaped":"\u00df\ud83d\ude80","control":"a\u0000b"}
121 changes: 121 additions & 0 deletions crates/sidex-gen-c/fuzz/json_runtime_fuzzer.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
#include "sidex_json.h"

#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>

#ifndef SIDEX_FUZZ_MAX_INPUT
#define SIDEX_FUZZ_MAX_INPUT 16384u
#endif

#ifndef SIDEX_FUZZ_ARENA_CAP
#define SIDEX_FUZZ_ARENA_CAP 65536u
#endif

static void require(bool condition)
{
if (!condition) {
abort();
}
}

static void exercise_skip_value(const uint8_t *data, size_t size)
{
sidex_json_reader reader;
sidex_json_token_kind kind;
int rc;

sidex_json_reader_init(&reader, data, size);
rc = sidex_json_skip_value(&reader, SIDEX_JSON_DEFAULT_MAX_DEPTH);
if (rc == SIDEX_OK) {
(void)sidex_json_peek(&reader, &kind);
}
}

static void exercise_value_round_trip(const uint8_t *data, size_t size)
{
uint8_t *arena_storage;
uint8_t *second_arena_storage;
uint8_t *output;
uint8_t *second_output;
size_t output_cap;
sidex_arena arena;
sidex_arena second_arena;
sidex_json_reader reader;
sidex_json_reader second_reader;
sidex_json_value value;
sidex_json_value second_value;
sidex_json_buffer buffer;
sidex_json_buffer second_buffer;
sidex_json_writer writer;
sidex_json_writer second_writer;
sidex_json_token_kind kind;
int rc;

arena_storage = (uint8_t *)malloc(SIDEX_FUZZ_ARENA_CAP);
second_arena_storage = (uint8_t *)malloc(SIDEX_FUZZ_ARENA_CAP);
output_cap = size * 8u + 256u;
output = (uint8_t *)malloc(output_cap);
second_output = (uint8_t *)malloc(output_cap);
if (arena_storage == NULL || second_arena_storage == NULL || output == NULL ||
second_output == NULL) {
goto done;
}

arena = sidex_arena_from_parts(arena_storage, SIDEX_FUZZ_ARENA_CAP);
sidex_json_reader_init(&reader, data, size);
rc = sidex_json_read_value(&reader, &value, &arena, SIDEX_JSON_DEFAULT_MAX_DEPTH);
if (rc != SIDEX_OK) {
goto done;
}

rc = sidex_json_peek(&reader, &kind);
if (rc != SIDEX_OK) {
goto done;
}
if (kind != SIDEX_JSON_TOKEN_END) {
goto done;
}

sidex_json_buffer_init(&buffer, output, output_cap);
writer = sidex_json_writer_for_buffer(&buffer);
rc = sidex_json_write_value(&writer, &value, SIDEX_JSON_DEFAULT_MAX_DEPTH);
if (rc != SIDEX_OK) {
goto done;
}

second_arena = sidex_arena_from_parts(second_arena_storage, SIDEX_FUZZ_ARENA_CAP);
sidex_json_reader_init(&second_reader, output, buffer.len);
rc = sidex_json_read_value(&second_reader, &second_value, &second_arena,
SIDEX_JSON_DEFAULT_MAX_DEPTH);
require(rc == SIDEX_OK);
rc = sidex_json_peek(&second_reader, &kind);
require(rc == SIDEX_OK);
require(kind == SIDEX_JSON_TOKEN_END);

sidex_json_buffer_init(&second_buffer, second_output, output_cap);
second_writer = sidex_json_writer_for_buffer(&second_buffer);
rc = sidex_json_write_value(&second_writer, &second_value, SIDEX_JSON_DEFAULT_MAX_DEPTH);
require(rc == SIDEX_OK);
require(second_buffer.len == buffer.len);
require(memcmp(second_output, output, buffer.len) == 0);

done:
free(second_output);
free(output);
free(second_arena_storage);
free(arena_storage);
}

int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
{
if (size > SIDEX_FUZZ_MAX_INPUT) {
return 0;
}

(void)sidex_utf8_validate(data, size);
exercise_skip_value(data, size);
exercise_value_round_trip(data, size);
return 0;
}
10 changes: 10 additions & 0 deletions crates/sidex-gen-c/runtime/sidex_json.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#ifndef SIDEX_JSON_H
#define SIDEX_JSON_H

/** Umbrella include for the Sidex JSON reader and writer APIs. */

#include "sidex_json_reader.h"
#include "sidex_json_value.h"
#include "sidex_json_writer.h"

#endif
Loading
Loading