feat: add MAX_FIELDS constant and runtime overflow checks for class field limit of 255

Add a MAX_FIELDS constant (255) to wren_common.h and implement field overflow
validation in the compiler and VM. The compiler now emits an error when a class
declares more than 255 fields, and the VM checks for inherited field overflow
at class creation time, producing a descriptive runtime error. Also add four
test files (many_fields, many_inherited_fields, too_many_fields,
too_many_inherited_fields) to verify both valid and invalid field counts.
This commit is contained in:
Bob Nystrom
2014-01-15 15:59:10 +00:00
parent b4437d5723
commit 7e196d9781
7 changed files with 1126 additions and 6 deletions
+7
View File
@@ -63,6 +63,13 @@
// extra spaces added to handle arity, and another byte to terminate the string.
#define MAX_METHOD_SIGNATURE (MAX_METHOD_NAME + MAX_PARAMETERS + 1)
// The maximum number of fields a class can have, including inherited fields.
// This is explicit in the bytecode since `CODE_CLASS` and `CODE_SUBCLASS` take
// a single byte for the number of fields. Note that it's 255 and not 256
// because creating a class takes the *number* of fields, not the *highest
// field index*.
#define MAX_FIELDS (255)
// Assertions are used to validate program invariants. They indicate things the
// program expects to be true about its internal state during execution. If an
// assertion fails, there is a bug in Wren.
+8 -6
View File
@@ -1504,6 +1504,11 @@ static void field(Compiler* compiler, bool allowAssignment)
field = wrenSymbolTableEnsure(compiler->parser->vm, compiler->fields,
compiler->parser->previous.start,
compiler->parser->previous.length);
if (field >= MAX_FIELDS)
{
error(compiler, "A class can only have %d fields.", MAX_FIELDS);
}
}
else
{
@@ -1513,8 +1518,6 @@ static void field(Compiler* compiler, bool allowAssignment)
field = 255;
}
// TODO: Make sure field number fits in byte.
// If there's an "=" after a field name, it's an assignment.
if (match(compiler, TOKEN_EQ))
{
@@ -2350,7 +2353,6 @@ void statement(Compiler* compiler)
static void classDefinition(Compiler* compiler)
{
// Create a variable to store the class in.
// TODO: Allow anonymous classes?
int symbol = declareVariable(compiler);
// Load the superclass (if there is one).
@@ -2522,10 +2524,10 @@ void wrenBindMethodCode(ObjClass* classObj, ObjFn* fn)
case CODE_STORE_FIELD:
case CODE_LOAD_FIELD_THIS:
case CODE_STORE_FIELD_THIS:
// Shift this class's fields down past the inherited ones.
// Shift this class's fields down past the inherited ones. We don't
// check for overflow here because we'll see if the number of fields
// overflows when the subclass is created.
fn->bytecode[ip++] += classObj->superclass->numFields;
// TODO: Make sure field number still fits in byte.
break;
case CODE_CLOSURE:
+26
View File
@@ -530,6 +530,22 @@ static void methodNotFound(WrenVM* vm, ObjFiber* fiber, Value* receiver,
wrenDebugPrintStackTrace(vm, fiber, *receiver);
}
static void tooManyInheritedFields(WrenVM* vm, ObjFiber* fiber, Value* slot)
{
// TODO: Tune size.
char message[200];
// TODO: Include class name in message. Mention inheritance.
snprintf(message, 200,
"A class may not have more than %d fields, including inherited ones.",
MAX_FIELDS);
// Store the error message in the receiver slot so that it's on the fiber's
// stack and doesn't get garbage collected.
*slot = wrenNewString(vm, message, strlen(message));
wrenDebugPrintStackTrace(vm, fiber, *slot);
}
// Pushes [function] onto [fiber]'s callstack and invokes it. Expects [numArgs]
// arguments (including the receiver) to be on the top of the stack already.
// [function] can be an `ObjFn` or `ObjClosure`.
@@ -1181,6 +1197,16 @@ static bool runInterpreter(WrenVM* vm)
ObjClass* classObj = wrenNewClass(vm, superclass, numFields);
// Now that we know the total number of fields, make sure we don't
// overflow.
// TODO: Same check for static fields.
if (superclass->numFields + numFields > MAX_FIELDS)
{
STORE_FRAME();
tooManyInheritedFields(vm, fiber, &fiber->stack[fiber->stackSize - 1]);
return false;
}
// Don't pop the superclass off the stack until the subclass is done
// being created, to make sure it doesn't get collected.
if (isSubclass) POP();