1
0
forked from Mirror/wren

Better argument validation for some list methods.

This commit is contained in:
Bob Nystrom
2014-01-02 07:32:00 -08:00
parent a0c32f2e6e
commit a89386b4e8
25 changed files with 133 additions and 65 deletions

View File

@ -1,8 +1,6 @@
#ifndef wren_common_h
#define wren_common_h
// TODO: Would be good to use "const" on pointer params when possible.
// This header contains macros and defines used across the entire Wren
// implementation. In particular, it contains "configuration" defines that
// control how Wren works. Some of these are only used while hacking on

View File

@ -87,6 +87,80 @@ const char* coreLibSource =
" ... other { return new Range(this, other - 1) }\n"
"}\n";
// TODO: Remove this and migrate everything to use validateIndex().
// Validates that [index] is an integer within `[0, count)`. Also allows
// negative indices which map backwards from the end. Returns the valid positive
// index value, or -1 if the index wasn't valid (not a number, not an int, out
// of bounds).
static int validateIndexOld(Value index, int count)
{
if (!IS_NUM(index)) return -1;
double indexNum = AS_NUM(index);
int intIndex = (int)indexNum;
// Make sure the index is an integer.
if (indexNum != intIndex) return -1;
// Negative indices count from the end.
if (indexNum < 0) indexNum = count + indexNum;
// Check bounds.
if (indexNum < 0 || indexNum >= count) return -1;
return indexNum;
}
// Validates that the given argument in [args] is a Num. Returns true if it is.
// If not, reports an error and returns false.
static bool validateNum(WrenVM* vm, Value* args, int index, const char* argName)
{
if (IS_NUM(args[index])) return true;
char message[100];
snprintf(message, 100, "%s must be a number.", argName);
args[0] = wrenNewString(vm, message, strlen(message));
return false;
}
// Validates that the given argument in [args] is an integer. Returns true if
// it is. If not, reports an error and returns false.
static bool validateInt(WrenVM* vm, Value* args, int index, const char* argName)
{
// Make sure it's a number first.
if (!validateNum(vm, args, index, argName)) return false;
double value = AS_NUM(args[index]);
if (trunc(value) == value) return true;
char message[100];
snprintf(message, 100, "%s must be an integer.", argName);
args[0] = wrenNewString(vm, message, strlen(message));
return false;
}
// Validates that [index] is an integer within `[0, count)`. Also allows
// negative indices which map backwards from the end. Returns the valid positive
// index value. If invalid, reports an error and returns -1.
static int validateIndex(WrenVM* vm, Value* args, int count, int argIndex,
const char* argName)
{
if (!validateInt(vm, args, argIndex, argName)) return -1;
int index = (int)AS_NUM(args[argIndex]);
// Negative indices count from the end.
if (index < 0) index = count + index;
// Check bounds.
if (index >= 0 && index < count) return index;
char message[100];
snprintf(message, 100, "%s out of bounds.", argName);
args[0] = wrenNewString(vm, message, strlen(message));
return -1;
}
DEF_NATIVE(bool_not)
{
RETURN_BOOL(!AS_BOOL(args[0]));
@ -106,28 +180,6 @@ DEF_NATIVE(bool_toString)
DEF_NATIVE(fn_call) { return PRIM_CALL; }
// Validates that [index] is an integer within `[0, count)`. Also allows
// negative indices which map backwards from the end. Returns the valid positive
// index value, or -1 if the index wasn't valid (not a number, not an int, out
// of bounds).
static int validateIndex(Value index, int count)
{
if (!IS_NUM(index)) return -1;
double indexNum = AS_NUM(index);
int intIndex = (int)indexNum;
// Make sure the index is an integer.
if (indexNum != intIndex) return -1;
// Negative indices count from the end.
if (indexNum < 0) indexNum = count + indexNum;
// Check bounds.
if (indexNum < 0 || indexNum >= count) return -1;
return indexNum;
}
DEF_NATIVE(list_add)
{
ObjList* list = AS_LIST(args[0]);
@ -156,10 +208,8 @@ DEF_NATIVE(list_insert)
ObjList* list = AS_LIST(args[0]);
// count + 1 here so you can "insert" at the very end.
int index = validateIndex(args[2], list->count + 1);
// TODO: Instead of returning null here, should signal an error explicitly
// somehow.
if (index == -1) RETURN_NULL;
int index = validateIndex(vm, args, list->count + 1, 2, "Index");
if (index == -1) return PRIM_ERROR;
wrenListInsert(vm, list, args[1], index);
RETURN_VAL(args[1]);
@ -170,12 +220,13 @@ DEF_NATIVE(list_iterate)
// If we're starting the iteration, return the first index.
if (IS_NULL(args[1])) RETURN_NUM(0);
ObjList* list = AS_LIST(args[0]);
double index = AS_NUM(args[1]);
// TODO: Handle arg not a number or not an integer.
if (!validateInt(vm, args, 1, "Iterator")) return PRIM_ERROR;
// Stop if we're out of elements.
if (index >= list->count - 1) RETURN_FALSE;
ObjList* list = AS_LIST(args[0]);
int index = (int)AS_NUM(args[1]);
// Stop if we're out of bounds.
if (index < 0 || index >= list->count - 1) RETURN_FALSE;
// Otherwise, move to the next index.
RETURN_NUM(index + 1);
@ -184,15 +235,16 @@ DEF_NATIVE(list_iterate)
DEF_NATIVE(list_iteratorValue)
{
ObjList* list = AS_LIST(args[0]);
double index = AS_NUM(args[1]);
// TODO: Handle index out of bounds or not integer.
RETURN_VAL(list->elements[(int)index]);
int index = validateIndex(vm, args, list->count, 1, "Iterator");
if (index == -1) return PRIM_ERROR;
RETURN_VAL(list->elements[index]);
}
DEF_NATIVE(list_removeAt)
{
ObjList* list = AS_LIST(args[0]);
int index = validateIndex(args[1], list->count);
int index = validateIndexOld(args[1], list->count);
// TODO: Instead of returning null here, should signal an error explicitly
// somehow.
if (index == -1) RETURN_NULL;
@ -204,7 +256,7 @@ DEF_NATIVE(list_subscript)
{
ObjList* list = AS_LIST(args[0]);
int index = validateIndex(args[1], list->count);
int index = validateIndexOld(args[1], list->count);
// TODO: Instead of returning null here, should signal an error explicitly
// somehow.
if (index == -1) RETURN_NULL;
@ -216,7 +268,7 @@ DEF_NATIVE(list_subscriptSetter)
{
ObjList* list = AS_LIST(args[0]);
int index = validateIndex(args[1], list->count);
int index = validateIndexOld(args[1], list->count);
// TODO: Instead of returning null here, should signal an error explicitly
// somehow.
if (index == -1) RETURN_NULL;
@ -249,45 +301,34 @@ DEF_NATIVE(num_negate)
RETURN_NUM(-AS_NUM(args[0]));
}
// Validates that the second argument in [args] is a Num. Returns true if it is.
// If not, sets a type error and returns false.
static bool checkNumType(WrenVM* vm, Value* args)
{
if (IS_NUM(args[1])) return true;
const char* msg = "TypeError: Right operand must be Num.";
args[0] = wrenNewString(vm, msg, strlen(msg));
return false;
}
DEF_NATIVE(num_minus)
{
if (!checkNumType(vm, args)) return PRIM_ERROR;
if (!validateNum(vm, args, 1, "Right operand")) return PRIM_ERROR;
RETURN_NUM(AS_NUM(args[0]) - AS_NUM(args[1]));
}
DEF_NATIVE(num_plus)
{
if (!checkNumType(vm, args)) return PRIM_ERROR;
if (!validateNum(vm, args, 1, "Right operand")) return PRIM_ERROR;
// TODO: Handle coercion to string if RHS is a string.
RETURN_NUM(AS_NUM(args[0]) + AS_NUM(args[1]));
}
DEF_NATIVE(num_multiply)
{
if (!checkNumType(vm, args)) return PRIM_ERROR;
if (!validateNum(vm, args, 1, "Right operand")) return PRIM_ERROR;
RETURN_NUM(AS_NUM(args[0]) * AS_NUM(args[1]));
}
DEF_NATIVE(num_divide)
{
if (!checkNumType(vm, args)) return PRIM_ERROR;
if (!validateNum(vm, args, 1, "Right operand")) return PRIM_ERROR;
RETURN_NUM(AS_NUM(args[0]) / AS_NUM(args[1]));
}
DEF_NATIVE(num_mod)
{
if (!checkNumType(vm, args)) return PRIM_ERROR;
if (!validateNum(vm, args, 1, "Right operand")) return PRIM_ERROR;
if (!IS_NUM(args[1])) RETURN_NULL;
RETURN_NUM(fmod(AS_NUM(args[0]), AS_NUM(args[1])));
}

View File

@ -1,6 +1,3 @@
// TODO: What happens if the index isn't an integer?
// TODO: Out of bounds.
// Add to empty list.
var a = []
a.insert(1, 0)

View File

@ -0,0 +1,2 @@
var a = [1, 2, 3]
a.insert("value", 1.5) // expect runtime error: Index must be an integer.

View File

@ -0,0 +1,2 @@
var a = [1, 2, 3]
a.insert("value", "2") // expect runtime error: Index must be a number.

View File

@ -0,0 +1,2 @@
var a = [1, 2, 3]
a.insert("value", 4) // expect runtime error: Index out of bounds.

View File

@ -0,0 +1,2 @@
var a = [1, 2, 3]
a.insert("value", -5) // expect runtime error: Index out of bounds.

7
test/list/iterate.wren Normal file
View File

@ -0,0 +1,7 @@
var a = ["one", "two", "three", "four"]
IO.write(a.iterate(null)) // expect: 0
IO.write(a.iterate(0)) // expect: 1
IO.write(a.iterate(1)) // expect: 2
IO.write(a.iterate(2)) // expect: 3
IO.write(a.iterate(3)) // expect: false
IO.write(a.iterate(-1)) // expect: false

View File

@ -0,0 +1,2 @@
var a = [1, 2, 3]
a.iterate(1.5) // expect runtime error: Iterator must be an integer.

View File

@ -0,0 +1,2 @@
var a = [1, 2, 3]
a.iterate("2") // expect runtime error: Iterator must be a number.

View File

@ -0,0 +1,5 @@
var a = ["one", "two", "three", "four"]
IO.write(a.iteratorValue(0)) // expect: one
IO.write(a.iteratorValue(1)) // expect: two
IO.write(a.iteratorValue(2)) // expect: three
IO.write(a.iteratorValue(3)) // expect: four

View File

@ -0,0 +1,2 @@
var a = [1, 2, 3]
a.iteratorValue(1.5) // expect runtime error: Iterator must be an integer.

View File

@ -0,0 +1,2 @@
var a = [1, 2, 3]
a.iteratorValue("2") // expect runtime error: Iterator must be a number.

View File

@ -0,0 +1,2 @@
var a = [1, 2, 3]
a.iteratorValue(4) // expect runtime error: Iterator out of bounds.

View File

@ -0,0 +1,2 @@
var a = [1, 2, 3]
a.iteratorValue(-5) // expect runtime error: Iterator out of bounds.

View File

@ -0,0 +1 @@
1 / false // expect runtime error: Right operand must be a number.

View File

@ -1 +0,0 @@
1 / false // expect runtime error: TypeError: Right operand must be Num.

View File

@ -0,0 +1 @@
1 - false // expect runtime error: Right operand must be a number.

View File

@ -1 +0,0 @@
1 - false // expect runtime error: TypeError: Right operand must be Num.

View File

@ -0,0 +1 @@
1 % false // expect runtime error: Right operand must be a number.

View File

@ -1 +0,0 @@
1 % false // expect runtime error: TypeError: Right operand must be Num.

View File

@ -0,0 +1 @@
1 * false // expect runtime error: Right operand must be a number.

View File

@ -1 +0,0 @@
1 * false // expect runtime error: TypeError: Right operand must be Num.

View File

@ -0,0 +1,3 @@
1 + false // expect runtime error: Right operand must be a number.
// TODO: What about a string on the RHS?

View File

@ -1,3 +0,0 @@
1 + false // expect runtime error: TypeError: Right operand must be Num.
// TODO: What about a string on the RHS?