Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

tests: add new fuzzer #2965

Merged
merged 1 commit into from
Dec 7, 2023
Merged
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
297 changes: 297 additions & 0 deletions tests/jq_fuzz_fixed.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,297 @@
#include <fuzzer/FuzzedDataProvider.h>
#include <string>

#include "jq.h"
#include "jv.h"


const char *jq_progs[] = {
". / \", \"",
".[]",
"$ENV.PAGER",
".[0]",
". < 0.12345678901234567890123456788",
".[] == 1",
".[] | (1 / .)?",
"10 / . * 3",
"[1,2,empty,3]",
"1, empty, 2",
"[.,1]|until(.[0] < 1; [.[0] - 1, .[1] * .[0]])|.[1]",
".[-2:]",
".[-2]",
".[2]",
"[ .[] | . * 2]",
".[2:4]",
"(. + 2) * 5",
".[:3]",
".[4,2]",
"42 and \"a string\"",
"4 - .a",
". < 5",
".. | .a?",
"[.[] | .a?]",
".a + 1",
"{a: 1} + {b: 2} + {c: 3} + {a: 42}",
".a + .b",
".a = .b",
".a |= .b",
"add",
"all",
".a + null",
"any",
".[] as [$a, $b] | {a: $a, b: $b}",
". as [$a, $b, {c: $c}] | $a + $b + $c",
".[] as {$a, $b, c: {$d}} ?// {$a, $b, c: [{$e}]} | {$a, $b, $d, $e}",
".[] as {$a, $b, c: {$d, $e}} ?// {$a, $b, c: [{$d, $e}]} | {$a, $b, "
"$d, $e}",
".[] as [$a] ?// [$b] | if $a != null then error(\"err: \($a)\") else "
"{$a,$b} end",
". as $big | [$big, $big + 1] | map(. > "
"10000000000000000000000000000000)",
". as $dot|fromstream($dot|tostream)|.==$dot",
". as $i|[(.*2|. as $i| $i), $i]",
"ascii_upcase",
".bar as $x | .foo | . + $x",
"@base64",
"@base64d",
". == {\"b\": {\"d\": (4 + 1e-20), \"c\": 3}, \"a\":1}",
"bsearch(0)",
"bsearch(4) as $ix | if $ix < 0 then .[-(1+$ix)] = 4 else . end",
"capture(\"(?<a>[a-z]+)-(?<n>[0-9]+)\")",
"capture(\"(?<a>[a-z]+)-(?<n>[0-9]+)\")",
"combinations",
"combinations(2)",
"contains(\"bar\")",
"contains([\"baz\", \"bar\"])",
"contains([\"bazzzzz\", \"bar\"])",
"contains({foo: 12, bar: [{barp: 15}]})",
"def addvalue(f): f as $x | map(. + $x); addvalue(.[0])",
"def addvalue(f): . + [f]; map(addvalue(.[0]))",
"def while(cond; update): def _while: if cond then ., (update | "
"_while) else empty end; _while; [while(.<100; .*2)]",
"del(.[1, 2])",
"del(.foo)",
"delpaths([[\"a\",\"b\"]])",
"empty // 42",
"[.[]|endswith(\"foo\")]",
"env.PAGER",
"explode",
". == false",
"(false, null, 1) // 42",
"(false, null, 1) | . // 42",
"flatten",
"flatten(1)",
"floor",
".[\"foo\"]",
".[\"foo\"]?",
".foo",
".foo?",
".foo[]",
"[.foo?]",
".foo += 1",
".foo // 42",
".foo, .bar",
"foreach .[] as $item (0; . + $item)",
"foreach .[] as $item (0; . + $item; [$item, . * 2])",
"foreach .[] as $item (0; . + 1; {index: ., $item})",
"fromdate",
"from_entries",
"fromstream(1|truncate_stream([[0],1],[[1,0],2],[[1,0]],[[1]]))",
"getpath([\"a\",\"b\"])",
"[getpath([\"a\",\"b\"], [\"a\",\"c\"])]",
"group_by(.foo)",
"[.[] | gsub(\", \"; \":\")]",
"gsub(\"$\"; \"a\"; \"g\")",
"gsub(\"^\"; \"a\")",
"[gsub(\"(?<a>.)\"; \"\(.a|ascii_upcase)\", \"\(.a|ascii_downcase)\", "
"\"c\")]",
"gsub(\"^.*?a\"; \"b\")",
"gsub(\"^.*a\"; \"b\")",
"gsub(\"a\";\"b\")",
"gsub(\"\"; \"a\"; \"g\")",
"gsub(\"\"; \"a\"; \"g\")",
"gsub(\"[^a-z]*(?<x>[a-z]*)\"; \"Z\(.x)\")",
"gsub(\"\\b(?<x>.)\"; \"\(.x|ascii_downcase)\")",
"gsub(\"(?<d>\\d)\"; \":\(.d);\")",
"gsub(\"^\"; \"\"; \"g\")",
"[gsub(\"p\"; \"a\", \"b\")]",
"gsub(\"(?=u)\"; \"u\")",
"gsub(\"(.*)\"; \"\"; \"x\")",
"gsub(\"(?<x>.)[^a]*\"; \"+\(.x)-\")",
"gsub(\"(?<x>.)(?<y>[0-9])\"; \"\(.x|ascii_downcase)\(.y)\")",
"@html",
"if . == 0 then \"zero\" elif . == 1 then \"one\" else \"many\" "
"end",
"implode",
"index(\", \")",
"index(1)",
"index([1,2])",
"indices(\", \")",
"indices(1)",
"indices([1,2])",
".[] | (infinite * .) < 0",
"infinite, nan | type",
".[] | in({\"foo\": 42})",
"inside({\"foo\": 12, \"bar\":[1,2,{\"barp\":12, \"blip\":13}]})",
"inside({\"foo\": 12, \"bar\":[1,2,{\"barp\":12, \"blip\":13}]})",
"inside(\"foobar\")",
"inside([\"foobar\", \"foobaz\", \"blarp\"])",
"inside([\"foobar\", \"foobaz\", \"blarp\"])",
"isempty(.[])",
"isempty(.[])",
"isempty(empty)",
"join(\" \")",
"join(\", \")",
"keys",
"keys",
".[] | length",
"[limit(3;.[])]",
"[.[]|ltrimstr(\"foo\")]",
"map(., .)",
"map(.+1)",
"map([., . == 1]) | tojson",
"map(abs)",
"map(has(2))",
"map(has(\"foo\"))",
"map(in([0,1]))",
"map(select(. >= 2))",
"map(type)",
"map_values(.+1)",
"map_values(. // empty)",
"match(\"(abc)+\"; \"g\")",
"[match(\"a\"; \"gi\")]",
"[match(\".+?\\b\")]",
"[match([\"(bar)\"])]",
"match(\"foo\")",
"[match([\"foo (?<bar123>bar)? foo\", \"ig\"])]",
"match(\"foo (?<bar123>bar)? foo\"; \"ig\")",
"match([\"foo\", \"ig\"])",
"[match(\"\"; \"g\")]",
"[ match(\".\"; \"g\")] | length",
"[match(\"( )*\"; \"gn\")]",
"max_by(.foo)",
"min",
".[] | .name",
".[]|numbers",
"[path(..)]",
"path(.a[0].b)",
"[paths]",
"[paths(type == \"number\")]",
"pick(.[2], .[0], .[0])",
"pick(.a, .b.c, .x)",
"[range(0; 10; -1)]",
"[range(0; 10; 3)]",
"[range(0; -5; -1)]",
"[range(2; 4)]",
"range(2; 4)",
"[range(4)]",
"[range(.)]|[first, last, nth(5)]",
"recurse",
"recurse(. * .; . < 20)",
"recurse(.foo[])",
"reduce .[] as [$i,$j] (0; . + $i * $j)",
"reduce .[] as $item (0; . + $item)",
"reduce .[] as {$x,$y} (null; .x += $x | .y += [$y])",
"[repeat(.*2, error)?]",
"reverse",
"rindex(\", \")",
"rindex(1)",
"rindex([1,2])",
"[.[]|rtrimstr(\"foo\")]",
"[.[] | scan(\", \")]",
"[.[] | scan(\"b+\"; \"i\")]",
"scan(\"c\")",
".[] | select(.id == \"second\")",
"(..|select(type==\"boolean\")) |= if . then 1 else 0 end",
"setpath([0,\"a\"]; 1)",
"setpath([\"a\",\"b\"]; 1)",
"setpath([\"a\",\"b\"]; 1)",
"@sh \"echo \(.)\"",
"sort",
"sort_by(.foo)",
"sort_by(.foo, .bar)",
"split(\", *\"; null)",
"splits(\", *\")",
"sqrt",
"[.[]|startswith(\"foo\")]",
"strptime(\"%Y-%m-%dT%H:%M:%SZ\")",
"strptime(\"%Y-%m-%dT%H:%M:%SZ\")|mktime",
"[sub(\"(?<a>.)\"; \"\(.a|ascii_upcase)\", \"\(.a|ascii_downcase)\")]",
"[sub(\"(?<a>.)\"; \"\(.a|ascii_upcase)\", \"\(.a|ascii_downcase)\", "
"\"c\")]",
"[sub(\"a\"; \"b\", \"c\")]",
"sub(\"[^a-z]*(?<x>[a-z]+)\"; \"Z\(.x)\"; \"g\")",
"[.[]|[[sub(\", *\";\":\")], [gsub(\", *\";\":\")], [scan(\", *\")]]]",
"[.[]|[[sub(\", +\";\":\")], [gsub(\", +\";\":\")], [scan(\", +\")]]]",
"sub(\"^(?<head>.)\"; \"Head=\(.head) Tail=\")",
"[test(\"ā\")]",
".[] | test(\"a b c # spaces are ignored\"; \"ix\")",
"test(\"foo\")",
"to_entries",
"[., tojson]",
"[.[]|tojson]",
"[.[]|tojson|fromjson]",
".[] | tonumber",
"[.[] | tonumber?]",
".[] | tostring",
"[.[]|tostring]",
"transpose",
"[true, false | not]",
"(true, false) or false",
"(true, true) and (true, false)",
"truncate_stream([[0],1],[[1,0],2],[[1,0]],[[1]])",
"[.[]|try .a]",
"unique",
"unique_by(.foo)",
"unique_by(length)",
".user, .projects[]",
"[.user, .projects[]]",
"{(.user): .titles}",
"{user, title: .titles[]}",
"utf8bytelength",
"walk(if type == \"array\" then sort else . end)",
"walk( if type == \"object\" then with_entries( .key |= sub( \"^_+\"; "
"\"\") ) else . end )",
"[while(.<100; .*2)]",
"with_entries(.key |= \"KEY_\" + .)",
". - [\"xml\", \"yaml\"]",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are these generated from documentation or test? maybe could be?

Copy link
Contributor Author

@DavidKorczynski DavidKorczynski Dec 7, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

from the tests/*.test. This one specifically is:

. - ["xml", "yaml"]

Specifically, it was generated using a simple scraping "capture each line that is after an empty line in some of the .test files" with a bit of sorting etc.

Please feel free to add any new ones as well! The more the merrier, and also it's not a big deal (for the fuzzer at least) if some of them are not valid.

};

// Fuzzer inspired by /src/jq_test.c
// The goal is to have the fuzzer execute the functions:
// jq_compile -> jv_parse -> jq_next.
extern "C" int LLVMFuzzerTestOneInput(uint8_t *data, size_t size) {
FuzzedDataProvider fdp(data, size);
std::string parse_payload1 = fdp.ConsumeRandomLengthString();
std::string parse_payload2 = fdp.ConsumeRandomLengthString();

int idx = fdp.ConsumeIntegralInRange<int>(
0, (sizeof(jq_progs) / sizeof(char *)) - 1);

jq_state *jq = NULL;
jq = jq_init();
if (jq != NULL) {
jq_set_attr(jq, jv_string("JQ_ORIGIN"), jv_string("/tmp/"));

if (jq_compile(jq, jq_progs[idx])) {
// Process to jv_parse and then jv_next
jv input = jv_parse(parse_payload1.c_str());
if (jv_is_valid(input)) {
jq_start(jq, input, 0);
jv next = jv_parse(parse_payload2.c_str());
if (jv_is_valid(next)) {
jv actual = jq_next(jq);
jv_free(actual);
}
jv_free(next);
} else {
// Only free if input is invalid as otherwise jq_teardown
// frees it.
jv_free(input);
}
}
}
jq_teardown(&jq);

return 0;
}