1
0
mirror of https://github.com/robertkrimen/otto synced 2025-10-05 19:19:10 +08:00

Merge pull request #147 from darkliquid/issue-145

Add Context method to aid debugging
This commit is contained in:
Conrad Pankoff 2015-12-06 12:03:00 +11:00
commit f28ccbfeea
3 changed files with 218 additions and 1 deletions

69
otto.go
View File

@ -363,6 +363,75 @@ func (self Otto) SetDebuggerHandler(fn func(vm *Otto)) {
self.runtime.debugger = fn
}
// Context is a structure that contains information about the current execution
// context.
type Context struct {
Filename string
Line int
Column int
Callee string
Symbols map[string]Value
This Value
Stacktrace []string
}
// Context returns the current execution context of the vm
func (self Otto) Context() (ctx Context) {
// Ensure we are operating in a scope
if self.runtime.scope == nil {
self.runtime.enterGlobalScope()
defer self.runtime.leaveScope()
}
scope := self.runtime.scope
frame := scope.frame
// Get location information
ctx.Filename = "<unknown>"
ctx.Callee = frame.callee
if frame.file != nil {
ctx.Filename = frame.file.Name()
if ctx.Filename == "" {
ctx.Filename = "<anonymous>"
}
ctx.Line, ctx.Column = _position(frame.file, frame.offset)
}
// Get the current scope this Value
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 {
// Get variables
stash := scope.lexical
for {
for _, name := range getStashProperties(stash) {
if _, ok := ctx.Symbols[name]; !ok {
ctx.Symbols[name] = stash.getBinding(name, true)
}
}
stash = stash.outer()
if stash == nil || stash.outer() == nil {
break
}
}
scope = scope.outer
if scope == nil {
break
}
if scope.frame.offset >= 0 {
ctx.Stacktrace = append(ctx.Stacktrace, scope.frame.location())
}
limit--
}
return
}
// Call the given JavaScript with a given this and arguments.
//
// If this is nil, then some special handling takes place to determine the proper

View File

@ -360,7 +360,7 @@ func TestTryFinally(t *testing.T) {
finally {
def = 1;
continue;
}
}
def -= 1;
}
while (abc < 2)
@ -1473,6 +1473,133 @@ func TestOttoEval(t *testing.T) {
})
}
func TestOttoContext(t *testing.T) {
// These are all the builtin global scope symbols
builtins := []string{
"escape",
"URIError",
"RegExp",
"ReferenceError",
"parseFloat",
"parseInt",
"SyntaxError",
"decodeURIComponent",
"encodeURIComponent",
"Infinity",
"JSON",
"isNaN",
"unescape",
"decodeURI",
"Object",
"Function",
"RangeError",
"Error",
"get_context",
"eval",
"Number",
"Math",
"NaN",
"Date",
"Boolean",
"console",
"encodeURI",
"EvalError",
"Array",
"TypeError",
"String",
"isFinite",
"undefined",
}
tt(t, func() {
vm := New()
vm.Set("get_context", func(c FunctionCall) Value {
ctx := c.Otto.Context()
is(ctx.Callee, "f1")
is(ctx.Filename, "<anonymous>")
is(ctx.Line, 8)
is(ctx.Column, 5)
is(ctx.Stacktrace, []string{
"f1 (<anonymous>:8:5)",
"f2 (<anonymous>:15:5)",
"f3 (<anonymous>:19:5)",
"t (<anonymous>:22:4)",
})
is(len(ctx.Symbols), 9+len(builtins))
is(ctx.Symbols["a"], 1)
is(ctx.Symbols["b"], "hello")
is(ctx.Symbols["c"], true)
is(ctx.Symbols["j"], 2)
is(ctx.Symbols["f1"].IsFunction(), true)
is(ctx.Symbols["f2"].IsFunction(), true)
is(ctx.Symbols["f3"].IsFunction(), true)
is(ctx.Symbols["t"].IsFunction(), true)
callee, _ := ctx.Symbols["arguments"].Object().Get("callee")
is(callee.IsDefined(), true)
return Value{}
})
_, err := vm.Run(`(function t() {
var a = 1;
var b = 'hello';
var c = true;
function f1() {
var j = 2;
get_context();
(function() {
var d = 4;
})()
}
function f2() {
f1();
}
function f3() {
f2();
}
f3();
a = 2;
b = 'goodbye';
c = false;
}())`)
is(err, nil)
})
// this test makes sure that `Context` works on global scope by default, if
// there is not a current scope.
tt(t, func() {
vm := New()
vm.Set("get_context", func(c FunctionCall) Value {
ctx := c.Otto.Context()
is(ctx.Callee, "")
is(ctx.Filename, "<anonymous>")
is(ctx.Line, 3)
is(ctx.Column, 4)
is(ctx.Stacktrace, []string{"<anonymous>:3:4"})
is(len(ctx.Symbols), 2+len(builtins))
is(ctx.Symbols["a"], 1)
is(ctx.Symbols["b"], UndefinedValue())
return Value{}
})
_, err := vm.Run(`
var a = 1;
get_context()
var b = 2;
`)
is(err, nil)
})
}
func Test_objectLength(t *testing.T) {
tt(t, func() {
_, vm := test()

View File

@ -273,3 +273,24 @@ func (in *_fnStash) clone(clone *_clone) _stash {
}
return out
}
func getStashProperties(stash _stash) (keys []string) {
switch vars := stash.(type) {
case *_dclStash:
for k := range vars.property {
keys = append(keys, k)
}
case *_fnStash:
for k := range vars.property {
keys = append(keys, k)
}
case *_objectStash:
for k := range vars.object.property {
keys = append(keys, k)
}
default:
panic("unknown stash type")
}
return
}