diff --git a/fuzz/.gitignore b/fuzz/.gitignore new file mode 100644 index 00000000..1a45eee7 --- /dev/null +++ b/fuzz/.gitignore @@ -0,0 +1,4 @@ +target +corpus +artifacts +coverage diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml new file mode 100644 index 00000000..b279d999 --- /dev/null +++ b/fuzz/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "benchmark-fuzz" +version = "0.0.0" +publish = false +edition = "2021" + +[package.metadata] +cargo-fuzz = true + +[dependencies] +libfuzzer-sys = "0.4" +jsonschema = { path = "../crates/jsonschema/" } +serde_json = "1" + +[workspace] +members = ["."] + +[profile.release] +debug = true + +[[bin]] +name = "compile" +path = "fuzz_targets/compile.rs" +test = false +doc = false +bench = false diff --git a/fuzz/dict b/fuzz/dict new file mode 100644 index 00000000..929fd17a --- /dev/null +++ b/fuzz/dict @@ -0,0 +1,95 @@ +# JSON Schema keywords +"$schema" +"$id" +"type" +"properties" +"required" +"additionalProperties" +"items" +"allOf" +"anyOf" +"oneOf" +"not" +"definitions" +"$ref" +"enum" +"const" +"multipleOf" +"maximum" +"exclusiveMaximum" +"minimum" +"exclusiveMinimum" +"maxLength" +"minLength" +"pattern" +"maxItems" +"minItems" +"uniqueItems" +"maxProperties" +"minProperties" +"dependencies" +"propertyNames" +"if" +"then" +"else" +"format" + +# Data types +"object" +"array" +"string" +"number" +"integer" +"boolean" +"null" + +# Common patterns +"^[a-zA-Z0-9_]+$" +"^[0-9]{4}-[0-9]{2}-[0-9]{2}$" + +# Special characters +"{" +"}" +"[" +"]" +":" +"," + +# Common values +"true" +"false" +"null" +"0" +"1" +"-1" +"3.14" + +# URI references +"#" +"#/definitions/" +"#/properties/" + +# Format specifiers +"date-time" +"date" +"time" +"email" +"idn-email" +"hostname" +"idn-hostname" +"ipv4" +"ipv6" +"uri" +"uri-reference" +"iri" +"iri-reference" +"uuid" + +# Common property names +"id" +"name" +"title" +"description" +"default" +"examples" +"$comment" diff --git a/fuzz/fuzz_targets/compile.rs b/fuzz/fuzz_targets/compile.rs new file mode 100644 index 00000000..bb7685a4 --- /dev/null +++ b/fuzz/fuzz_targets/compile.rs @@ -0,0 +1,9 @@ +#![no_main] +use libfuzzer_sys::fuzz_target; + +fuzz_target!(|data: &[u8]| { + let Ok(schema) = serde_json::from_slice(data) else { + return; + }; + let _ = jsonschema::compile(&schema); +}); diff --git a/fuzz/seeds/compile/array b/fuzz/seeds/compile/array new file mode 100644 index 00000000..6be66b0d --- /dev/null +++ b/fuzz/seeds/compile/array @@ -0,0 +1,14 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { "type": "integer" }, + "value": { "type": "string" } + }, + "required": ["id", "value"] + }, + "minItems": 1, + "uniqueItems": true +} diff --git a/fuzz/seeds/compile/combinators b/fuzz/seeds/compile/combinators new file mode 100644 index 00000000..a9308615 --- /dev/null +++ b/fuzz/seeds/compile/combinators @@ -0,0 +1,16 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "allOf": [ + { "properties": { "name": { "type": "string" } } }, + { "properties": { "age": { "type": "integer", "minimum": 0 } } } + ], + "anyOf": [ + { "properties": { "email": { "type": "string", "format": "email" } } }, + { "properties": { "phone": { "type": "string", "pattern": "^\\+?[0-9]{10,14}$" } } } + ], + "oneOf": [ + { "properties": { "type": { "const": "user" } } }, + { "properties": { "type": { "const": "admin" } } } + ] +} diff --git a/fuzz/seeds/compile/enum b/fuzz/seeds/compile/enum new file mode 100644 index 00000000..4e930ca3 --- /dev/null +++ b/fuzz/seeds/compile/enum @@ -0,0 +1,13 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "color": { "enum": ["red", "green", "blue"] }, + "version": { "const": "1.0" }, + "features": { + "type": "array", + "items": { "type": "string" } + } + }, + "required": ["color", "version"] +} diff --git a/fuzz/seeds/compile/nested-object b/fuzz/seeds/compile/nested-object new file mode 100644 index 00000000..cdbf31d4 --- /dev/null +++ b/fuzz/seeds/compile/nested-object @@ -0,0 +1,18 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "user": { + "type": "object", + "properties": { + "id": { "type": "integer" }, + "username": { "type": "string", "pattern": "^[a-z0-9_]{3,16}$" } + }, + "required": ["id", "username"] + } + }, + "patternProperties": { + "^data_": { "type": "string" } + }, + "additionalProperties": false +} diff --git a/fuzz/seeds/compile/object b/fuzz/seeds/compile/object new file mode 100644 index 00000000..5ce39610 --- /dev/null +++ b/fuzz/seeds/compile/object @@ -0,0 +1,10 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "name": { "type": "string" }, + "age": { "type": "integer", "minimum": 0 }, + "email": { "type": "string", "format": "email" } + }, + "required": ["name", "age"] +} diff --git a/fuzz/seeds/compile/ref-combo b/fuzz/seeds/compile/ref-combo new file mode 100644 index 00000000..91ab05e0 --- /dev/null +++ b/fuzz/seeds/compile/ref-combo @@ -0,0 +1,34 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "definitions": { + "positiveInteger": { + "type": "integer", + "minimum": 1 + }, + "nonEmptyString": { + "type": "string", + "minLength": 1 + } + }, + "properties": { + "id": { "$ref": "#/definitions/positiveInteger" }, + "name": { "$ref": "#/definitions/nonEmptyString" }, + "tags": { + "type": "array", + "items": { "$ref": "#/definitions/nonEmptyString" }, + "uniqueItems": true + }, + "metadata": { + "type": "object", + "propertyNames": { "$ref": "#/definitions/nonEmptyString" }, + "additionalProperties": { + "oneOf": [ + { "$ref": "#/definitions/positiveInteger" }, + { "$ref": "#/definitions/nonEmptyString" } + ] + } + } + }, + "required": ["id", "name"] +} diff --git a/fuzz/seeds/compile/ref-external b/fuzz/seeds/compile/ref-external new file mode 100644 index 00000000..0f0ba795 --- /dev/null +++ b/fuzz/seeds/compile/ref-external @@ -0,0 +1,16 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "user": { "$ref": "https://example.com/schemas/user.json" }, + "order": { + "type": "object", + "properties": { + "id": { "type": "string" }, + "items": { "$ref": "https://example.com/schemas/order-items.json" } + }, + "required": ["id", "items"] + } + }, + "required": ["user", "order"] +} diff --git a/fuzz/seeds/compile/ref-local b/fuzz/seeds/compile/ref-local new file mode 100644 index 00000000..a941ceb9 --- /dev/null +++ b/fuzz/seeds/compile/ref-local @@ -0,0 +1,29 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "definitions": { + "address": { + "type": "object", + "properties": { + "street": { "type": "string" }, + "city": { "type": "string" }, + "country": { "type": "string" } + }, + "required": ["street", "city", "country"] + }, + "person": { + "type": "object", + "properties": { + "name": { "type": "string" }, + "age": { "type": "integer", "minimum": 0 }, + "address": { "$ref": "#/definitions/address" } + }, + "required": ["name", "age", "address"] + } + }, + "properties": { + "user": { "$ref": "#/definitions/person" }, + "emergencyContact": { "$ref": "#/definitions/person" } + }, + "required": ["user", "emergencyContact"] +} diff --git a/fuzz/seeds/compile/ref-nested b/fuzz/seeds/compile/ref-nested new file mode 100644 index 00000000..a722a37c --- /dev/null +++ b/fuzz/seeds/compile/ref-nested @@ -0,0 +1,37 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "definitions": { + "dimension": { + "type": "number", + "minimum": 0 + }, + "rectangle": { + "type": "object", + "properties": { + "width": { "$ref": "#/definitions/dimension" }, + "height": { "$ref": "#/definitions/dimension" } + }, + "required": ["width", "height"] + }, + "circle": { + "type": "object", + "properties": { + "radius": { "$ref": "#/definitions/dimension" } + }, + "required": ["radius"] + } + }, + "properties": { + "shapes": { + "type": "array", + "items": { + "oneOf": [ + { "$ref": "#/definitions/rectangle" }, + { "$ref": "#/definitions/circle" } + ] + } + } + }, + "required": ["shapes"] +} diff --git a/fuzz/seeds/compile/ref-recursive b/fuzz/seeds/compile/ref-recursive new file mode 100644 index 00000000..d89073bc --- /dev/null +++ b/fuzz/seeds/compile/ref-recursive @@ -0,0 +1,21 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "definitions": { + "person": { + "type": "object", + "properties": { + "name": { "type": "string" }, + "children": { + "type": "array", + "items": { "$ref": "#/definitions/person" } + } + }, + "required": ["name"] + } + }, + "properties": { + "familyTree": { "$ref": "#/definitions/person" } + }, + "required": ["familyTree"] +}