From ddcbf14a26612df962e95d4a9e14bf2e929e5339 Mon Sep 17 00:00:00 2001 From: Steven Hartland Date: Wed, 7 Dec 2022 00:13:42 +0000 Subject: [PATCH] chore: improve error messages (#482) Improve error messages so we don't just get TypeError with no clue what the issue was. --- .github/workflows/test-lint.yml | 1 + array_test.go | 24 +++++++-------- builtin_array.go | 18 +++++------ builtin_date.go | 2 +- builtin_error.go | 2 +- builtin_function.go | 12 ++++---- builtin_json.go | 4 +-- builtin_object.go | 35 ++++++++++------------ cmpl_evaluate_expression.go | 10 +++---- error_test.go | 14 ++++----- evaluate.go | 4 +-- function_test.go | 4 +-- generate.go | 1 + object_class.go | 53 +++++++++++++++++---------------- object_test.go | 18 +++++------ panic_test.go | 2 +- property.go | 10 +++---- runtime.go | 15 +++++----- runtime_test.go | 4 +-- stash.go | 2 +- type_array.go | 22 +++++++------- type_date.go | 7 +++-- type_function.go | 9 +++--- type_go_struct.go | 2 +- value.go | 10 ++----- value_kind.gen.go | 31 +++++++++++++++++++ 26 files changed, 173 insertions(+), 143 deletions(-) create mode 100644 value_kind.gen.go diff --git a/.github/workflows/test-lint.yml b/.github/workflows/test-lint.yml index 263201c8..f56b0261 100644 --- a/.github/workflows/test-lint.yml +++ b/.github/workflows/test-lint.yml @@ -25,6 +25,7 @@ jobs: - name: Validate go mod / generate run: | go mod tidy + go install golang.org/x/tools/cmd/stringer@latest go generate ./... git --no-pager diff && [[ 0 -eq $(git status --porcelain | wc -l) ]] diff --git a/array_test.go b/array_test.go index 602f3418..1a33a86b 100644 --- a/array_test.go +++ b/array_test.go @@ -111,7 +111,7 @@ func TestArray_toLocaleString(t *testing.T) { test(`raise: [ { toLocaleString: undefined } ].toLocaleString(); - `, "TypeError") + `, `TypeError: Array.toLocaleString index[0] "undefined" is not callable`) }) } @@ -429,9 +429,9 @@ func TestArray_every(t *testing.T) { tt(t, func() { test, _ := test() - test(`raise: [].every()`, "TypeError") + test(`raise: [].every()`, `TypeError: Array.every argument "undefined" is not callable`) - test(`raise: [].every("abc")`, "TypeError") + test(`raise: [].every("abc")`, `TypeError: Array.every argument "abc" is not callable`) test(`[].every(function() { return false })`, true) @@ -479,7 +479,7 @@ func TestArray_some(t *testing.T) { tt(t, func() { test, _ := test() - test(`raise: [].some("abc")`, "TypeError") + test(`raise: [].some("abc")`, `TypeError: Array.some "abc" if not callable`) test(`[].some(function() { return true })`, false) @@ -521,7 +521,7 @@ func TestArray_forEach(t *testing.T) { tt(t, func() { test, _ := test() - test(`raise: [].forEach("abc")`, "TypeError") + test(`raise: [].forEach("abc")`, `TypeError: Array.foreach "abc" if not callable`) test(` var abc = 0; @@ -594,7 +594,7 @@ func TestArray_map(t *testing.T) { tt(t, func() { test, _ := test() - test(`raise: [].map("abc")`, "TypeError") + test(`raise: [].map("abc")`, `TypeError: Array.foreach "abc" if not callable`) test(`[].map(function() { return 1 }).length`, 0) @@ -626,7 +626,7 @@ func TestArray_filter(t *testing.T) { tt(t, func() { test, _ := test() - test(`raise: [].filter("abc")`, "TypeError") + test(`raise: [].filter("abc")`, `TypeError: Array.filter "abc" if not callable`) test(`[].filter(function() { return 1 }).length`, 0) @@ -640,9 +640,9 @@ func TestArray_reduce(t *testing.T) { tt(t, func() { test, _ := test() - test(`raise: [].reduce("abc")`, "TypeError") + test(`raise: [].reduce("abc")`, `TypeError: Array.reduce "abc" if not callable`) - test(`raise: [].reduce(function() {})`, "TypeError") + test(`raise: [].reduce(function() {})`, `TypeError: Array.reduce "function() {}" if not callable`) test(`[].reduce(function() {}, 0)`, 0) @@ -660,9 +660,9 @@ func TestArray_reduceRight(t *testing.T) { tt(t, func() { test, _ := test() - test(`raise: [].reduceRight("abc")`, "TypeError") + test(`raise: [].reduceRight("abc")`, `TypeError: Array.reduceRight "abc" if not callable`) - test(`raise: [].reduceRight(function() {})`, "TypeError") + test(`raise: [].reduceRight(function() {})`, `TypeError: Array.reduceRight "function() {}" if not callable`) test(`[].reduceRight(function() {}, 0)`, 0) @@ -697,7 +697,7 @@ func TestArray_defineOwnProperty(t *testing.T) { Object.defineProperty(abc, "length", { writable: true }); - `, "TypeError") + `, `TypeError: Object.DefineOwnProperty: property not configurable or writeable and descriptor not writeable`) }) } diff --git a/builtin_array.go b/builtin_array.go index 2744bca0..a82ed012 100644 --- a/builtin_array.go +++ b/builtin_array.go @@ -52,7 +52,7 @@ func builtinArrayToLocaleString(call FunctionCall) Value { obj := call.runtime.toObject(value) toLocaleString := obj.get("toLocaleString") if !toLocaleString.isCallable() { - panic(call.runtime.panicTypeError()) + panic(call.runtime.panicTypeError("Array.toLocaleString index[%d] %q is not callable", index, toLocaleString)) } stringValue = toLocaleString.call(call.runtime, objectValue(obj)).string() } @@ -457,7 +457,7 @@ func builtinArraySort(call FunctionCall) Value { compare := compareValue.object() if compareValue.IsUndefined() { } else if !compareValue.isCallable() { - panic(call.runtime.panicTypeError()) + panic(call.runtime.panicTypeError("Array.sort value %q is not callable", compareValue)) } if length > 1 { arraySortQuickSort(thisObject, 0, length-1, compare) @@ -541,7 +541,7 @@ func builtinArrayEvery(call FunctionCall) Value { } return trueValue } - panic(call.runtime.panicTypeError()) + panic(call.runtime.panicTypeError("Array.every argument %q is not callable", call.Argument(0))) } func builtinArraySome(call FunctionCall) Value { @@ -559,7 +559,7 @@ func builtinArraySome(call FunctionCall) Value { } return falseValue } - panic(call.runtime.panicTypeError()) + panic(call.runtime.panicTypeError("Array.some %q if not callable", call.Argument(0))) } func builtinArrayForEach(call FunctionCall) Value { @@ -575,7 +575,7 @@ func builtinArrayForEach(call FunctionCall) Value { } return Value{} } - panic(call.runtime.panicTypeError()) + panic(call.runtime.panicTypeError("Array.foreach %q if not callable", call.Argument(0))) } func builtinArrayMap(call FunctionCall) Value { @@ -594,7 +594,7 @@ func builtinArrayMap(call FunctionCall) Value { } return objectValue(call.runtime.newArrayOf(values)) } - panic(call.runtime.panicTypeError()) + panic(call.runtime.panicTypeError("Array.foreach %q if not callable", call.Argument(0))) } func builtinArrayFilter(call FunctionCall) Value { @@ -614,7 +614,7 @@ func builtinArrayFilter(call FunctionCall) Value { } return objectValue(call.runtime.newArrayOf(values)) } - panic(call.runtime.panicTypeError()) + panic(call.runtime.panicTypeError("Array.filter %q if not callable", call.Argument(0))) } func builtinArrayReduce(call FunctionCall) Value { @@ -647,7 +647,7 @@ func builtinArrayReduce(call FunctionCall) Value { return accumulator } } - panic(call.runtime.panicTypeError()) + panic(call.runtime.panicTypeError("Array.reduce %q if not callable", call.Argument(0))) } func builtinArrayReduceRight(call FunctionCall) Value { @@ -679,5 +679,5 @@ func builtinArrayReduceRight(call FunctionCall) Value { return accumulator } } - panic(call.runtime.panicTypeError()) + panic(call.runtime.panicTypeError("Array.reduceRight %q if not callable", call.Argument(0))) } diff --git a/builtin_date.go b/builtin_date.go index 3ee81b0b..2d1d601a 100644 --- a/builtin_date.go +++ b/builtin_date.go @@ -81,7 +81,7 @@ func builtinDateToJSON(call FunctionCall) Value { toISOString := obj.get("toISOString") if !toISOString.isCallable() { // FIXME - panic(call.runtime.panicTypeError()) + panic(call.runtime.panicTypeError("Date.toJSON toISOString %q is not callable", toISOString)) } return toISOString.call(call.runtime, objectValue(obj), []Value{}) } diff --git a/builtin_error.go b/builtin_error.go index 7e043d7c..9d524f54 100644 --- a/builtin_error.go +++ b/builtin_error.go @@ -15,7 +15,7 @@ func builtinNewError(obj *object, argumentList []Value) Value { func builtinErrorToString(call FunctionCall) Value { thisObject := call.thisObject() if thisObject == nil { - panic(call.runtime.panicTypeError()) + panic(call.runtime.panicTypeError("Error.toString is nil")) } name := classErrorName diff --git a/builtin_function.go b/builtin_function.go index 6ae1ae1b..51d2cac7 100644 --- a/builtin_function.go +++ b/builtin_function.go @@ -58,14 +58,14 @@ func builtinFunctionToString(call FunctionCall) Value { return stringValue(fn.node.source) case bindFunctionObject: return stringValue("function () { [native code] }") + default: + panic(call.runtime.panicTypeError("Function.toString unknown type %T", obj.value)) } - - panic(call.runtime.panicTypeError("Function.toString()")) } func builtinFunctionApply(call FunctionCall) Value { if !call.This.isCallable() { - panic(call.runtime.panicTypeError()) + panic(call.runtime.panicTypeError("Function.apply %q is not callable", call.This)) } this := call.Argument(0) if this.IsUndefined() { @@ -78,7 +78,7 @@ func builtinFunctionApply(call FunctionCall) Value { return call.thisObject().call(this, nil, false, nativeFrame) case valueObject: default: - panic(call.runtime.panicTypeError()) + panic(call.runtime.panicTypeError("Function.apply unknown type %T for second argument")) } arrayObject := argumentList.object() @@ -93,7 +93,7 @@ func builtinFunctionApply(call FunctionCall) Value { func builtinFunctionCall(call FunctionCall) Value { if !call.This.isCallable() { - panic(call.runtime.panicTypeError()) + panic(call.runtime.panicTypeError("Function.call %q is not callable", call.This)) } thisObject := call.thisObject() this := call.Argument(0) @@ -110,7 +110,7 @@ func builtinFunctionCall(call FunctionCall) Value { func builtinFunctionBind(call FunctionCall) Value { target := call.This if !target.isCallable() { - panic(call.runtime.panicTypeError()) + panic(call.runtime.panicTypeError("Function.bind %q is not callable", call.This)) } targetObject := target.object() diff --git a/builtin_json.go b/builtin_json.go index 9d59c445..960c3937 100644 --- a/builtin_json.go +++ b/builtin_json.go @@ -180,12 +180,12 @@ func builtinJSONStringify(call FunctionCall) Value { } valueJSON, err := json.Marshal(value) if err != nil { - panic(call.runtime.panicTypeError(err.Error())) + panic(call.runtime.panicTypeError("JSON.stringify marshal: %s", err)) } if ctx.gap != "" { valueJSON1 := bytes.Buffer{} if err = json.Indent(&valueJSON1, valueJSON, "", ctx.gap); err != nil { - panic(call.runtime.panicTypeError(err.Error())) + panic(call.runtime.panicTypeError("JSON.stringify indent: %s", err)) } valueJSON = valueJSON1.Bytes() } diff --git a/builtin_object.go b/builtin_object.go index 6d7408c1..029a75f7 100644 --- a/builtin_object.go +++ b/builtin_object.go @@ -81,7 +81,7 @@ func builtinObjectToString(call FunctionCall) Value { func builtinObjectToLocaleString(call FunctionCall) Value { toString := call.thisObject().get("toString") if !toString.isCallable() { - panic(call.runtime.panicTypeError()) + panic(call.runtime.panicTypeError("Object.toLocaleString %q is not callable", toString)) } return toString.call(call.runtime, call.This) } @@ -90,7 +90,7 @@ func builtinObjectGetPrototypeOf(call FunctionCall) Value { val := call.Argument(0) obj := val.object() if obj == nil { - panic(call.runtime.panicTypeError()) + panic(call.runtime.panicTypeError("Object.GetPrototypeOf is nil")) } if obj.prototype == nil { @@ -104,7 +104,7 @@ func builtinObjectGetOwnPropertyDescriptor(call FunctionCall) Value { val := call.Argument(0) obj := val.object() if obj == nil { - panic(call.runtime.panicTypeError()) + panic(call.runtime.panicTypeError("Object.GetOwnPropertyDescriptor is nil")) } name := call.Argument(1).string() @@ -119,7 +119,7 @@ func builtinObjectDefineProperty(call FunctionCall) Value { val := call.Argument(0) obj := val.object() if obj == nil { - panic(call.runtime.panicTypeError()) + panic(call.runtime.panicTypeError("Object.DefineProperty is nil")) } name := call.Argument(1).string() descriptor := toPropertyDescriptor(call.runtime, call.Argument(2)) @@ -131,7 +131,7 @@ func builtinObjectDefineProperties(call FunctionCall) Value { val := call.Argument(0) obj := val.object() if obj == nil { - panic(call.runtime.panicTypeError()) + panic(call.runtime.panicTypeError("Object.DefineProperties is nil")) } properties := call.runtime.toObject(call.Argument(1)) @@ -147,7 +147,7 @@ func builtinObjectDefineProperties(call FunctionCall) Value { func builtinObjectCreate(call FunctionCall) Value { prototypeValue := call.Argument(0) if !prototypeValue.IsNull() && !prototypeValue.IsObject() { - panic(call.runtime.panicTypeError()) + panic(call.runtime.panicTypeError("Object.Create is nil")) } obj := call.runtime.newObject() @@ -171,17 +171,16 @@ func builtinObjectIsExtensible(call FunctionCall) Value { if obj := val.object(); obj != nil { return boolValue(obj.extensible) } - panic(call.runtime.panicTypeError()) + panic(call.runtime.panicTypeError("Object.IsExtensible is nil")) } func builtinObjectPreventExtensions(call FunctionCall) Value { val := call.Argument(0) if obj := val.object(); obj != nil { obj.extensible = false - } else { - panic(call.runtime.panicTypeError()) + return val } - return val + panic(call.runtime.panicTypeError("Object.PreventExtensions is nil")) } func builtinObjectIsSealed(call FunctionCall) Value { @@ -200,7 +199,7 @@ func builtinObjectIsSealed(call FunctionCall) Value { }) return boolValue(result) } - panic(call.runtime.panicTypeError()) + panic(call.runtime.panicTypeError("Object.IsSealed is nil")) } func builtinObjectSeal(call FunctionCall) Value { @@ -214,10 +213,9 @@ func builtinObjectSeal(call FunctionCall) Value { return true }) obj.extensible = false - } else { - panic(call.runtime.panicTypeError()) + return val } - return val + panic(call.runtime.panicTypeError("Object.Seal is nil")) } func builtinObjectIsFrozen(call FunctionCall) Value { @@ -236,7 +234,7 @@ func builtinObjectIsFrozen(call FunctionCall) Value { }) return boolValue(result) } - panic(call.runtime.panicTypeError()) + panic(call.runtime.panicTypeError("Object.IsFrozen is nil")) } func builtinObjectFreeze(call FunctionCall) Value { @@ -259,10 +257,9 @@ func builtinObjectFreeze(call FunctionCall) Value { return true }) obj.extensible = false - } else { - panic(call.runtime.panicTypeError()) + return val } - return val + panic(call.runtime.panicTypeError("Object.Freeze is nil")) } func builtinObjectKeys(call FunctionCall) Value { @@ -273,7 +270,7 @@ func builtinObjectKeys(call FunctionCall) Value { }) return objectValue(call.runtime.newArrayOf(keys)) } - panic(call.runtime.panicTypeError()) + panic(call.runtime.panicTypeError("Object.Keys is nil")) } func builtinObjectGetOwnPropertyNames(call FunctionCall) Value { diff --git a/cmpl_evaluate_expression.go b/cmpl_evaluate_expression.go index 27964dcc..69c673a8 100644 --- a/cmpl_evaluate_expression.go +++ b/cmpl_evaluate_expression.go @@ -169,7 +169,7 @@ func (rt *runtime) cmplEvaluateNodeBracketExpression(node *nodeBracketExpression // TODO Pass in base value as-is, and defer toObject till later? obj, err := rt.objectCoerce(targetValue) if err != nil { - panic(rt.panicTypeError("Cannot access member '%s' of %s", memberValue.string(), err.Error(), at(node.idx))) + panic(rt.panicTypeError("Cannot access member %q of %s", memberValue.string(), err, at(node.idx))) } return toValue(newPropertyReference(rt, obj, memberValue.string(), false, at(node.idx))) } @@ -201,7 +201,7 @@ func (rt *runtime) cmplEvaluateNodeCallExpression(node *nodeCallExpression, with eval = rf.name == "eval" // Possible direct eval default: // FIXME? - panic(rt.panicTypeError("Here be dragons")) + panic(rt.panicTypeError("unexpected callee type %T to node call expression", rf)) } } @@ -226,7 +226,7 @@ func (rt *runtime) cmplEvaluateNodeCallExpression(node *nodeCallExpression, with // FIXME Maybe typeof? panic(rt.panicTypeError("%v is not a function", vl, atv)) } - panic(rt.panicTypeError("'%s' is not a function", name, atv)) + panic(rt.panicTypeError("%q is not a function", name, atv)) } rt.scope.frame.offset = int(atv) @@ -249,7 +249,7 @@ func (rt *runtime) cmplEvaluateNodeDotExpression(node *nodeDotExpression) Value // TODO Pass in base value as-is, and defer toObject till later? obj, err := rt.objectCoerce(targetValue) if err != nil { - panic(rt.panicTypeError("Cannot access member '%s' of %s", node.identifier, err.Error(), at(node.idx))) + panic(rt.panicTypeError("Cannot access member %q of %s", node.identifier, err, at(node.idx))) } return toValue(newPropertyReference(rt, obj, node.identifier, false, at(node.idx))) } @@ -270,7 +270,7 @@ func (rt *runtime) cmplEvaluateNodeNewExpression(node *nodeNewExpression) Value case *stashReference: name = rf.name default: - panic(rt.panicTypeError("Here be dragons")) + panic(rt.panicTypeError("node new expression unexpected callee type %T", rf)) } } diff --git a/error_test.go b/error_test.go index 1a40c889..27b627f3 100644 --- a/error_test.go +++ b/error_test.go @@ -80,7 +80,7 @@ func TestErrorContext(t *testing.T) { `) { err := asError(t, err) - is(err.message, "'undefined' is not a function") + is(err.message, `"undefined" is not a function`) is(len(err.trace), 1) is(err.trace[0].location(), ":2:13") } @@ -90,7 +90,7 @@ func TestErrorContext(t *testing.T) { `) { err := asError(t, err) - is(err.message, "'abc' is not a function") + is(err.message, `"abc" is not a function`) is(len(err.trace), 1) is(err.trace[0].location(), ":2:14") } @@ -100,7 +100,7 @@ func TestErrorContext(t *testing.T) { `) { err := asError(t, err) - is(err.message, "'abc' is not a function") + is(err.message, `"abc" is not a function`) is(len(err.trace), 1) is(err.trace[0].location(), ":2:14") } @@ -111,7 +111,7 @@ func TestErrorContext(t *testing.T) { `) { err := asError(t, err) - is(err.message, "'ghi' is not a function") + is(err.message, `"ghi" is not a function`) is(len(err.trace), 1) is(err.trace[0].location(), ":3:13") } @@ -127,7 +127,7 @@ func TestErrorContext(t *testing.T) { `) { err := asError(t, err) - is(err.message, "'undefined' is not a function") + is(err.message, `"undefined" is not a function`) is(len(err.trace), 3) is(err.trace[0].location(), "def (:3:17)") is(err.trace[1].location(), "abc (:6:17)") @@ -174,7 +174,7 @@ func TestErrorContext(t *testing.T) { `) { err := asError(t, err) - is(err.message, "'xyzzy' is not a function") + is(err.message, `"xyzzy" is not a function`) is(len(err.trace), 1) is(err.trace[0].location(), ":1:1") } @@ -251,7 +251,7 @@ func TestErrorContext(t *testing.T) { f, _ := vm.Get("C") _, err := f.Call(UndefinedValue()) err1 := asError(t, err) - is(err1.message, "Cannot access member 'prop' of null") + is(err1.message, `Cannot access member "prop" of null`) is(len(err1.trace), 1) is(err1.trace[0].location(), "C (file1.js:7:5)") } diff --git a/evaluate.go b/evaluate.go index aba7276b..2108b7c3 100644 --- a/evaluate.go +++ b/evaluate.go @@ -119,14 +119,14 @@ func (rt *runtime) calculateBinaryExpression(operator token.Token, left Value, r case token.INSTANCEOF: rightValue := right.resolve() if !rightValue.IsObject() { - panic(rt.panicTypeError("Expecting a function in instanceof check, but got: %v", rightValue)) + panic(rt.panicTypeError("invalid kind %s for instanceof (expected object)", rightValue.kind)) } return boolValue(rightValue.object().hasInstance(leftValue)) case token.IN: rightValue := right.resolve() if !rightValue.IsObject() { - panic(rt.panicTypeError()) + panic(rt.panicTypeError("invalid kind %s for in (expected object)", rightValue.kind)) } return boolValue(rightValue.object().hasProperty(leftValue.string())) } diff --git a/function_test.go b/function_test.go index 030bac3d..28c64e69 100644 --- a/function_test.go +++ b/function_test.go @@ -229,7 +229,7 @@ func TestFunction_bind(t *testing.T) { test(`raise: Math.bind(); - `, "TypeError: 'bind' is not a function") + `, `TypeError: "bind" is not a function`) test(` function construct(fn, arguments) { @@ -268,7 +268,7 @@ func TestFunction_toString(t *testing.T) { test(`raise: Function.prototype.toString.call(undefined); - `, "TypeError") + `, "TypeError: Function.Class environment != Function") test(` abc = function() { return -1 ; diff --git a/generate.go b/generate.go index e1b069bd..767091e8 100644 --- a/generate.go +++ b/generate.go @@ -1,3 +1,4 @@ package otto //go:generate go run ./tools/gen-jscore -output inline.go +//go:generate stringer -type=valueKind -trimprefix=value -output=value_kind.gen.go diff --git a/object_class.go b/object_class.go index c1a59bb7..a9cfbf67 100644 --- a/object_class.go +++ b/object_class.go @@ -210,7 +210,7 @@ func objectCanPutDetails(obj *object, name string) (canPut bool, prop *property, canPut = setter != nil return default: - panic(obj.runtime.panicTypeError()) + panic(obj.runtime.panicTypeError("unexpected type %T to Object.CanPutDetails", prop.value)) } } @@ -234,7 +234,7 @@ func objectCanPutDetails(obj *object, name string) (canPut bool, prop *property, canPut = setter != nil return default: - panic(obj.runtime.panicTypeError()) + panic(obj.runtime.panicTypeError("unexpected type %T to Object.CanPutDetails", prop.value)) } } @@ -282,22 +282,23 @@ func objectPut(obj *object, name string, value Value, throw bool) { } } obj.defineProperty(name, value, 0o111, throw) - } else { - switch propertyValue := prop.value.(type) { - case Value: - prop.value = value - obj.defineOwnProperty(name, *prop, throw) - case propertyGetSet: - if propertyValue[1] != nil { - propertyValue[1].call(toValue(obj), []Value{value}, false, nativeFrame) - return - } - if throw { - panic(obj.runtime.panicTypeError()) - } - default: - panic(obj.runtime.panicTypeError()) + return + } + + switch propertyValue := prop.value.(type) { + case Value: + prop.value = value + obj.defineOwnProperty(name, *prop, throw) + case propertyGetSet: + if propertyValue[1] != nil { + propertyValue[1].call(toValue(obj), []Value{value}, false, nativeFrame) + return + } + if throw { + panic(obj.runtime.panicTypeError("Object.Put nil second parameter to propertyGetSet")) } + default: + panic(obj.runtime.panicTypeError("Object.Put unexpected type %T", prop.value)) } } @@ -312,9 +313,9 @@ func objectHasOwnProperty(obj *object, name string) bool { // 8.12.9. func objectDefineOwnProperty(obj *object, name string, descriptor property, throw bool) bool { - reject := func() bool { + reject := func(reason string) bool { if throw { - panic(obj.runtime.panicTypeError()) + panic(obj.runtime.panicTypeError("Object.DefineOwnProperty: %s", reason)) } return false } @@ -322,7 +323,7 @@ func objectDefineOwnProperty(obj *object, name string, descriptor property, thro prop, exists := obj.readProperty(name) if !exists { if !obj.extensible { - return reject() + return reject("not exists and not extensible") } if newGetSet, isAccessor := descriptor.value.(propertyGetSet); isAccessor { if newGetSet[0] == &nilGetSetObject { @@ -347,12 +348,12 @@ func objectDefineOwnProperty(obj *object, name string, descriptor property, thro configurable := prop.configurable() if !configurable { if descriptor.configurable() { - return reject() + return reject("property and descriptor not configurable") } // Test that, if enumerable is set on the property descriptor, then it should // be the same as the existing property if descriptor.enumerateSet() && descriptor.enumerable() != prop.enumerable() { - return reject() + return reject("property not configurable and enumerable miss match") } } @@ -364,17 +365,17 @@ func objectDefineOwnProperty(obj *object, name string, descriptor property, thro case isDataDescriptor != descriptor.isDataDescriptor(): // DataDescriptor <=> AccessorDescriptor if !configurable { - return reject() + return reject("property descriptor not configurable") } case isDataDescriptor && descriptor.isDataDescriptor(): // DataDescriptor <=> DataDescriptor if !configurable { if !prop.writable() && descriptor.writable() { - return reject() + return reject("property not configurable or writeable and descriptor not writeable") } if !prop.writable() { if descriptor.value != nil && !sameValue(value, descriptor.value.(Value)) { - return reject() + return reject("property not configurable or writeable and descriptor not the same") } } } @@ -400,7 +401,7 @@ func objectDefineOwnProperty(obj *object, name string, descriptor property, thro } if !configurable { if (presentGet && (getSet[0] != newGetSet[0])) || (presentSet && (getSet[1] != newGetSet[1])) { - return reject() + return reject("access descriptor not configurable") } } descriptor.value = newGetSet diff --git a/object_test.go b/object_test.go index 8f806d57..3af01888 100644 --- a/object_test.go +++ b/object_test.go @@ -63,7 +63,7 @@ func TestObject_create(t *testing.T) { tt(t, func() { test, _ := test() - test(`raise: Object.create()`, "TypeError") + test(`raise: Object.create()`, "TypeError: Object.Create is nil") test(` var abc = Object.create(null) @@ -121,7 +121,7 @@ func TestObject_isExtensible(t *testing.T) { test(`raise: Object.isExtensible(); - `, "TypeError") + `, "TypeError: Object.IsExtensible is nil") // FIXME terst, Why raise? test(`raise: @@ -139,7 +139,7 @@ func TestObject_preventExtensions(t *testing.T) { test(`raise: Object.preventExtensions() - `, "TypeError") + `, "TypeError: Object.PreventExtensions is nil") test(`raise: var abc = { def: true }; @@ -178,7 +178,7 @@ func TestObject_seal(t *testing.T) { tt(t, func() { test, _ := test() - test(`raise: Object.seal()`, "TypeError") + test(`raise: Object.seal()`, "TypeError: Object.Seal is nil") test(` var abc = {a:1,b:1,c:3}; @@ -211,7 +211,7 @@ func TestObject_isFrozen(t *testing.T) { tt(t, func() { test, _ := test() - test(`raise: Object.isFrozen()`, "TypeError") + test(`raise: Object.isFrozen()`, "TypeError: Object.IsFrozen is nil") test(`Object.isFrozen(Object.preventExtensions({a:1}))`, false) test(`Object.isFrozen({})`, false) @@ -235,7 +235,7 @@ func TestObject_freeze(t *testing.T) { tt(t, func() { test, _ := test() - test(`raise: Object.freeze()`, "TypeError") + test(`raise: Object.freeze()`, "TypeError: Object.Freeze is nil") test(` var abc = {a:1,b:2,c:3}; @@ -395,7 +395,7 @@ func TestObjectGetterSetter(t *testing.T) { writable: true } }).abc; - `, "TypeError") + `, "TypeError: toPropertyDescriptor descriptor writeSet") test(`raise: Object.create({}, { @@ -406,7 +406,7 @@ func TestObjectGetterSetter(t *testing.T) { writable: false } }).abc; - `, "TypeError") + `, "TypeError: toPropertyDescriptor descriptor writeSet") test(` Object.create({}, { @@ -512,7 +512,7 @@ func TestObjectGetterSetter(t *testing.T) { return 2; } }); - `, "TypeError") + `, `TypeError: Array.DefineOwnProperty ["function () {\n return 2;\n }" ] is not a value`) test(` var abc = {}; diff --git a/panic_test.go b/panic_test.go index 06f0a64f..43da0468 100644 --- a/panic_test.go +++ b/panic_test.go @@ -21,7 +21,7 @@ func Test_panic(t *testing.T) { var abc = []; Object.defineProperty(abc, "0", { writable: false }); Object.defineProperty(abc, "0", { value: false, writable: false }); - `, "TypeError") + `, "TypeError: Array.DefineOwnProperty Object.DefineOwnProperty failed") // Test that a regular expression can contain \c0410 (CYRILLIC CAPITAL LETTER A) // without panicking diff --git a/property.go b/property.go index 8fa7c1c0..9c2758ed 100644 --- a/property.go +++ b/property.go @@ -117,7 +117,7 @@ func (p property) isEmpty() bool { func toPropertyDescriptor(rt *runtime, value Value) property { objectDescriptor := value.object() if objectDescriptor == nil { - panic(rt.panicTypeError()) + panic(rt.panicTypeError("toPropertyDescriptor on nil")) } var descriptor property @@ -153,7 +153,7 @@ func toPropertyDescriptor(rt *runtime, value Value) property { value := objectDescriptor.get("get") if value.IsDefined() { if !value.isCallable() { - panic(rt.panicTypeError()) + panic(rt.panicTypeError("toPropertyDescriptor get not callable")) } getter = value.object() getterSetter = true @@ -167,7 +167,7 @@ func toPropertyDescriptor(rt *runtime, value Value) property { value := objectDescriptor.get("set") if value.IsDefined() { if !value.isCallable() { - panic(rt.panicTypeError()) + panic(rt.panicTypeError("toPropertyDescriptor set not callable")) } setter = value.object() getterSetter = true @@ -179,14 +179,14 @@ func toPropertyDescriptor(rt *runtime, value Value) property { if getterSetter { if descriptor.writeSet() { - panic(rt.panicTypeError()) + panic(rt.panicTypeError("toPropertyDescriptor descriptor writeSet")) } descriptor.value = propertyGetSet{getter, setter} } if objectDescriptor.hasProperty("value") { if getterSetter { - panic(rt.panicTypeError()) + panic(rt.panicTypeError("toPropertyDescriptor value getterSetter")) } descriptor.value = objectDescriptor.get("value") } diff --git a/runtime.go b/runtime.go index cbe18571..883b5127 100644 --- a/runtime.go +++ b/runtime.go @@ -147,7 +147,7 @@ func (rt *runtime) tryCatchEvaluate(inner func() Value) (tryValue Value, isExcep func (rt *runtime) toObject(value Value) *object { switch value.kind { case valueEmpty, valueUndefined, valueNull: - panic(rt.panicTypeError()) + panic(rt.panicTypeError("toObject unsupported kind %s", value.kind)) case valueBoolean: return rt.newBoolean(value) case valueString: @@ -157,7 +157,7 @@ func (rt *runtime) toObject(value Value) *object { case valueObject: return value.object() default: - panic(rt.panicTypeError()) + panic(rt.panicTypeError("toObject unknown kind %s", value.kind)) } } @@ -176,19 +176,18 @@ func (rt *runtime) objectCoerce(value Value) (*object, error) { case valueObject: return value.object(), nil default: - panic(rt.panicTypeError()) + panic(rt.panicTypeError("objectCoerce unknown kind %s", value.kind)) } } func checkObjectCoercible(rt *runtime, value Value) { isObject, mustCoerce := testObjectCoercible(value) if !isObject && !mustCoerce { - panic(rt.panicTypeError()) + panic(rt.panicTypeError("checkObjectCoercible not object or mustCoerce")) } } -// testObjectCoercible - +// testObjectCoercible. func testObjectCoercible(value Value) (isObject, mustCoerce bool) { //nolint: nonamedreturns switch value.kind { case valueReference, valueEmpty, valueNull, valueUndefined: @@ -198,7 +197,7 @@ func testObjectCoercible(value Value) (isObject, mustCoerce bool) { //nolint: no case valueObject: return true, false default: - panic("this should never happen") + panic(fmt.Sprintf("testObjectCoercible unknown kind %s", value.kind)) } } @@ -534,7 +533,7 @@ func (rt *runtime) convertCallParameter(v Value, t reflect.Type) (reflect.Value, r, err := rt.convertCallParameter(rv, t.Out(0)) if err != nil { - panic(rt.panicTypeError(err.Error())) + panic(rt.panicTypeError("convertCallParameter Func: %s", err)) } return []reflect.Value{r} diff --git a/runtime_test.go b/runtime_test.go index 7036f640..d7b861e8 100644 --- a/runtime_test.go +++ b/runtime_test.go @@ -627,11 +627,11 @@ func Test_instanceof(t *testing.T) { test(`raise: abc = {} instanceof "abc"; - `, "TypeError: Expecting a function in instanceof check, but got: abc") + `, "TypeError: invalid kind String for instanceof (expected object)") test(`raise: "xyzzy" instanceof Math; - `, "TypeError") + `, "TypeError: Object.hasInstance not callable") }) } diff --git a/stash.go b/stash.go index fcbb7173..40aeb858 100644 --- a/stash.go +++ b/stash.go @@ -194,7 +194,7 @@ func (s *dclStash) getBinding(name string, throw bool) Value { } if !prop.mutable && !prop.readable { if throw { // strict? - panic(s.rt.panicTypeError()) + panic(s.rt.panicTypeError("getBinding property %s not mutable and not readable", name)) } return Value{} } diff --git a/type_array.go b/type_array.go index 0907d389..cb673544 100644 --- a/type_array.go +++ b/type_array.go @@ -55,6 +55,13 @@ func arrayDefineOwnProperty(obj *object, name string, descriptor property, throw if !valid { panic("Array.length != Value{}") } + + reject := func(reason string) bool { + if throw { + panic(obj.runtime.panicTypeError("Array.DefineOwnProperty %s", reason)) + } + return false + } length := lengthValue.value.(uint32) if name == propertyLength { if descriptor.value == nil { @@ -62,7 +69,7 @@ func arrayDefineOwnProperty(obj *object, name string, descriptor property, throw } newLengthValue, isValue := descriptor.value.(Value) if !isValue { - panic(obj.runtime.panicTypeError()) + panic(obj.runtime.panicTypeError("Array.DefineOwnProperty %q is not a value", descriptor.value)) } newLength := arrayUint32(obj.runtime, newLengthValue) descriptor.value = uint32Value(newLength) @@ -70,7 +77,7 @@ func arrayDefineOwnProperty(obj *object, name string, descriptor property, throw return objectDefineOwnProperty(obj, name, descriptor, throw) } if !lengthProperty.writable() { - goto Reject + return reject("property length for not writable") } newWritable := true if descriptor.mode&0o700 == 0 { @@ -89,7 +96,7 @@ func arrayDefineOwnProperty(obj *object, name string, descriptor property, throw descriptor.mode &= 0o077 } objectDefineOwnProperty(obj, name, descriptor, false) - goto Reject + return reject("delete failed") } } if !newWritable { @@ -98,10 +105,10 @@ func arrayDefineOwnProperty(obj *object, name string, descriptor property, throw } } else if index := stringToArrayIndex(name); index >= 0 { if index >= int64(length) && !lengthProperty.writable() { - goto Reject + return reject("property length not writable") } if !objectDefineOwnProperty(obj, strconv.FormatInt(index, 10), descriptor, false) { - goto Reject + return reject("Object.DefineOwnProperty failed") } if index >= int64(length) { lengthProperty.value = uint32Value(uint32(index + 1)) @@ -110,9 +117,4 @@ func arrayDefineOwnProperty(obj *object, name string, descriptor property, throw } } return objectDefineOwnProperty(obj, name, descriptor, throw) -Reject: - if throw { - panic(obj.runtime.panicTypeError()) - } - return false } diff --git a/type_date.go b/type_date.go index d5482e26..ac956760 100644 --- a/type_date.go +++ b/type_date.go @@ -140,8 +140,11 @@ func (o *object) dateValue() dateObject { } func dateObjectOf(rt *runtime, date *object) dateObject { - if date == nil || date.class != classDateName { - panic(rt.panicTypeError()) + if date == nil { + panic(rt.panicTypeError("Date.ObjectOf is nil")) + } + if date.class != classDateName { + panic(rt.panicTypeError("Date.ObjectOf %q != %q", date.class, classDateName)) } return date.dateValue() } diff --git a/type_function.go b/type_function.go index 28f6f9b8..bd407704 100644 --- a/type_function.go +++ b/type_function.go @@ -107,8 +107,9 @@ func (fn bindFunctionObject) construct(argumentList []Value) Value { case nodeFunctionObject: argumentList = append(fn.argumentList, argumentList...) return obj.construct(argumentList) + default: + panic(fn.target.runtime.panicTypeError("construct unknown type %T", obj.value)) } - panic(fn.target.runtime.panicTypeError()) } // nodeFunctionObject. @@ -246,14 +247,14 @@ func (o *object) construct(argumentList []Value) Value { func (o *object) hasInstance(of Value) bool { if !o.isCall() { // We should not have a hasInstance method - panic(o.runtime.panicTypeError()) + panic(o.runtime.panicTypeError("Object.hasInstance not callable")) } if !of.IsObject() { return false } prototype := o.get("prototype") if !prototype.IsObject() { - panic(o.runtime.panicTypeError()) + panic(o.runtime.panicTypeError("Object.hasInstance prototype %q is not an object", prototype)) } prototypeObject := prototype.object() @@ -306,7 +307,7 @@ func (f *FunctionCall) thisObject() *object { func (f *FunctionCall) thisClassObject(class string) *object { if o := f.thisObject(); o.class != class { - panic(f.runtime.panicTypeError()) + panic(f.runtime.panicTypeError("Function.Class %s != %s", o.class, class)) } return f.thisObj } diff --git a/type_go_struct.go b/type_go_struct.go index 8cae329a..e191c57c 100644 --- a/type_go_struct.go +++ b/type_go_struct.go @@ -69,7 +69,7 @@ func (o goStructObject) setValue(rt *runtime, name string, value Value) bool { fieldValue := o.getValue(name) converted, err := rt.convertCallParameter(value, fieldValue.Type()) if err != nil { - panic(rt.panicTypeError(err.Error())) + panic(rt.panicTypeError("Object.setValue convertCallParameter: %s", err)) } fieldValue.Set(converted) diff --git a/value.go b/value.go index 521e08a2..1f86e144 100644 --- a/value.go +++ b/value.go @@ -118,10 +118,7 @@ func (v Value) call(rt *runtime, this Value, argumentList ...interface{}) Value if function, ok := v.value.(*object); ok { return function.call(this, function.runtime.toValueArray(argumentList...), false, nativeFrame) } - if rt == nil { - panic("FIXME TypeError") - } - panic(rt.panicTypeError()) + panic(rt.panicTypeError("call %q is not an object", v.value)) } func (v Value) constructSafe(rt *runtime, this Value, argumentList ...interface{}) (Value, error) { @@ -136,10 +133,7 @@ func (v Value) construct(rt *runtime, this Value, argumentList ...interface{}) V if fn, ok := v.value.(*object); ok { return fn.construct(fn.runtime.toValueArray(argumentList...)) } - if rt == nil { - panic("FIXME TypeError") - } - panic(rt.panicTypeError()) + panic(rt.panicTypeError("construct %q is not an object", v.value)) } // IsPrimitive will return true if value is a primitive (any kind of primitive). diff --git a/value_kind.gen.go b/value_kind.gen.go new file mode 100644 index 00000000..cf99c0a7 --- /dev/null +++ b/value_kind.gen.go @@ -0,0 +1,31 @@ +// Code generated by "stringer -type=valueKind -trimprefix=value -output=value_kind.gen.go"; DO NOT EDIT. + +package otto + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[valueUndefined-0] + _ = x[valueNull-1] + _ = x[valueNumber-2] + _ = x[valueString-3] + _ = x[valueBoolean-4] + _ = x[valueObject-5] + _ = x[valueEmpty-6] + _ = x[valueResult-7] + _ = x[valueReference-8] +} + +const _valueKind_name = "UndefinedNullNumberStringBooleanObjectEmptyResultReference" + +var _valueKind_index = [...]uint8{0, 9, 13, 19, 25, 32, 38, 43, 49, 58} + +func (i valueKind) String() string { + if i < 0 || i >= valueKind(len(_valueKind_index)-1) { + return "valueKind(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _valueKind_name[_valueKind_index[i]:_valueKind_index[i+1]] +}