Packages

Pure Gleam implementation of the Apache Thrift Compact Protocol

Current section

Files

Jump to
thrifty src thrifty@fuzz_cli.erl
Raw

src/thrifty@fuzz_cli.erl

-module(thrifty@fuzz_cli).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/thrifty/fuzz_cli.gleam").
-export([main/0]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
-file("src/thrifty/fuzz_cli.gleam", 155).
?DOC(" Attempt to save a failing payload and a small metadata file.\n").
-spec maybe_save_failure(
boolean(),
binary(),
integer(),
integer(),
bitstring(),
thrifty@types:decode_error()
) -> {ok, nil} | {error, binary()}.
maybe_save_failure(Enabled, Dir, Seed, Iter, Data, Reason) ->
case Enabled of
false ->
{ok, nil};
true ->
thrifty@fuzz_persistence:persist_failure(
Dir,
Seed,
Iter,
Data,
Reason
)
end.
-file("src/thrifty/fuzz_cli.gleam", 179).
?DOC(
" Core fuzz loop that mutates a single byte and invokes the reader.\n"
"\n"
" Notes\n"
" - This harness is purposely conservative: it uses `with_options` to limit\n"
" resources and discards reader results. Extend to persist failing inputs\n"
" when required.\n"
).
-spec normalize_mod(integer(), integer()) -> integer().
normalize_mod(Value, Modulus) ->
Rem = case Modulus of
0 -> 0;
Gleam@denominator -> Value rem Gleam@denominator
end,
case Rem < 0 of
true ->
Rem + Modulus;
false ->
Rem
end.
-file("src/thrifty/fuzz_cli.gleam", 113).
?DOC(
" Run deterministic single-byte mutation iterations for a single seed.\n"
"\n"
" Inputs\n"
" - `data`: original payload.\n"
" - `size`: payload byte size.\n"
" - `seed`: deterministic seed controlling mutation positions/values.\n"
" - `iterations`: number of mutations to generate for this seed.\n"
).
-spec fuzz_loop(bitstring(), integer(), integer(), integer(), integer()) -> nil.
fuzz_loop(Data, Size, Seed, Index, Remaining) ->
case Remaining =:= 0 of
true ->
nil;
false ->
Position = normalize_mod((Seed * 31) + (Index * 17), Size),
Value = normalize_mod((Seed * 97) + (Index * 101), 256),
Mutated = thrifty@fuzz_utils:mutate_byte(Data, Position, Value),
Reader = thrifty@reader:with_options(
Mutated,
{reader_options, 32, 4096, 4194304, accept_canonical_only}
),
case thrifty@reader:read_struct(Reader) of
{ok, _} ->
nil;
{error, Err} ->
_ = maybe_save_failure(
false,
<<"artifact/fuzz-failures"/utf8>>,
Seed,
Index,
Mutated,
Err
),
nil
end,
fuzz_loop(Data, Size, Seed, Index + 1, Remaining - 1)
end.
-file("src/thrifty/fuzz_cli.gleam", 102).
?DOC(
" Prepare fuzzing state for a given payload and spawn seed-based iterations.\n"
"\n"
" Inputs\n"
" - `file`: path to the golden file (used only for logging).\n"
" - `data`: payload bytes to mutate.\n"
"\n"
" Behavior\n"
" - Skips empty payloads. Logs size and iterates `seeds` invoking `fuzz_with_seed`.\n"
).
-spec fuzz_with_seed(bitstring(), integer(), integer(), integer()) -> nil.
fuzz_with_seed(Data, Size, Seed, Iterations) ->
fuzz_loop(Data, Size, Seed, 0, Iterations).
-file("src/thrifty/fuzz_cli.gleam", 81).
?DOC(
" Read a golden file and trigger fuzzing of its payloads.\n"
"\n"
" Inputs\n"
" - `file`: path to a golden binary file.\n"
"\n"
" Behavior\n"
" - On read error prints a message and continues. On success delegates to\n"
" `fuzz_payloads/2` to run deterministic mutations.\n"
).
-spec fuzz_payloads(binary(), bitstring()) -> nil.
fuzz_payloads(File, Data) ->
Size = erlang:byte_size(Data),
case Size =:= 0 of
true ->
gleam_stdlib:println(
<<"Skipping empty payload: "/utf8, File/binary>>
);
false ->
gleam_stdlib:println(
<<<<<<<<"Fuzzing "/utf8, File/binary>>/binary, " ("/utf8>>/binary,
(erlang:integer_to_binary(Size))/binary>>/binary,
" bytes)"/utf8>>
),
gleam@list:each(
[13, 29, 41, 53, 67, 79, 97, 109],
fun(Seed) -> fuzz_with_seed(Data, Size, Seed, 200) end
)
end.
-file("src/thrifty/fuzz_cli.gleam", 66).
-spec fuzz_file(binary()) -> nil.
fuzz_file(File) ->
case thrifty@file_io:read_binary(File) of
{error, Error} ->
gleam_stdlib:println(
<<<<<<"Skipping "/utf8, File/binary>>/binary, ": "/utf8>>/binary,
Error/binary>>
);
{ok, Data} ->
fuzz_payloads(File, Data)
end.
-file("src/thrifty/fuzz_cli.gleam", 60).
?DOC(
" Run a short deterministic fuzz run over the configured golden payloads.\n"
"\n"
" Purpose\n"
" - Convenience entrypoint for manual or CI smoke tests that exercise the\n"
" reader against small, mutated variants of canonical golden payloads.\n"
"\n"
" Behavior\n"
" - Iterates the predefined `files` list, reads each binary, and applies a set\n"
" of deterministic single-byte mutations controlled by `seeds` and\n"
" `iterations_per_seed`.\n"
" - Each mutated payload is passed to the reader created with conservative\n"
" `ReaderOptions` to verify the reader does not crash on malformed inputs.\n"
"\n"
" Outputs\n"
" - Prints progress to stdout. This function is intended as a smoke-test only\n"
" and does not return structured results; callers should inspect logs or\n"
" extend the harness to persist failing cases.\n"
).
-spec main() -> nil.
main() ->
gleam_stdlib:println(<<"Starting fuzz run over golden payloads"/utf8>>),
gleam@list:each(
[<<"artifact/golden/user_profile.bin"/utf8>>,
<<"artifact/golden/ping_message.bin"/utf8>>,
<<"artifact/golden/complex_struct.bin"/utf8>>,
<<"artifact/golden/bool_list.bin"/utf8>>],
fun fuzz_file/1
),
gleam_stdlib:println(<<"Fuzz run completed without crashes"/utf8>>).