diff options
| -rw-r--r-- | tools/javafuzz/Android.mk | 25 | ||||
| -rw-r--r-- | tools/javafuzz/README.md | 63 | ||||
| -rw-r--r-- | tools/javafuzz/javafuzz.cc | 1072 |
3 files changed, 1160 insertions, 0 deletions
diff --git a/tools/javafuzz/Android.mk b/tools/javafuzz/Android.mk new file mode 100644 index 0000000000..63db57ab18 --- /dev/null +++ b/tools/javafuzz/Android.mk @@ -0,0 +1,25 @@ +# Copyright (C) 2016 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Java fuzzer tool. + +LOCAL_PATH:= $(call my-dir) + +include $(CLEAR_VARS) +LOCAL_CPP_EXTENSION := cc +LOCAL_SRC_FILES := javafuzz.cc +LOCAL_CFLAGS += -O0 -g -Wall +LOCAL_MODULE_HOST_OS := darwin linux windows +LOCAL_MODULE := javafuzz +include $(BUILD_HOST_EXECUTABLE) diff --git a/tools/javafuzz/README.md b/tools/javafuzz/README.md new file mode 100644 index 0000000000..ca8532ae72 --- /dev/null +++ b/tools/javafuzz/README.md @@ -0,0 +1,63 @@ +JavaFuzz +======== + +JavaFuzz is tool for generating random Java programs with the objective of +fuzz testing the ART infrastructure. Each randomly generated Java program +can be run under various modes of execution, such as using the interpreter, +using the optimizing compiler, using an external reference implementation, +or using various target architectures. Any difference between the outputs +(a divergence) may indicate a bug in one of the execution modes. + +JavaFuzz can be combined with dexfuzz to get multilayered fuzz testing. + +How to run JavaFuzz +=================== + + javafuzz [-s seed] [-d expr-depth] [-l stmt-length] + [-i if-nest] [-n loop-nest] + +where + + -s : defines a deterministic random seed + (randomized using time by default) + -d : defines a fuzzing depth for expressions + (higher values yield deeper expressions) + -l : defines a fuzzing length for statement lists + (higher values yield longer statement sequences) + -i : defines a fuzzing nest for if/switch statements + (higher values yield deeper nested conditionals) + -n : defines a fuzzing nest for for/while/do-while loops + (higher values yield deeper nested loops) + +The current version of JavaFuzz sends all output to stdout, and uses +a fixed testing class named Test. So a typical test run looks as follows. + + javafuzz > Test.java + jack -cp ${JACK_CLASSPATH} --output-dex . Test.java + art -classpath classes.dex Test + +Background +========== + +Although test suites are extremely useful to validate the correctness of a +system and to ensure that no regressions occur, any test suite is necessarily +finite in size and scope. Tests typically focus on validating particular +features by means of code sequences most programmers would expect. Regression +tests often use slightly less idiomatic code sequences, since they reflect +problems that were not anticipated originally, but occurred “in the field”. +Still, any test suite leaves the developer wondering whether undetected bugs +and flaws still linger in the system. + +Over the years, fuzz testing has gained popularity as a testing technique for +discovering such lingering bugs, including bugs that can bring down a system in +an unexpected way. Fuzzing refers to feeding a large amount of random data as +input to a system in an attempt to find bugs or make it crash. Mutation-based +fuzz testing is a special form of fuzzing that applies small random changes to +existing inputs in order to detect shortcomings in a system. Profile-guided or +coverage-guided fuzzing adds a direction to the way these random changes are +applied. Multilayer approaches generate random inputs that are subsequently +mutated at various stages of execution. + +The randomness of fuzz testing implies that the size and scope of testing is no +longer bounded. Every new run can potentially discover bugs and crashes that were +hereto undetected. diff --git a/tools/javafuzz/javafuzz.cc b/tools/javafuzz/javafuzz.cc new file mode 100644 index 0000000000..4e6e978043 --- /dev/null +++ b/tools/javafuzz/javafuzz.cc @@ -0,0 +1,1072 @@ +/* + * Copyright 2016, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <random> + +#include <inttypes.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <time.h> + +namespace { + +/* + * Java operators. + */ + +#define EMIT(x) fputs((x)[random0(sizeof(x)/sizeof(const char*))], out_); + +static constexpr const char* kIncDecOps[] = { "++", "--" }; +static constexpr const char* kIntUnaryOps[] = { "+", "-", "~" }; +static constexpr const char* kFpUnaryOps[] = { "+", "-" }; + +static constexpr const char* kBoolBinOps[] = { "&&", "||", "&", "|", "^" }; // few less common +static constexpr const char* kIntBinOps[] = { "+", "-", "*", "/", "%", + ">>", ">>>", "<<", "&", "|", "^" }; +static constexpr const char* kFpBinOps[] = { "+", "-", "*", "/" }; + +static constexpr const char* kBoolAssignOps[] = { "=", "&=" , "|=", "^=" }; // few less common +static constexpr const char* kIntAssignOps[] = { "=", "+=", "-=", "*=", "/=", "%=", + ">>=", ">>>=", "<<=", "&=", "|=", "^=" }; +static constexpr const char* kFpAssignOps[] = { "=", "+=", "-=", "*=", "/=" }; + +static constexpr const char* kBoolRelOps[] = { "==", "!=" }; +static constexpr const char* kRelOps[] = { "==", "!=", ">", ">=", "<", "<=" }; + +/* + * Version of JavaFuzz. Increase this each time changes are made to the program + * to preserve the property that a given version of JavaFuzz yields the same + * fuzzed Java program for a deterministic random seed. + */ +const char* VERSION = "1.0"; + +/** + * A class that generates a random Java program that compiles correctly. The program + * is generated using rules that generate various programming constructs. Each rule + * has a fixed probability to "fire". Running a generated program yields deterministic + * output, making it suited to test various modes of execution (e.g an interpreter vs. + * an compiler or two different run times) for divergences. + * + * TODO: Due to the original scope of this project, the generated Java program is heavy + * on loops, arrays, and basic operations; fuzzing other aspects of Java programs, + * like elaborate typing, class hierarchies, and interfaces is still TBD. + */ +class JavaFuzz { + public: + JavaFuzz(FILE* out, + uint32_t seed, + uint32_t expr_depth, + uint32_t stmt_length, + uint32_t if_nest, + uint32_t loop_nest) + : out_(out), + fuzz_random_engine_(seed), + fuzz_seed_(seed), + fuzz_expr_depth_(expr_depth), + fuzz_stmt_length_(stmt_length), + fuzz_if_nest_(if_nest), + fuzz_loop_nest_(loop_nest), + return_type_(randomType()), + array_type_(randomType()), + array_dim_(random1(3)), + array_size_(random1(10)), + indentation_(0), + expr_depth_(0), + stmt_length_(0), + if_nest_(0), + loop_nest_(0), + switch_nest_(0), + do_nest_(0), + boolean_local_(0), + int_local_(0), + long_local_(0), + float_local_(0), + double_local_(0) { } + + ~JavaFuzz() { } + + void emitProgram() { + emitHeader(); + emitTestClassWithMain(); + } + + private: + // + // Types. + // + + // Current type of each expression during generation. + enum Type { + kBoolean, + kInt, + kLong, + kFloat, + kDouble + }; + + // Test for an integral type. + static bool isInteger(Type tp) { + return tp == kInt || tp == kLong; + } + + // Test for a floating-point type. + static bool isFP(Type tp) { + return tp == kFloat || tp == kDouble; + } + + // Emit type. + void emitType(Type tp) const { + switch (tp) { + case kBoolean: fputs("boolean", out_); break; + case kInt: fputs("int", out_); break; + case kLong: fputs("long", out_); break; + case kFloat: fputs("float", out_); break; + case kDouble: fputs("double", out_); break; + } + } + + // Emit type class. + void emitTypeClass(Type tp) const { + switch (tp) { + case kBoolean: fputs("Boolean", out_); break; + case kInt: fputs("Integer", out_); break; + case kLong: fputs("Long", out_); break; + case kFloat: fputs("Float", out_); break; + case kDouble: fputs("Double", out_); break; + } + } + + // Return a random type. + Type randomType() { + switch (random1(5)) { + case 1: return kBoolean; + case 2: return kInt; + case 3: return kLong; + case 4: return kFloat; + default: return kDouble; + } + } + + // + // Expressions. + // + + // Emit an unary operator (same type in-out). + void emitUnaryOp(Type tp) { + if (tp == kBoolean) { + fputs("!", out_); + } else if (isInteger(tp)) { + EMIT(kIntUnaryOps); + } else { // isFP(tp) + EMIT(kFpUnaryOps); + } + } + + // Emit a pre/post-increment/decrement operator (same type in-out). + void emitIncDecOp(Type tp) { + if (tp == kBoolean) { + // Not applicable, just leave "as is". + } else { // isInteger(tp) || isFP(tp) + EMIT(kIncDecOps); + } + } + + // Emit a binary operator (same type in-out). + void emitBinaryOp(Type tp) { + if (tp == kBoolean) { + EMIT(kBoolBinOps); + } else if (isInteger(tp)) { + EMIT(kIntBinOps); + } else { // isFP(tp) + EMIT(kFpBinOps); + } + } + + // Emit an assignment operator (same type in-out). + void emitAssignmentOp(Type tp) { + if (tp == kBoolean) { + EMIT(kBoolAssignOps); + } else if (isInteger(tp)) { + EMIT(kIntAssignOps); + } else { // isFP(tp) + EMIT(kFpAssignOps); + } + } + + // Emit a relational operator (one type in, boolean out). + void emitRelationalOp(Type tp) { + if (tp == kBoolean) { + EMIT(kBoolRelOps); + } else { // isInteger(tp) || isFP(tp) + EMIT(kRelOps); + } + } + + // Emit a type conversion operator sequence (out type given, new suitable in type picked). + Type emitTypeConversionOp(Type tp) { + if (tp == kInt) { + switch (random1(5)) { + case 1: fputs("(int)", out_); return kLong; + case 2: fputs("(int)", out_); return kFloat; + case 3: fputs("(int)", out_); return kDouble; + // Narrowing-widening. + case 4: fputs("(int)(byte)(int)", out_); return kInt; + case 5: fputs("(int)(short)(int)", out_); return kInt; + } + } else if (tp == kLong) { + switch (random1(6)) { + case 1: /* implicit */ return kInt; + case 2: fputs("(long)", out_); return kFloat; + case 3: fputs("(long)", out_); return kDouble; + // Narrowing-widening. + case 4: fputs("(long)(byte)(long)", out_); return kLong; + case 5: fputs("(long)(short)(long)", out_); return kLong; + case 6: fputs("(long)(int)(long)", out_); return kLong; + } + } else if (tp == kFloat) { + switch (random1(3)) { + case 1: fputs("(float)", out_); return kInt; + case 2: fputs("(float)", out_); return kLong; + case 3: fputs("(float)", out_); return kDouble; + } + } else if (tp == kDouble) { + switch (random1(3)) { + case 1: fputs("(double)", out_); return kInt; + case 2: fputs("(double)", out_); return kLong; + case 3: fputs("(double)", out_); return kFloat; + } + } + return tp; // nothing suitable, just keep type + } + + // Emit a type conversion (out type given, new suitable in type picked). + void emitTypeConversion(Type tp) { + if (tp == kBoolean) { + Type tp = randomType(); + emitExpression(tp); + fputc(' ', out_); + emitRelationalOp(tp); + fputc(' ', out_); + emitExpression(tp); + } else { + tp = emitTypeConversionOp(tp); + fputc(' ', out_); + emitExpression(tp); + } + } + + // Emit an unary intrinsic (out type given, new suitable in type picked). + Type emitIntrinsic1(Type tp) { + if (tp == kBoolean) { + switch (random1(4)) { + case 1: fputs("Float.isNaN", out_); return kFloat; + case 2: fputs("Float.isInfinite", out_); return kFloat; + case 3: fputs("Double.isNaN", out_); return kDouble; + case 4: fputs("Double.isInfinite", out_); return kDouble; + } + } else if (isInteger(tp)) { + const char* prefix = tp == kLong ? "Long" : "Integer"; + switch (random1(9)) { + case 1: fprintf(out_, "%s.highestOneBit", prefix); break; + case 2: fprintf(out_, "%s.lowestOneBit", prefix); break; + case 3: fprintf(out_, "%s.numberOfLeadingZeros", prefix); break; + case 4: fprintf(out_, "%s.numberOfTrailingZeros", prefix); break; + case 5: fprintf(out_, "%s.bitCount", prefix); break; + case 6: fprintf(out_, "%s.signum", prefix); break; + case 7: fprintf(out_, "%s.reverse", prefix); break; + case 8: fprintf(out_, "%s.reverseBytes", prefix); break; + case 9: fputs("Math.abs", out_); break; + } + } else { // isFP(tp) + switch (random1(5)) { + case 1: fputs("Math.abs", out_); break; + case 2: fputs("Math.ulp", out_); break; + case 3: fputs("Math.signum", out_); break; + case 4: fputs("Math.nextUp", out_); break; + case 5: fputs("Math.nextDown", out_); break; + } + } + return tp; // same type in-out + } + + // Emit a binary intrinsic (out type given, new suitable in type picked). + Type emitIntrinsic2(Type tp) { + if (tp == kBoolean) { + switch (random1(3)) { + case 1: fputs("Boolean.logicalAnd", out_); break; + case 2: fputs("Boolean.logicalOr", out_); break; + case 3: fputs("Boolean.logicalXor", out_); break; + } + } else if (isInteger(tp)) { + const char* prefix = tp == kLong ? "Long" : "Integer"; + switch (random1(3)) { + case 1: fprintf(out_, "%s.compare", prefix); break; + case 2: fputs("Math.min", out_); break; + case 3: fputs("Math.max", out_); break; + } + } else { // isFP(tp) + switch (random1(2)) { + case 1: fputs("Math.min", out_); break; + case 2: fputs("Math.max", out_); break; + } + } + return tp; // same type in-out + } + + // Emit an intrinsic (out type given, new suitable in type picked). + void emitIntrinsic(Type tp) { + if (random1(2) == 1) { + tp = emitIntrinsic1(tp); + fputc('(', out_); + emitExpression(tp); + fputc(')', out_); + } else { + tp = emitIntrinsic2(tp); + fputc('(', out_); + emitExpression(tp); + fputs(", ", out_); + emitExpression(tp); + fputc(')', out_); + } + } + + // Emit unboxing boxed object. + void emitUnbox(Type tp) { + fputc('(', out_); + emitType(tp); + fputs(") new ", out_); + emitTypeClass(tp); + fputc('(', out_); + emitExpression(tp); + fputc(')', out_); + } + + // Emit miscellaneous constructs. + void emitMisc(Type tp) { + switch (tp) { + case kBoolean: fputs("this instanceof Test", out_); break; + case kInt: fputs("mArray.length", out_); break; + case kLong: fputs("Long.MAX_VALUE", out_); break; + case kFloat: fputs("Float.MAX_VALUE", out_); break; + case kDouble: fputs("Double.MAX_VALUE", out_); break; + } + } + + // Adjust local of given type and return adjusted value. + uint32_t adjustLocal(Type tp, int32_t a) { + switch (tp) { + case kBoolean: boolean_local_ += a; return boolean_local_; + case kInt: int_local_ += a; return int_local_; + case kLong: long_local_ += a; return long_local_; + case kFloat: float_local_ += a; return float_local_; + default: double_local_ += a; return double_local_; + } + } + + // Emit an expression that is a strict upper bound for an array index. + void emitUpperBound() { + if (random1(8) == 1) { + fputs("mArray.length", out_); + } else if (random1(8) == 1) { + fprintf(out_, "%u", random1(array_size_)); // random in range + } else { + fprintf(out_, "%u", array_size_); + } + } + + // Emit an array index, usually within proper range. + void emitArrayIndex() { + if (loop_nest_ > 0 && random1(2) == 1) { + fprintf(out_, "i%u", random0(loop_nest_)); + } else if (random1(8) == 1) { + fputs("mArray.length - 1", out_); + } else { + fprintf(out_, "%u", random0(array_size_)); // random in range + } + // Introduce potential off by one errors with low probability. + if (random1(100) == 1) { + if (random1(2) == 1) { + fputs(" - 1", out_); + } else { + fputs(" + 1", out_); + } + } + } + + // Emit a literal. + void emitLiteral(Type tp) { + switch (tp) { + case kBoolean: fputs(random1(2) == 1 ? "true" : "false", out_); break; + case kInt: fprintf(out_, "%d", random0(100)); break; + case kLong: fprintf(out_, "%dL", random0(100)); break; + case kFloat: fprintf(out_, "%d.0f", random0(100)); break; + case kDouble: fprintf(out_, "%d.0", random0(100)); break; + } + } + + // Emit array variable, if available. + bool emitArrayVariable(Type tp) { + if (tp == array_type_) { + fputs("mArray", out_); + for (uint32_t i = 0; i < array_dim_; i++) { + fputc('[', out_); + emitArrayIndex(); + fputc(']', out_); + } + return true; + } + return false; + } + + // Emit a loop variable, if available. + bool emitLoopVariable(Type tp) { + if (tp == kInt) { + if (loop_nest_ > 0) { + fprintf(out_, "i%u", random0(loop_nest_)); + return true; + } + } + return false; + } + + // Emit a local variable, if available. + bool emitLocalVariable(Type tp) { + uint32_t locals = adjustLocal(tp, 0); + if (locals > 0) { + uint32_t local = random0(locals); + switch (tp) { + case kBoolean: fprintf(out_, "lZ%u", local); break; + case kInt: fprintf(out_, "lI%u", local); break; + case kLong: fprintf(out_, "lJ%u", local); break; + case kFloat: fprintf(out_, "lF%u", local); break; + case kDouble: fprintf(out_, "lD%u", local); break; + } + return true; + } + return false; + } + + // Emit a field variable. + void emitFieldVariable(Type tp) { + switch (tp) { + case kBoolean:fputs("mZ", out_); break; + case kInt: fputs("mI", out_); break; + case kLong: fputs("mJ", out_); break; + case kFloat: fputs("mF", out_); break; + case kDouble: fputs("mD", out_); break; + } + } + + // Emit a variable. + void emitVariable(Type tp) { + switch (random1(4)) { + case 1: + if (emitArrayVariable(tp)) + return; + // FALL-THROUGH + case 2: + if (emitLocalVariable(tp)) + return; + // FALL-THROUGH + case 3: + if (emitLoopVariable(tp)) + return; + // FALL-THROUGH + default: + emitFieldVariable(tp); + break; + } + } + + // Emit an expression. + void emitExpression(Type tp) { + // Continuing expression becomes less likely as the depth grows. + if (random1(expr_depth_ + 1) > fuzz_expr_depth_) { + if (random1(2) == 1) { + emitLiteral(tp); + } else { + emitVariable(tp); + } + return; + } + + expr_depth_++; + + fputc('(', out_); + switch (random1(12)) { // favor binary operations + case 1: + // Unary operator: ~x + emitUnaryOp(tp); + emitExpression(tp); + break; + case 2: + // Pre-increment: ++x + emitIncDecOp(tp); + emitVariable(tp); + break; + case 3: + // Post-increment: x++ + emitVariable(tp); + emitIncDecOp(tp); + break; + case 4: + // Ternary operator: b ? x : y + emitExpression(kBoolean); + fputs(" ? ", out_); + emitExpression(tp); + fputs(" : ", out_); + emitExpression(tp); + break; + case 5: + // Type conversion: (float) x + emitTypeConversion(tp); + break; + case 6: + // Intrinsic: foo(x) + emitIntrinsic(tp); + break; + case 7: + // Emit unboxing boxed value: (int) Integer(x) + emitUnbox(tp); + break; + case 8: + // Miscellaneous constructs: a.length + emitMisc(tp); + break; + default: + // Binary operator: x + y + emitExpression(tp); + fputc(' ', out_); + emitBinaryOp(tp); + fputc(' ', out_); + emitExpression(tp); + break; + } + fputc(')', out_); + + --expr_depth_; + } + + // + // Statements. + // + + // Emit current indentation. + void emitIndentation() const { + for (uint32_t i = 0; i < indentation_; i++) { + fputc(' ', out_); + } + } + + // Emit a return statement. + bool emitReturn(bool mustEmit) { + // Only emit when we must, or with low probability inside ifs/loops, + // but outside do-while to avoid confusing the may follow status. + if (mustEmit || ((if_nest_ + loop_nest_) > 0 && do_nest_ == 0 && random1(10) == 1)) { + fputs("return ", out_); + emitExpression(return_type_); + fputs(";\n", out_); + return false; + } + // Fall back to assignment. + return emitAssignment(); + } + + // Emit a continue statement. + bool emitContinue() { + // Only emit with low probability inside loops. + if (loop_nest_ > 0 && random1(10) == 1) { + fputs("continue;\n", out_); + return false; + } + // Fall back to assignment. + return emitAssignment(); + } + + // Emit a break statement. + bool emitBreak() { + // Only emit with low probability inside loops, but outside switches + // to avoid confusing the may follow status. + if (loop_nest_ > 0 && switch_nest_ == 0 && random1(10) == 1) { + fputs("break;\n", out_); + return false; + } + // Fall back to assignment. + return emitAssignment(); + } + + // Emit a new scope with a local variable declaration statement. + bool emitScope() { + Type tp = randomType(); + fputs("{\n", out_); + indentation_ += 2; + emitIndentation(); + emitType(tp); + switch (tp) { + case kBoolean: fprintf(out_, " lZ%u = ", boolean_local_); break; + case kInt: fprintf(out_, " lI%u = ", int_local_); break; + case kLong: fprintf(out_, " lJ%u = ", long_local_); break; + case kFloat: fprintf(out_, " lF%u = ", float_local_); break; + case kDouble: fprintf(out_, " lD%u = ", double_local_); break; + } + emitExpression(tp); + fputs(";\n", out_); + + adjustLocal(tp, 1); // local now visible + + bool mayFollow = emitStatementList(); + + adjustLocal(tp, -1); // local no longer visible + + indentation_ -= 2; + emitIndentation(); + fputs("}\n", out_); + return mayFollow; + } + + // Emit a for loop. + bool emitForLoop() { + // Continuing loop nest becomes less likely as the depth grows. + if (random1(loop_nest_ + 1) > fuzz_loop_nest_) { + return emitAssignment(); // fall back + } + + bool goesUp = random1(2) == 1; + fprintf(out_, "for (int i%u = ", loop_nest_); + if (goesUp) { + fprintf(out_, "0; i%u < ", loop_nest_); + emitUpperBound(); + fprintf(out_, "; i%u++) {\n", loop_nest_); + } else { + emitUpperBound(); + fprintf(out_, " - 1; i%d >= 0", loop_nest_); + fprintf(out_, "; i%d--) {\n", loop_nest_); + } + + ++loop_nest_; // now in loop + + indentation_ += 2; + emitStatementList(); + + --loop_nest_; // no longer in loop + + indentation_ -= 2; + emitIndentation(); + fprintf(out_, "}\n"); + return true; // loop-body does not block flow + } + + // Emit while or do-while loop. + bool emitDoLoop() { + // Continuing loop nest becomes less likely as the depth grows. + if (random1(loop_nest_ + 1) > fuzz_loop_nest_) { + return emitAssignment(); // fall back + } + + // TODO: remove this + // The jack bug b/28862040 prevents generating while/do-while loops because otherwise + // we get dozens of reports on the same issue per nightly/ run. + if (true) { + return emitAssignment(); + } + + bool isWhile = random1(2) == 1; + fputs("{\n", out_); + indentation_ += 2; + emitIndentation(); + fprintf(out_, "int i%u = %d;", loop_nest_, isWhile ? -1 : 0); + emitIndentation(); + if (isWhile) { + fprintf(out_, "while (++i%u < ", loop_nest_); + emitUpperBound(); + fputs(") {\n", out_); + } else { + fputs("do {\n", out_); + do_nest_++; + } + + ++loop_nest_; // now in loop + + indentation_ += 2; + emitStatementList(); + + --loop_nest_; // no longer in loop + + indentation_ -= 2; + emitIndentation(); + if (isWhile) { + fputs("}\n", out_); + } else { + fprintf(out_, "} while (++i%u < ", loop_nest_); + emitUpperBound(); + fputs(");\n", out_); + --do_nest_; + } + indentation_ -= 2; + emitIndentation(); + fputs("}\n", out_); + return true; // loop-body does not block flow + } + + // Emit an if statement. + bool emitIfStmt() { + // Continuing if nest becomes less likely as the depth grows. + if (random1(if_nest_ + 1) > fuzz_if_nest_) { + return emitAssignment(); // fall back + } + + fputs("if (", out_); + emitExpression(kBoolean); + fputs(") {\n", out_); + + ++if_nest_; // now in if + + indentation_ += 2; + bool mayFollowTrue = emitStatementList(); + indentation_ -= 2; + emitIndentation(); + fprintf(out_, "} else {\n"); + indentation_ += 2; + bool mayFollowFalse = emitStatementList(); + + --if_nest_; // no longer in if + + indentation_ -= 2; + emitIndentation(); + fprintf(out_, "}\n"); + return mayFollowTrue || mayFollowFalse; + } + + // Emit a switch statement. + bool emitSwitch() { + // Continuing if nest becomes less likely as the depth grows. + if (random1(if_nest_ + 1) > fuzz_if_nest_) { + return emitAssignment(); // fall back + } + + bool mayFollow = false; + fputs("switch (", out_); + emitExpression(kInt); + fputs(") {\n", out_); + + ++if_nest_; + ++switch_nest_; // now in switch + + indentation_ += 2; + for (uint32_t i = 0; i < 2; i++) { + emitIndentation(); + if (i == 0) { + fprintf(out_, "case %d: {\n", random0(100)); + } else { + fprintf(out_, "default: {\n"); + } + indentation_ += 2; + if (emitStatementList()) { + // Must end with break. + emitIndentation(); + fputs("break;\n", out_); + mayFollow = true; + } + indentation_ -= 2; + emitIndentation(); + fputs("}\n", out_); + } + + --if_nest_; + --switch_nest_; // no longer in switch + + indentation_ -= 2; + emitIndentation(); + fprintf(out_, "}\n"); + return mayFollow; + } + + // Emit an assignment statement. + bool emitAssignment() { + Type tp = randomType(); + emitVariable(tp); + fputc(' ', out_); + emitAssignmentOp(tp); + fputc(' ', out_); + emitExpression(tp); + fputs(";\n", out_); + return true; + } + + // Emit a single statement. Returns true if statements may follow. + bool emitStatement() { + switch (random1(16)) { // favor assignments + case 1: return emitReturn(false); break; + case 2: return emitContinue(); break; + case 3: return emitBreak(); break; + case 4: return emitScope(); break; + case 5: return emitForLoop(); break; + case 6: return emitDoLoop(); break; + case 7: return emitIfStmt(); break; + case 8: return emitSwitch(); break; + default: return emitAssignment(); break; + } + } + + // Emit a statement list. Returns true if statements may follow. + bool emitStatementList() { + while (stmt_length_ < 1000) { // avoid run-away + stmt_length_++; + emitIndentation(); + if (!emitStatement()) { + return false; // rest would be dead code + } + // Continuing this list becomes less likely as the total statement list grows. + if (random1(stmt_length_) > fuzz_stmt_length_) { + break; + } + } + return true; + } + + // Emit field declarations. + void emitFieldDecls() { + fputs(" private boolean mZ = false;\n", out_); + fputs(" private int mI = 0;\n", out_); + fputs(" private long mJ = 0;\n", out_); + fputs(" private float mF = 0;\n", out_); + fputs(" private double mD = 0;\n\n", out_); + } + + // Emit array declaration. + void emitArrayDecl() { + fputs(" private ", out_); + emitType(array_type_); + for (uint32_t i = 0; i < array_dim_; i++) { + fputs("[]", out_); + } + fputs(" mArray = new ", out_); + emitType(array_type_); + for (uint32_t i = 0; i < array_dim_; i++) { + fprintf(out_, "[%d]", array_size_); + } + fputs(";\n\n", out_); + } + + // Emit test constructor. + void emitTestConstructor() { + fputs(" private Test() {\n", out_); + indentation_ += 2; + emitIndentation(); + emitType(array_type_); + fputs(" a = ", out_); + emitLiteral(array_type_); + fputs(";\n", out_); + for (uint32_t i = 0; i < array_dim_; i++) { + emitIndentation(); + fprintf(out_, "for (int i%u = 0; i%u < %u; i%u++) {\n", i, i, array_size_, i); + indentation_ += 2; + } + emitIndentation(); + fputs("mArray", out_); + for (uint32_t i = 0; i < array_dim_; i++) { + fprintf(out_, "[i%u]", i); + } + fputs(" = a;\n", out_); + emitIndentation(); + if (array_type_ == kBoolean) { + fputs("a = !a;\n", out_); + } else { + fputs("a++;\n", out_); + } + for (uint32_t i = 0; i < array_dim_; i++) { + indentation_ -= 2; + emitIndentation(); + fputs("}\n", out_); + } + indentation_ -= 2; + fputs(" }\n\n", out_); + } + + // Emit test method. + void emitTestMethod() { + fputs(" private ", out_); + emitType(return_type_); + fputs(" testMethod() {\n", out_); + indentation_ += 2; + if (emitStatementList()) { + // Must end with return. + emitIndentation(); + emitReturn(true); + } + indentation_ -= 2; + fputs(" }\n\n", out_); + } + + // Emit main method driver. + void emitMainMethod() { + fputs(" public static void main(String[] args) {\n", out_); + indentation_ += 2; + fputs(" Test t = new Test();\n ", out_); + emitType(return_type_); + fputs(" r = ", out_); + emitLiteral(return_type_); + fputs(";\n", out_); + fputs(" try {\n", out_); + fputs(" r = t.testMethod();\n", out_); + fputs(" } catch (Exception e) {\n", out_); + fputs(" // Arithmetic, null pointer, index out of bounds, etc.\n", out_); + fputs(" System.out.println(\"An exception was caught.\");\n", out_); + fputs(" }\n", out_); + fputs(" System.out.println(\"r = \" + r);\n", out_); + fputs(" System.out.println(\"mZ = \" + t.mZ);\n", out_); + fputs(" System.out.println(\"mI = \" + t.mI);\n", out_); + fputs(" System.out.println(\"mJ = \" + t.mJ);\n", out_); + fputs(" System.out.println(\"mF = \" + t.mF);\n", out_); + fputs(" System.out.println(\"mD = \" + t.mD);\n", out_); + fputs(" System.out.println(\"mArray = \" + ", out_); + if (array_dim_ == 1) { + fputs("Arrays.toString(t.mArray)", out_); + } else { + fputs("Arrays.deepToString(t.mArray)", out_); + } + fputs(");\n", out_); + indentation_ -= 2; + fputs(" }\n", out_); + } + + // Emit program header. Emit command line options in the comments. + void emitHeader() { + fputs("\n/**\n * AOSP Java Fuzz Tester.\n", out_); + fputs(" * Automatically generated Java program.\n", out_); + fprintf(out_, + " * javafuzz -s %u -d %u -l %u -i %u -n %u (version %s)\n */\n\n", + fuzz_seed_, + fuzz_expr_depth_, + fuzz_stmt_length_, + fuzz_if_nest_, + fuzz_loop_nest_, + VERSION); + fputs("import java.util.Arrays;\n\n", out_); + } + + // Emit single test class with main driver. + void emitTestClassWithMain() { + fputs("public class Test {\n\n", out_); + indentation_ += 2; + emitFieldDecls(); + emitArrayDecl(); + emitTestConstructor(); + emitTestMethod(); + emitMainMethod(); + indentation_ -= 2; + fputs("}\n\n", out_); + } + + // + // Random integers. + // + + // Return random integer in range [0,max). + uint32_t random0(uint32_t max) { + std::uniform_int_distribution<uint32_t> gen(0, max - 1); + return gen(fuzz_random_engine_); + } + + // Return random integer in range [1,max]. + uint32_t random1(uint32_t max) { + std::uniform_int_distribution<uint32_t> gen(1, max); + return gen(fuzz_random_engine_); + } + + // Fuzzing parameters. + FILE* out_; + std::mt19937 fuzz_random_engine_; + const uint32_t fuzz_seed_; + const uint32_t fuzz_expr_depth_; + const uint32_t fuzz_stmt_length_; + const uint32_t fuzz_if_nest_; + const uint32_t fuzz_loop_nest_; + + // Return and array setup. + const Type return_type_; + const Type array_type_; + const uint32_t array_dim_; + const uint32_t array_size_; + + // Current context. + uint32_t indentation_; + uint32_t expr_depth_; + uint32_t stmt_length_; + uint32_t if_nest_; + uint32_t loop_nest_; + uint32_t switch_nest_; + uint32_t do_nest_; + uint32_t boolean_local_; + uint32_t int_local_; + uint32_t long_local_; + uint32_t float_local_; + uint32_t double_local_; +}; + +} // anonymous namespace + +int32_t main(int32_t argc, char** argv) { + // Defaults. + uint32_t seed = time(NULL); + uint32_t expr_depth = 1; + uint32_t stmt_length = 4; + uint32_t if_nest = 2; + uint32_t loop_nest = 3; + + // Parse options. + while (1) { + int32_t option = getopt(argc, argv, "s:d:l:i:n:h"); + if (option < 0) { + break; // done + } + switch (option) { + case 's': + seed = strtoul(optarg, nullptr, 0); // deterministic seed + break; + case 'd': + expr_depth = strtoul(optarg, nullptr, 0); + break; + case 'l': + stmt_length = strtoul(optarg, nullptr, 0); + break; + case 'i': + if_nest = strtoul(optarg, nullptr, 0); + break; + case 'n': + loop_nest = strtoul(optarg, nullptr, 0); + break; + case 'h': + default: + fprintf(stderr, + "usage: %s [-s seed] " + "[-d expr-depth] [-l stmt-length] " + "[-i if-nest] [-n loop-nest] [-h]\n", + argv[0]); + return 1; + } + } + + // Seed global random generator. + srand(seed); + + // Generate fuzzed Java program. + JavaFuzz fuzz(stdout, seed, expr_depth, stmt_length, if_nest, loop_nest); + fuzz.emitProgram(); + return 0; +} |