blob: fb96a11261c69cf43518811a53a17dc4c3b59b30 [file] [log] [blame]
#!/usr/bin/env python3
#
# Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file
# for details. All rights reserved. Use of this source code is governed by a
# BSD-style license that can be found in the LICENSE file.
import argparse
import random
import time
from enum import IntEnum
from enum import unique
# Version of DartFuzz. Increase this each time changes are made
# to preserve the property that a given version of DartFuzz yields
# the same fuzzed program for a deterministic random seed.
VERSION='1.0'
#
# Dart operators and types.
#
NUM_UNARY_OPS = [ '-' ]
INT_UNARY_OPS = NUM_UNARY_OPS + [ '~' ]
BOOL_BIN_OPS = [ ' && ', ' || ' ]
NUM_BIN_OPS = [ ' + ', ' - ', ' * ' ]
INT_BIN_OPS = NUM_BIN_OPS + [ ' & ', ' | ', ' ^ ', ' % ', ' ~/ ', ' >> ', ' << ' ]
FP_BIN_OPS = NUM_BIN_OPS + [ ' / ' ]
NUM_ASSIGN_OPS = [ ' = ', ' += ', ' -= ', ' *= ' ]
INT_ASSIGN_OPS = NUM_ASSIGN_OPS + [ ' &= ', ' |= ', ' ^= ', ' %= ', ' ~/= ', ' >>= ', ' <<= ' ]
FP_ASSIGN_OPS = NUM_ASSIGN_OPS + [ ' /= ' ]
NUM_INC_OPS = [ '++', '--' ]
REL_OPS = [ ' == ', ' != ' ]
NUM_REL_OPS = REL_OPS + [ ' > ', ' >= ', ' < ', ' <= ' ]
@unique
class Type(IntEnum):
"""Enum representing Dart types."""
BOOL = 0,
INT = 1,
DOUBLE = 2,
STRING = 3,
INT_LIST = 4,
INT_STRING_MAP = 5
Types = {
Type.BOOL : 'bool',
Type.INT : 'int',
Type.DOUBLE : 'double',
Type.STRING : 'String',
Type.INT_LIST : 'List<int>',
Type.INT_STRING_MAP : 'Map<int, String>'
}
TypeList = list(Types.keys())
#
# DartFuzz generator class.
#
class DartFuzz(object):
"""Generates a random, but runnable Dart program for fuzz testing."""
def __init__(self, seed):
"""Constructor.
Args:
seed: int, random seed from which randomness is obtained.
"""
self._seed = seed
def Run(self):
# Setup
self._rand = random.Random()
self._rand.seed(self._seed)
self._indent = 0
self._num_classes = self._rand.randint(1, 4)
# Header.
self.EmitHeader()
# Top level.
for v in range(0, len(TypeList)):
self.EmitTopVarDecl(v)
self.EmitTopLevelMethod()
# Classes.
for c in range(0, self._num_classes):
self.EmitClass(c)
# Main.
self.EmitMain()
#
# Program components.
#
def EmitHeader(self):
self.EmitLn('')
self.EmitLn('// The Dart Project Fuzz Tester (' + VERSION + ').')
self.EmitLn('// Program generated as:')
self.EmitLn('// dartfuzz.py --seed ' + str(self._seed))
self.EmitLn('')
def EmitTopLevelMethod(self):
self.EmitLn('')
self.EmitLn('void top() {')
self._indent += 2
self.EmitStmtList(0)
self._indent -= 2
self.EmitLn('}')
def EmitTopVarDecl(self, v):
tp = v # int as type
self.EmitType(tp)
self.Emit(' var' + str(v) + ' = ')
self.EmitLiteral(tp)
self.Emit(';', end='\n')
def EmitClass(self, class_id):
self.EmitLn('')
self.EmitLn('class X' + str(class_id), end='')
if class_id > 0:
self.Emit(' extends X' + str(class_id - 1))
self.Emit(' {', end='\n')
self._indent += 2
self.EmitFieldDecls()
self.EmitMethod(class_id)
self._indent -= 2
self.EmitLn('}')
def EmitFieldDecls(self):
pass
def EmitMethod(self, class_id):
self.EmitLn('void run() {')
self._indent += 2
if class_id > 0:
self.EmitLn('super.run();')
else:
self.EmitLn('top();')
self.EmitStmtList(0)
self._indent -= 2
self.EmitLn('}')
def EmitMain(self):
self.EmitLn('')
self.EmitLn('main() {')
self._indent += 2
self.EmitLn('try {')
self._indent += 2
self.EmitLn('new X' + str(self._num_classes - 1) + '().run();')
self._indent -= 2
self.EmitLn('} catch (e) {')
self._indent += 2
self.EmitLn('print("exception");')
self._indent -= 2
self.EmitLn('} finally {')
self._indent += 2
for v in range(0, len(TypeList)):
self.EmitLn("print(var" + str(v) + ");");
self.EmitLn('print("done");')
self._indent -= 2
self.EmitLn('}')
self._indent -= 2
self.EmitLn('}')
#
# Statements.
#
def EmitStmtList(self, depth):
num_stmts = self._rand.randint(1, 4)
for s in range(0, num_stmts):
if not self.EmitStmt(depth):
return False # rest would be dead code
return True
def EmitStmt(self, depth):
self.EmitLn('', end='')
r = self._rand.randint(1, 8) # favors assignment
if r == 1 and depth <= 2:
return self.EmitIf(depth)
elif r == 2:
return self.EmitPrint(depth)
else:
return self.EmitAssign(depth)
def EmitIf(self, depth):
self.Emit('if (')
self.EmitExpr(Type.BOOL, 0)
self.Emit(') {', end='\n')
self._indent += 2
self.EmitStmtList(depth + 1)
self._indent -= 2
self.EmitLn('} else {')
self._indent += 2
self.EmitStmtList(depth + 1)
self._indent -= 2
self.EmitLn('}')
return True
def EmitPrint(self, depth):
self.Emit('print(')
tp = self.RandomType()
self.EmitExpr(tp, 0)
self.Emit(');', end='\n')
return True
def EmitAssign(self, depth):
tp = self.RandomType()
self.EmitVar(tp)
self.EmitAssignOp(tp)
self.EmitExpr(tp, 0)
self.Emit(';', end='\n')
return True
#
# Expressions.
#
def RandLen(self, x):
return self._rand.randint(0, len(x) - 1)
def EmitAssignOp(self, tp):
if tp == Type.INT:
self.Emit(INT_ASSIGN_OPS[self.RandLen(INT_ASSIGN_OPS)])
elif tp == Type.DOUBLE:
self.Emit(FP_ASSIGN_OPS[self.RandLen(FP_ASSIGN_OPS)])
else:
self.Emit(' = ')
def EmitUnaryOp(self, tp):
"""Emit same type in-out binary operator."""
if tp == Type.INT:
self.Emit(INT_UNARY_OPS[self.RandLen(INT_UNARY_OPS)])
elif tp == Type.DOUBLE:
self.Emit(NUM_UNARY_OPS[self.RandLen(NUM_UNARY_OPS)])
else:
self.Emit(' !')
def EmitBinOp(self, tp):
"""Emit same type in-out binary operator."""
if tp == Type.BOOL:
self.Emit(BOOL_BIN_OPS[self.RandLen(BOOL_BIN_OPS)])
elif tp == Type.INT:
self.Emit(INT_BIN_OPS[self.RandLen(INT_BIN_OPS)])
elif tp == Type.DOUBLE:
self.Emit(FP_BIN_OPS[self.RandLen(FP_BIN_OPS)])
else:
self.Emit(' + ')
def EmitRelOp(self, tp):
"""Emit one type in, boolean out operator."""
if tp == Type.INT or tp == Type.DOUBLE:
self.Emit(NUM_REL_OPS[self.RandLen(NUM_REL_OPS)])
else:
self.Emit(REL_OPS[self.RandLen(REL_OPS)])
def EmitExpr(self, tp, depth):
if (depth > 2):
self.EmitTerm(tp)
return
r = self._rand.randint(1, 5)
if r == 1 and tp <= Type.DOUBLE:
# Unary operator: (~(x))
self.Emit('(')
self.EmitUnaryOp(tp)
self.Emit('(')
self.EmitExpr(tp, depth + 1)
self.Emit('))')
elif r == 2 and tp <= Type.STRING:
# Binary operator: (x + y)
self.Emit('(')
self.EmitExpr(tp, depth + 1)
self.EmitBinOp(tp)
self.EmitExpr(tp, depth + 1)
self.Emit(')')
elif r == 3 and Type.INT <= tp and tp <= Type.DOUBLE:
# Pre- or post-increment/decrement: (++x) or (x++)
self.Emit('(')
pre = self._rand.randint(1, 1)
if pre == 1:
self.Emit(NUM_INC_OPS[self.RandLen(NUM_INC_OPS)])
self.EmitVar(tp);
if pre == 2:
self.Emit(NUM_INC_OPS[self.RandLen(NUM_INC_OPS)])
self.Emit(')')
elif r == 4:
# Type conversion: x.toInt()
self.EmitTypeConv(tp, depth)
else:
# Terminal expression: x or 1
self.EmitTerm(tp)
def EmitTypeConv(self, tp, depth):
if tp == Type.BOOL:
new_tp = self.RandomType()
self.Emit('(')
self.EmitExpr(new_tp, depth + 1)
self.EmitRelOp(new_tp)
self.EmitExpr(new_tp, depth + 1)
self.Emit(')')
elif tp == Type.INT:
self.Emit('(')
self.EmitExpr(Type.DOUBLE, depth + 1)
self.Emit(').toInt()')
elif tp == Type.DOUBLE:
self.Emit('(')
self.EmitExpr(Type.INT, depth + 1)
self.Emit(').toDouble()')
else:
self.EmitTerm(tp)
def EmitTerm(self, tp):
r = self._rand.randint(1, 2)
if r == 1:
self.EmitVar(tp)
else:
self.EmitLiteral(tp)
def EmitVar(self, tp):
v = int(tp) # type as int
self.Emit('var' + str(v))
def EmitLiteral(self, tp):
if tp == Type.BOOL:
self.Emit('true' if self._rand.randint(0, 1) == 0 else 'false')
elif tp == Type.INT:
self.Emit(str(self._rand.randint(-1000, 1000)))
elif tp == Type.DOUBLE:
self.Emit(str(self._rand.uniform(-1000.0, +1000.0)))
elif tp == Type.STRING:
len = self._rand.randint(1, 5)
self.Emit('"' + ''.join(self._rand.choice('AaBbCcDdEeFfGgHh')
for _ in range(len)) + '"')
elif tp == Type.INT_LIST:
len = self._rand.randint(1, 5)
self.Emit('[')
self.EmitLiteral(Type.INT)
for i in range(1, len):
self.Emit(', ')
self.EmitLiteral(Type.INT)
self.Emit(']')
elif tp == Type.INT_STRING_MAP:
len = self._rand.randint(1, 5)
self.Emit('{ 0 : ')
self.EmitLiteral(Type.STRING)
for i in range(1, len):
self.Emit(', ' + str(i) + ' : ')
self.EmitLiteral(Type.STRING)
self.Emit('}')
#
# Types.
#
def RandomType(self):
return TypeList[self._rand.randint(0, len(TypeList) - 1)]
def EmitType(self, tp):
self.Emit(Types[tp])
#
# Output.
#
def EmitLn(self, line, end='\n'):
"""Emits indented line to append to program (stdout).
Args:
line: string, line to append to program.
"""
print(self._indent * ' ', end='')
print(line, end=end)
def Emit(self, txt, end=''):
"""Emits string to append to program (stdout).
Args:
txt: string, text to append to program.
"""
print(txt, end=end)
#
# Main driver.
#
def main():
# Handle arguments.
parser = argparse.ArgumentParser()
parser.add_argument('--seed', default=0, type=int,
help='random seed (0 forces time-based seed)')
args = parser.parse_args()
# By default (zero seed), select a random seed.
seed = args.seed
if seed == 0:
# Pick system's best way of seeding randomness.
# Then pick a user visible nonzero seed.
random.seed()
while seed == 0:
seed = random.getrandbits(64)
# Run DartFuzz.
fuzzer = DartFuzz(seed)
fuzzer.Run()
if __name__ == '__main__':
main()