mirror of
https://github.com/robertkrimen/otto
synced 2025-10-05 19:19:10 +08:00
make call stacks aware of native functions
* add stackFramesToPop argument to error factories * put native functions in their own stack frames * add tests for native stack frames * amend Context functionality to account for native frames
This commit is contained in:
parent
3a69c44018
commit
668c95f04e
|
@ -5,11 +5,11 @@ import (
|
|||
)
|
||||
|
||||
func builtinError(call FunctionCall) Value {
|
||||
return toValue_object(call.runtime.newError("Error", call.Argument(0)))
|
||||
return toValue_object(call.runtime.newError("Error", call.Argument(0), 1))
|
||||
}
|
||||
|
||||
func builtinNewError(self *_object, argumentList []Value) Value {
|
||||
return toValue_object(self.runtime.newError("Error", valueOfArrayIndex(argumentList, 0)))
|
||||
return toValue_object(self.runtime.newError("Error", valueOfArrayIndex(argumentList, 0), 0))
|
||||
}
|
||||
|
||||
func builtinError_toString(call FunctionCall) Value {
|
||||
|
@ -42,7 +42,7 @@ func builtinError_toString(call FunctionCall) Value {
|
|||
}
|
||||
|
||||
func (runtime *_runtime) newEvalError(message Value) *_object {
|
||||
self := runtime.newErrorObject("EvalError", message)
|
||||
self := runtime.newErrorObject("EvalError", message, 0)
|
||||
self.prototype = runtime.global.EvalErrorPrototype
|
||||
return self
|
||||
}
|
||||
|
@ -56,7 +56,7 @@ func builtinNewEvalError(self *_object, argumentList []Value) Value {
|
|||
}
|
||||
|
||||
func (runtime *_runtime) newTypeError(message Value) *_object {
|
||||
self := runtime.newErrorObject("TypeError", message)
|
||||
self := runtime.newErrorObject("TypeError", message, 0)
|
||||
self.prototype = runtime.global.TypeErrorPrototype
|
||||
return self
|
||||
}
|
||||
|
@ -70,7 +70,7 @@ func builtinNewTypeError(self *_object, argumentList []Value) Value {
|
|||
}
|
||||
|
||||
func (runtime *_runtime) newRangeError(message Value) *_object {
|
||||
self := runtime.newErrorObject("RangeError", message)
|
||||
self := runtime.newErrorObject("RangeError", message, 0)
|
||||
self.prototype = runtime.global.RangeErrorPrototype
|
||||
return self
|
||||
}
|
||||
|
@ -84,13 +84,13 @@ func builtinNewRangeError(self *_object, argumentList []Value) Value {
|
|||
}
|
||||
|
||||
func (runtime *_runtime) newURIError(message Value) *_object {
|
||||
self := runtime.newErrorObject("URIError", message)
|
||||
self := runtime.newErrorObject("URIError", message, 0)
|
||||
self.prototype = runtime.global.URIErrorPrototype
|
||||
return self
|
||||
}
|
||||
|
||||
func (runtime *_runtime) newReferenceError(message Value) *_object {
|
||||
self := runtime.newErrorObject("ReferenceError", message)
|
||||
self := runtime.newErrorObject("ReferenceError", message, 0)
|
||||
self.prototype = runtime.global.ReferenceErrorPrototype
|
||||
return self
|
||||
}
|
||||
|
@ -104,7 +104,7 @@ func builtinNewReferenceError(self *_object, argumentList []Value) Value {
|
|||
}
|
||||
|
||||
func (runtime *_runtime) newSyntaxError(message Value) *_object {
|
||||
self := runtime.newErrorObject("SyntaxError", message)
|
||||
self := runtime.newErrorObject("SyntaxError", message, 0)
|
||||
self.prototype = runtime.global.SyntaxErrorPrototype
|
||||
return self
|
||||
}
|
||||
|
|
62
error.go
62
error.go
|
@ -50,9 +50,12 @@ func (err _error) formatWithStack() string {
|
|||
}
|
||||
|
||||
type _frame struct {
|
||||
file *file.File
|
||||
offset int
|
||||
callee string
|
||||
native bool
|
||||
nativeFile string
|
||||
nativeLine int
|
||||
file *file.File
|
||||
offset int
|
||||
callee string
|
||||
}
|
||||
|
||||
var (
|
||||
|
@ -62,23 +65,24 @@ var (
|
|||
type _at int
|
||||
|
||||
func (fr _frame) location() string {
|
||||
if fr.file == nil {
|
||||
return "<unknown>"
|
||||
}
|
||||
str := "<unknown>"
|
||||
|
||||
var str string
|
||||
|
||||
p := fr.file.Position(file.Idx(fr.offset))
|
||||
if p == nil {
|
||||
str = "<unknown>"
|
||||
} else {
|
||||
path, line, column := p.Filename, p.Line, p.Column
|
||||
|
||||
if path == "" {
|
||||
path = "<anonymous>"
|
||||
switch {
|
||||
case fr.native:
|
||||
str = "<native code>"
|
||||
if fr.nativeFile != "" && fr.nativeLine != 0 {
|
||||
str = fmt.Sprintf("%s:%d", fr.nativeFile, fr.nativeLine)
|
||||
}
|
||||
case fr.file != nil:
|
||||
if p := fr.file.Position(file.Idx(fr.offset)); p != nil {
|
||||
path, line, column := p.Filename, p.Line, p.Column
|
||||
|
||||
str = fmt.Sprintf("%s:%d:%d", path, line, column)
|
||||
if path == "" {
|
||||
path = "<anonymous>"
|
||||
}
|
||||
|
||||
str = fmt.Sprintf("%s:%d:%d", path, line, column)
|
||||
}
|
||||
}
|
||||
|
||||
if fr.callee != "" {
|
||||
|
@ -130,7 +134,7 @@ func (rt *_runtime) typeErrorResult(throw bool) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
func newError(rt *_runtime, name string, in ...interface{}) _error {
|
||||
func newError(rt *_runtime, name string, stackFramesToPop int, in ...interface{}) _error {
|
||||
err := _error{
|
||||
name: name,
|
||||
offset: -1,
|
||||
|
@ -140,7 +144,15 @@ func newError(rt *_runtime, name string, in ...interface{}) _error {
|
|||
|
||||
if rt != nil {
|
||||
scope := rt.scope
|
||||
|
||||
for i := 0; i < stackFramesToPop; i++ {
|
||||
if scope.outer != nil {
|
||||
scope = scope.outer
|
||||
}
|
||||
}
|
||||
|
||||
frame := scope.frame
|
||||
|
||||
if length > 0 {
|
||||
if at, ok := in[length-1].(_at); ok {
|
||||
in = in[0 : length-1]
|
||||
|
@ -173,36 +185,37 @@ func newError(rt *_runtime, name string, in ...interface{}) _error {
|
|||
}
|
||||
}
|
||||
err.message = err.describe(description, in...)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (rt *_runtime) panicTypeError(argumentList ...interface{}) *_exception {
|
||||
return &_exception{
|
||||
value: newError(rt, "TypeError", argumentList...),
|
||||
value: newError(rt, "TypeError", 0, argumentList...),
|
||||
}
|
||||
}
|
||||
|
||||
func (rt *_runtime) panicReferenceError(argumentList ...interface{}) *_exception {
|
||||
return &_exception{
|
||||
value: newError(rt, "ReferenceError", argumentList...),
|
||||
value: newError(rt, "ReferenceError", 0, argumentList...),
|
||||
}
|
||||
}
|
||||
|
||||
func (rt *_runtime) panicURIError(argumentList ...interface{}) *_exception {
|
||||
return &_exception{
|
||||
value: newError(rt, "URIError", argumentList...),
|
||||
value: newError(rt, "URIError", 0, argumentList...),
|
||||
}
|
||||
}
|
||||
|
||||
func (rt *_runtime) panicSyntaxError(argumentList ...interface{}) *_exception {
|
||||
return &_exception{
|
||||
value: newError(rt, "SyntaxError", argumentList...),
|
||||
value: newError(rt, "SyntaxError", 0, argumentList...),
|
||||
}
|
||||
}
|
||||
|
||||
func (rt *_runtime) panicRangeError(argumentList ...interface{}) *_exception {
|
||||
return &_exception{
|
||||
value: newError(rt, "RangeError", argumentList...),
|
||||
value: newError(rt, "RangeError", 0, argumentList...),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -213,6 +226,9 @@ func catchPanic(function func()) (err error) {
|
|||
caught = exception.eject()
|
||||
}
|
||||
switch caught := caught.(type) {
|
||||
case *Error:
|
||||
err = caught
|
||||
return
|
||||
case _error:
|
||||
err = &Error{caught}
|
||||
return
|
||||
|
|
39
error_native_test.go
Normal file
39
error_native_test.go
Normal file
|
@ -0,0 +1,39 @@
|
|||
package otto
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
// this is its own file because the tests in it rely on the line numbers of
|
||||
// some of the functions defined here. putting it in with the rest of the
|
||||
// tests would probably be annoying.
|
||||
|
||||
func TestErrorContextNative(t *testing.T) {
|
||||
tt(t, func() {
|
||||
vm := New()
|
||||
|
||||
vm.Set("N", func(c FunctionCall) Value {
|
||||
v, err := c.Argument(0).Call(NullValue())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return v
|
||||
})
|
||||
|
||||
s, _ := vm.Compile("test.js", `
|
||||
function F() { throw new Error('wow'); }
|
||||
function G() { return N(F); }
|
||||
`)
|
||||
|
||||
vm.Run(s)
|
||||
|
||||
f1, _ := vm.Get("G")
|
||||
_, err := f1.Call(NullValue())
|
||||
err1 := err.(*Error)
|
||||
is(err1.message, "wow")
|
||||
is(len(err1.trace), 3)
|
||||
is(err1.trace[0].location(), "F (test.js:2:29)")
|
||||
is(err1.trace[1].location(), "github.com/robertkrimen/otto.TestErrorContextNative.func1.1 (error_native_test.go:15)")
|
||||
is(err1.trace[2].location(), "G (test.js:3:26)")
|
||||
})
|
||||
}
|
48
function_stack_test.go
Normal file
48
function_stack_test.go
Normal file
|
@ -0,0 +1,48 @@
|
|||
package otto
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
// this is its own file because the tests in it rely on the line numbers of
|
||||
// some of the functions defined here. putting it in with the rest of the
|
||||
// tests would probably be annoying.
|
||||
|
||||
func TestFunction_stack(t *testing.T) {
|
||||
tt(t, func() {
|
||||
vm := New()
|
||||
|
||||
s, _ := vm.Compile("fake.js", `function X(fn1, fn2, fn3) { fn1(fn2, fn3); }`)
|
||||
vm.Run(s)
|
||||
|
||||
expected := []_frame{
|
||||
{native: true, nativeFile: "function_stack_test.go", nativeLine: 30, offset: 0, callee: "github.com/robertkrimen/otto.TestFunction_stack.func1.2"},
|
||||
{native: true, nativeFile: "function_stack_test.go", nativeLine: 25, offset: 0, callee: "github.com/robertkrimen/otto.TestFunction_stack.func1.1"},
|
||||
{native: false, nativeFile: "", nativeLine: 0, offset: 29, callee: "X", file: s.program.file},
|
||||
{native: false, nativeFile: "", nativeLine: 0, offset: 29, callee: "X", file: s.program.file},
|
||||
}
|
||||
|
||||
vm.Set("A", func(c FunctionCall) Value {
|
||||
c.Argument(0).Call(UndefinedValue())
|
||||
return UndefinedValue()
|
||||
})
|
||||
|
||||
vm.Set("B", func(c FunctionCall) Value {
|
||||
depth := 0
|
||||
for scope := c.Otto.runtime.scope; scope != nil; scope = scope.outer {
|
||||
is(scope.frame, expected[depth])
|
||||
depth++
|
||||
}
|
||||
|
||||
is(depth, 4)
|
||||
|
||||
return UndefinedValue()
|
||||
})
|
||||
|
||||
x, _ := vm.Get("X")
|
||||
a, _ := vm.Get("A")
|
||||
b, _ := vm.Get("B")
|
||||
|
||||
x.Call(UndefinedValue(), x, a, b)
|
||||
})
|
||||
}
|
|
@ -166,7 +166,7 @@ func (runtime *_runtime) newDate(epoch float64) *_object {
|
|||
return self
|
||||
}
|
||||
|
||||
func (runtime *_runtime) newError(name string, message Value) *_object {
|
||||
func (runtime *_runtime) newError(name string, message Value, stackFramesToPop int) *_object {
|
||||
var self *_object
|
||||
switch name {
|
||||
case "EvalError":
|
||||
|
@ -183,7 +183,7 @@ func (runtime *_runtime) newError(name string, message Value) *_object {
|
|||
return runtime.newURIError(message)
|
||||
}
|
||||
|
||||
self = runtime.newErrorObject(name, message)
|
||||
self = runtime.newErrorObject(name, message, stackFramesToPop)
|
||||
self.prototype = runtime.global.ErrorPrototype
|
||||
if name != "" {
|
||||
self.defineProperty("name", toValue_string(name), 0111, false)
|
||||
|
@ -191,8 +191,8 @@ func (runtime *_runtime) newError(name string, message Value) *_object {
|
|||
return self
|
||||
}
|
||||
|
||||
func (runtime *_runtime) newNativeFunction(name string, _nativeFunction _nativeFunction) *_object {
|
||||
self := runtime.newNativeFunctionObject(name, _nativeFunction, 0)
|
||||
func (runtime *_runtime) newNativeFunction(name, file string, line int, _nativeFunction _nativeFunction) *_object {
|
||||
self := runtime.newNativeFunctionObject(name, file, line, _nativeFunction, 0)
|
||||
self.prototype = runtime.global.FunctionPrototype
|
||||
prototype := runtime.newObject()
|
||||
self.defineProperty("prototype", toValue_object(prototype), 0100, false)
|
||||
|
|
111
native_stack_test.go
Normal file
111
native_stack_test.go
Normal file
|
@ -0,0 +1,111 @@
|
|||
package otto
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNativeStackFrames(t *testing.T) {
|
||||
tt(t, func() {
|
||||
vm := New()
|
||||
|
||||
s, err := vm.Compile("input.js", `
|
||||
function A() { ext1(); }
|
||||
function B() { ext2(); }
|
||||
A();
|
||||
`)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
vm.Set("ext1", func(c FunctionCall) Value {
|
||||
if _, err := c.Otto.Eval("B()"); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return UndefinedValue()
|
||||
})
|
||||
|
||||
vm.Set("ext2", func(c FunctionCall) Value {
|
||||
{
|
||||
// no limit, include innermost native frames
|
||||
ctx := c.Otto.ContextSkip(-1, false)
|
||||
|
||||
is(ctx.Stacktrace, []string{
|
||||
"github.com/robertkrimen/otto.TestNativeStackFrames.func1.2 (native_stack_test.go:28)",
|
||||
"B (input.js:3:22)",
|
||||
"github.com/robertkrimen/otto.TestNativeStackFrames.func1.1 (native_stack_test.go:20)",
|
||||
"A (input.js:2:22)", "input.js:4:7",
|
||||
})
|
||||
|
||||
is(ctx.Callee, "github.com/robertkrimen/otto.TestNativeStackFrames.func1.2")
|
||||
is(ctx.Filename, "native_stack_test.go")
|
||||
is(ctx.Line, 28)
|
||||
is(ctx.Column, 0)
|
||||
}
|
||||
|
||||
{
|
||||
// no limit, skip innermost native frames
|
||||
ctx := c.Otto.ContextSkip(-1, true)
|
||||
|
||||
is(ctx.Stacktrace, []string{
|
||||
"B (input.js:3:22)",
|
||||
"github.com/robertkrimen/otto.TestNativeStackFrames.func1.1 (native_stack_test.go:20)",
|
||||
"A (input.js:2:22)", "input.js:4:7",
|
||||
})
|
||||
|
||||
is(ctx.Callee, "B")
|
||||
is(ctx.Filename, "input.js")
|
||||
is(ctx.Line, 3)
|
||||
is(ctx.Column, 22)
|
||||
}
|
||||
|
||||
if _, err := c.Otto.Eval("ext3()"); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return UndefinedValue()
|
||||
})
|
||||
|
||||
vm.Set("ext3", func(c FunctionCall) Value {
|
||||
{
|
||||
// no limit, include innermost native frames
|
||||
ctx := c.Otto.ContextSkip(-1, false)
|
||||
|
||||
is(ctx.Stacktrace, []string{
|
||||
"github.com/robertkrimen/otto.TestNativeStackFrames.func1.3 (native_stack_test.go:69)",
|
||||
"github.com/robertkrimen/otto.TestNativeStackFrames.func1.2 (native_stack_test.go:28)",
|
||||
"B (input.js:3:22)",
|
||||
"github.com/robertkrimen/otto.TestNativeStackFrames.func1.1 (native_stack_test.go:20)",
|
||||
"A (input.js:2:22)", "input.js:4:7",
|
||||
})
|
||||
|
||||
is(ctx.Callee, "github.com/robertkrimen/otto.TestNativeStackFrames.func1.3")
|
||||
is(ctx.Filename, "native_stack_test.go")
|
||||
is(ctx.Line, 69)
|
||||
is(ctx.Column, 0)
|
||||
}
|
||||
|
||||
{
|
||||
// no limit, skip innermost native frames
|
||||
ctx := c.Otto.ContextSkip(-1, true)
|
||||
|
||||
is(ctx.Stacktrace, []string{
|
||||
"B (input.js:3:22)",
|
||||
"github.com/robertkrimen/otto.TestNativeStackFrames.func1.1 (native_stack_test.go:20)",
|
||||
"A (input.js:2:22)", "input.js:4:7",
|
||||
})
|
||||
|
||||
is(ctx.Callee, "B")
|
||||
is(ctx.Filename, "input.js")
|
||||
is(ctx.Line, 3)
|
||||
is(ctx.Column, 22)
|
||||
}
|
||||
|
||||
return UndefinedValue()
|
||||
})
|
||||
|
||||
if _, err := vm.Run(s); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
})
|
||||
}
|
40
otto.go
40
otto.go
|
@ -383,7 +383,7 @@ func (self Otto) SetStackDepthLimit(limit int) {
|
|||
// MakeCustomError creates a new Error object with the given name and message,
|
||||
// returning it as a Value.
|
||||
func (self Otto) MakeCustomError(name, message string) Value {
|
||||
return self.runtime.toValue(self.runtime.newError(name, self.runtime.toValue(message)))
|
||||
return self.runtime.toValue(self.runtime.newError(name, self.runtime.toValue(message), 0))
|
||||
}
|
||||
|
||||
// MakeRangeError creates a new RangeError object with the given message,
|
||||
|
@ -416,8 +416,23 @@ type Context struct {
|
|||
Stacktrace []string
|
||||
}
|
||||
|
||||
// Context returns the current execution context of the vm
|
||||
func (self Otto) Context() (ctx Context) {
|
||||
// Context returns the current execution context of the vm, traversing up to
|
||||
// ten stack frames, and skipping any innermost native function stack frames.
|
||||
func (self Otto) Context() Context {
|
||||
return self.ContextSkip(10, true)
|
||||
}
|
||||
|
||||
// ContextLimit returns the current execution context of the vm, with a
|
||||
// specific limit on the number of stack frames to traverse, skipping any
|
||||
// innermost native function stack frames.
|
||||
func (self Otto) ContextLimit(limit int) Context {
|
||||
return self.ContextSkip(limit, true)
|
||||
}
|
||||
|
||||
// ContextSkip returns the current execution context of the vm, with a
|
||||
// specific limit on the number of stack frames to traverse, optionally
|
||||
// skipping any innermost native function stack frames.
|
||||
func (self Otto) ContextSkip(limit int, skipNative bool) (ctx Context) {
|
||||
// Ensure we are operating in a scope
|
||||
if self.runtime.scope == nil {
|
||||
self.runtime.enterGlobalScope()
|
||||
|
@ -427,14 +442,24 @@ func (self Otto) Context() (ctx Context) {
|
|||
scope := self.runtime.scope
|
||||
frame := scope.frame
|
||||
|
||||
for skipNative && frame.native && scope.outer != nil {
|
||||
scope = scope.outer
|
||||
frame = scope.frame
|
||||
}
|
||||
|
||||
// Get location information
|
||||
ctx.Filename = "<unknown>"
|
||||
ctx.Callee = frame.callee
|
||||
if frame.file != nil {
|
||||
|
||||
switch {
|
||||
case frame.native:
|
||||
ctx.Filename = frame.nativeFile
|
||||
ctx.Line = frame.nativeLine
|
||||
ctx.Column = 0
|
||||
case frame.file != nil:
|
||||
ctx.Filename = "<anonymous>"
|
||||
|
||||
p := frame.file.Position(file.Idx(frame.offset))
|
||||
if p != nil {
|
||||
if p := frame.file.Position(file.Idx(frame.offset)); p != nil {
|
||||
ctx.Line = p.Line
|
||||
ctx.Column = p.Column
|
||||
|
||||
|
@ -448,10 +473,9 @@ func (self Otto) Context() (ctx Context) {
|
|||
ctx.This = toValue_object(scope.this)
|
||||
|
||||
// Build stacktrace (up to 10 levels deep)
|
||||
limit := 10
|
||||
ctx.Symbols = make(map[string]Value)
|
||||
ctx.Stacktrace = append(ctx.Stacktrace, frame.location())
|
||||
for limit > 0 {
|
||||
for limit != 0 {
|
||||
// Get variables
|
||||
stash := scope.lexical
|
||||
for {
|
||||
|
|
43
runtime.go
43
runtime.go
|
@ -4,7 +4,9 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"path"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"sync"
|
||||
|
||||
"github.com/robertkrimen/otto/ast"
|
||||
|
@ -123,7 +125,7 @@ func (self *_runtime) tryCatchEvaluate(inner func() Value) (tryValue Value, exce
|
|||
switch caught := caught.(type) {
|
||||
case _error:
|
||||
exception = true
|
||||
tryValue = toValue_object(self.newError(caught.name, caught.messageValue()))
|
||||
tryValue = toValue_object(self.newError(caught.name, caught.messageValue(), 0))
|
||||
case Value:
|
||||
exception = true
|
||||
tryValue = caught
|
||||
|
@ -351,9 +353,27 @@ func (self *_runtime) toValue(value interface{}) Value {
|
|||
case Value:
|
||||
return value
|
||||
case func(FunctionCall) Value:
|
||||
return toValue_object(self.newNativeFunction("", value))
|
||||
var name, file string
|
||||
var line int
|
||||
pc := reflect.ValueOf(value).Pointer()
|
||||
fn := runtime.FuncForPC(pc)
|
||||
if fn != nil {
|
||||
name = fn.Name()
|
||||
file, line = fn.FileLine(pc)
|
||||
file = path.Base(file)
|
||||
}
|
||||
return toValue_object(self.newNativeFunction(name, file, line, value))
|
||||
case _nativeFunction:
|
||||
return toValue_object(self.newNativeFunction("", value))
|
||||
var name, file string
|
||||
var line int
|
||||
pc := reflect.ValueOf(value).Pointer()
|
||||
fn := runtime.FuncForPC(pc)
|
||||
if fn != nil {
|
||||
name = fn.Name()
|
||||
file, line = fn.FileLine(pc)
|
||||
file = path.Base(file)
|
||||
}
|
||||
return toValue_object(self.newNativeFunction(name, file, line, value))
|
||||
case Object, *Object, _object, *_object:
|
||||
// Nothing happens.
|
||||
// FIXME We should really figure out what can come here.
|
||||
|
@ -370,10 +390,23 @@ func (self *_runtime) toValue(value interface{}) Value {
|
|||
return toValue_object(self.newGoArray(value))
|
||||
}
|
||||
case reflect.Func:
|
||||
var name, file string
|
||||
var line int
|
||||
v := reflect.ValueOf(value)
|
||||
if v.Kind() == reflect.Ptr {
|
||||
pc := v.Pointer()
|
||||
fn := runtime.FuncForPC(pc)
|
||||
if fn != nil {
|
||||
name = fn.Name()
|
||||
file, line = fn.FileLine(pc)
|
||||
file = path.Base(file)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO Maybe cache this?
|
||||
return toValue_object(self.newNativeFunction("", func(call FunctionCall) Value {
|
||||
return toValue_object(self.newNativeFunction(name, file, line, func(call FunctionCall) Value {
|
||||
argsCount := len(call.ArgumentList)
|
||||
in := make([]reflect.Value, argsCount)
|
||||
in := make([]reflect.Value, len(call.ArgumentList))
|
||||
t := value.Type()
|
||||
callSlice := false
|
||||
paramsCount := t.NumIn()
|
||||
|
|
|
@ -1,18 +1,18 @@
|
|||
package otto
|
||||
|
||||
func (rt *_runtime) newErrorObject(name string, message Value) *_object {
|
||||
func (rt *_runtime) newErrorObject(name string, message Value, stackFramesToPop int) *_object {
|
||||
self := rt.newClassObject("Error")
|
||||
if message.IsDefined() {
|
||||
msg := message.string()
|
||||
self.defineProperty("message", toValue_string(msg), 0111, false)
|
||||
self.value = newError(rt, name, msg)
|
||||
self.value = newError(rt, name, stackFramesToPop, msg)
|
||||
} else {
|
||||
self.value = newError(rt, name)
|
||||
self.value = newError(rt, name, stackFramesToPop)
|
||||
}
|
||||
|
||||
self.defineOwnProperty("stack", _property{
|
||||
value: _propertyGetSet{
|
||||
rt.newNativeFunction("get", func(FunctionCall) Value {
|
||||
rt.newNativeFunction("get", "internal", 0, func(FunctionCall) Value {
|
||||
return toValue_string(self.value.(_error).formatWithStack())
|
||||
}),
|
||||
&_nilGetSetObject,
|
||||
|
|
|
@ -31,13 +31,18 @@ type _nativeFunction func(FunctionCall) Value
|
|||
|
||||
type _nativeFunctionObject struct {
|
||||
name string
|
||||
file string
|
||||
line int
|
||||
call _nativeFunction // [[Call]]
|
||||
construct _constructFunction // [[Construct]]
|
||||
}
|
||||
|
||||
func (runtime *_runtime) newNativeFunctionObject(name string, native _nativeFunction, length int) *_object {
|
||||
func (runtime *_runtime) newNativeFunctionObject(name, file string, line int, native _nativeFunction, length int) *_object {
|
||||
self := runtime.newClassObject("Function")
|
||||
self.value = _nativeFunctionObject{
|
||||
name: name,
|
||||
file: file,
|
||||
line: line,
|
||||
call: native,
|
||||
construct: defaultConstruct,
|
||||
}
|
||||
|
@ -125,11 +130,27 @@ func (self *_object) call(this Value, argumentList []Value, eval bool, frame _fr
|
|||
switch fn := self.value.(type) {
|
||||
|
||||
case _nativeFunctionObject:
|
||||
// TODO Enter a scope, name from the native object...
|
||||
// Since eval is a native function, we only have to check for it here
|
||||
if eval {
|
||||
eval = self == self.runtime.eval // If eval is true, then it IS a direct eval
|
||||
}
|
||||
|
||||
// Enter a scope, name from the native object...
|
||||
rt := self.runtime
|
||||
if rt.scope != nil && !eval {
|
||||
rt.enterFunctionScope(rt.scope.lexical, this)
|
||||
rt.scope.frame = _frame{
|
||||
native: true,
|
||||
nativeFile: fn.file,
|
||||
nativeLine: fn.line,
|
||||
callee: fn.name,
|
||||
file: nil,
|
||||
}
|
||||
defer func() {
|
||||
rt.leaveScope()
|
||||
}()
|
||||
}
|
||||
|
||||
return fn.call(FunctionCall{
|
||||
runtime: self.runtime,
|
||||
eval: eval,
|
||||
|
@ -149,7 +170,7 @@ func (self *_object) call(this Value, argumentList []Value, eval bool, frame _fr
|
|||
stash := rt.enterFunctionScope(fn.stash, this)
|
||||
rt.scope.frame = _frame{
|
||||
callee: fn.node.name,
|
||||
file: fn.node.file,
|
||||
file: fn.node.file,
|
||||
}
|
||||
defer func() {
|
||||
rt.leaveScope()
|
||||
|
@ -267,5 +288,5 @@ func (self FunctionCall) toObject(value Value) *_object {
|
|||
// CallerLocation will return file location information (file:line:pos) where this function is being called.
|
||||
func (self FunctionCall) CallerLocation() string {
|
||||
// see error.go for location()
|
||||
return self.runtime.scope.frame.location()
|
||||
return self.runtime.scope.outer.frame.location()
|
||||
}
|
||||
|
|
8
value.go
8
value.go
|
@ -271,11 +271,11 @@ func toValue_reflectValuePanic(value interface{}, kind reflect.Kind) {
|
|||
// FIXME?
|
||||
switch kind {
|
||||
case reflect.Struct:
|
||||
panic(newError(nil, "TypeError", "invalid value (struct): missing runtime: %v (%T)", value, value))
|
||||
panic(newError(nil, "TypeError", 0, "invalid value (struct): missing runtime: %v (%T)", value, value))
|
||||
case reflect.Map:
|
||||
panic(newError(nil, "TypeError", "invalid value (map): missing runtime: %v (%T)", value, value))
|
||||
panic(newError(nil, "TypeError", 0, "invalid value (map): missing runtime: %v (%T)", value, value))
|
||||
case reflect.Slice:
|
||||
panic(newError(nil, "TypeError", "invalid value (slice): missing runtime: %v (%T)", value, value))
|
||||
panic(newError(nil, "TypeError", 0, "invalid value (slice): missing runtime: %v (%T)", value, value))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -375,7 +375,7 @@ func toValue(value interface{}) Value {
|
|||
return toValue(reflect.ValueOf(value))
|
||||
}
|
||||
// FIXME?
|
||||
panic(newError(nil, "TypeError", "invalid value: %v (%T)", value, value))
|
||||
panic(newError(nil, "TypeError", 0, "invalid value: %v (%T)", value, value))
|
||||
}
|
||||
|
||||
// String will return the value as a string.
|
||||
|
|
Loading…
Reference in New Issue
Block a user