|
1 | 1 | using JuliaSyntax
|
2 | 2 | using JuliaSyntax: tokenize
|
| 3 | +import Logging |
| 4 | +import Test |
3 | 5 |
|
4 | 6 | # Parser fuzz testing tools.
|
5 | 7 |
|
@@ -758,6 +760,7 @@ const cutdown_tokens = [
|
758 | 760 | "\t"
|
759 | 761 | "\n"
|
760 | 762 | "x"
|
| 763 | + "β" |
761 | 764 | "@"
|
762 | 765 | ","
|
763 | 766 | ";"
|
@@ -884,33 +887,36 @@ const cutdown_tokens = [
|
884 | 887 | ]
|
885 | 888 |
|
886 | 889 | #-------------------------------------------------------------------------------
|
887 |
| - |
888 |
| -# The parser should never throw an exception. To test whether this is true, |
889 |
| -# try passing randomly generated bad input data into it. |
890 |
| -function _fuzz_test(bad_input_iter) |
891 |
| - error_strings = [] |
892 |
| - for str in bad_input_iter |
893 |
| - try |
894 |
| - JuliaSyntax.parseall(JuliaSyntax.SyntaxNode, str, ignore_errors=true); |
895 |
| - catch exc |
896 |
| - !(exc isa InterruptException) || rethrow() |
897 |
| - rstr = reduce_text(str, parser_throws_exception) |
898 |
| - @error "Parser threw exception" rstr exception=current_exceptions() |
899 |
| - push!(error_strings, rstr) |
900 |
| - end |
| 890 | +# Parsing functions for use with fuzz_test |
| 891 | + |
| 892 | +function try_parseall_failure(str) |
| 893 | + try |
| 894 | + JuliaSyntax.parseall(JuliaSyntax.SyntaxNode, str, ignore_errors=true); |
| 895 | + return nothing |
| 896 | + catch exc |
| 897 | + !(exc isa InterruptException) || rethrow() |
| 898 | + rstr = reduce_text(str, parser_throws_exception) |
| 899 | + @error "Parser threw exception" rstr exception=current_exceptions() |
| 900 | + return rstr |
901 | 901 | end
|
902 |
| - return error_strings |
903 | 902 | end
|
904 | 903 |
|
905 |
| -""" |
906 |
| -Fuzz test parser against all tuples of length `N` with elements taken from |
907 |
| -`tokens`. |
908 |
| -""" |
909 |
| -function fuzz_tokens(tokens, N) |
910 |
| - iter = (join(ts) for ts in Iterators.product([tokens for _ in 1:N]...)) |
911 |
| - _fuzz_test(iter) |
| 904 | +function try_hook_failure(str) |
| 905 | + try |
| 906 | + test_logger = Test.TestLogger() |
| 907 | + Logging.with_logger(test_logger) do |
| 908 | + Meta_parseall(str) |
| 909 | + end |
| 910 | + if !isempty(test_logger.logs) |
| 911 | + return str |
| 912 | + end |
| 913 | + catch exc |
| 914 | + return str |
| 915 | + end |
| 916 | + return nothing |
912 | 917 | end
|
913 | 918 |
|
| 919 | +#------------------------------------------------------------------------------- |
914 | 920 | """Delete `nlines` adjacent lines from code, at `niters` randomly chosen positions"""
|
915 | 921 | function delete_lines(lines, nlines, niters)
|
916 | 922 | selection = trues(length(lines))
|
@@ -953,29 +959,59 @@ function delete_tokens(code, tokens, ntokens, niters)
|
953 | 959 | end
|
954 | 960 |
|
955 | 961 | #-------------------------------------------------------------------------------
|
956 |
| -# Fuzzer functions |
| 962 | +# Generators for "potentially bad input" |
| 963 | + |
| 964 | +""" |
| 965 | +Fuzz test parser against all tuples of length `N` with elements taken from |
| 966 | +`tokens`. |
| 967 | +""" |
| 968 | +function product_token_fuzz(tokens, N) |
| 969 | + (join(ts) for ts in Iterators.product([tokens for _ in 1:N]...)) |
| 970 | +end |
957 | 971 |
|
958 | 972 | """
|
959 | 973 | Fuzz test parser against randomly generated binary strings
|
960 | 974 | """
|
961 |
| -function fuzz_binary(nbytes, N) |
962 |
| - bad_strs = _fuzz_test(String(rand(UInt8, nbytes)) for _ in 1:N) |
963 |
| - reduce_text.(bad_strs, parser_throws_exception) |
| 975 | +function random_binary_fuzz(nbytes, N) |
| 976 | + (String(rand(UInt8, nbytes)) for _ in 1:N) |
964 | 977 | end
|
965 | 978 |
|
966 | 979 | """
|
967 | 980 | Fuzz test by deleting random lines of some given source `code`
|
968 | 981 | """
|
969 |
| -function fuzz_lines(code, N; nlines=10, niters=10) |
| 982 | +function deleted_line_fuzz(code, N; nlines=10, niters=10) |
970 | 983 | lines = split(code, '\n')
|
971 |
| - _fuzz_test(delete_lines(lines, nlines, niters) for _=1:N) |
| 984 | + (delete_lines(lines, nlines, niters) for _=1:N) |
972 | 985 | end
|
973 | 986 |
|
974 | 987 | """
|
975 | 988 | Fuzz test by deleting random tokens from given source `code`
|
976 | 989 | """
|
977 |
| -function fuzz_tokens(code, N; ntokens=10, niters=10) |
| 990 | +function deleted_token_fuzz(code, N; ntokens=10, niters=10) |
978 | 991 | ts = tokenize(code)
|
979 |
| - _fuzz_test(delete_tokens(code, ts, ntokens, niters) for _=1:N) |
| 992 | + (delete_tokens(code, ts, ntokens, niters) for _=1:N) |
980 | 993 | end
|
981 | 994 |
|
| 995 | +""" |
| 996 | +Fuzz test a parsing function by trying it with many "bad" input strings. |
| 997 | +
|
| 998 | +`try_parsefail` should return `nothing` when the parser succeeds, and return a |
| 999 | +string (or reduced string) when parsing succeeds. |
| 1000 | +""" |
| 1001 | +function fuzz_test(try_parsefail::Function, bad_input_iter) |
| 1002 | + error_strings = [] |
| 1003 | + for str in bad_input_iter |
| 1004 | + res = try_parsefail(str) |
| 1005 | + if !isnothing(res) |
| 1006 | + push!(error_strings, res) |
| 1007 | + end |
| 1008 | + end |
| 1009 | + return error_strings |
| 1010 | +end |
| 1011 | + |
| 1012 | + |
| 1013 | +# Examples |
| 1014 | +# |
| 1015 | +# fuzz_test(try_hook_failure, product_token_fuzz(cutdown_tokens, 2)) |
| 1016 | +# fuzz_test(try_parseall_failure, product_token_fuzz(cutdown_tokens, 2)) |
| 1017 | + |
0 commit comments