mirror of
https://github.com/robertkrimen/otto
synced 2025-10-12 20:27:30 +08:00

This patch implements source map support in the parser, the runtime, the script record, and the stack trace printing. The library used to parse and use the source maps is gopkg.in/sourcemap.v1. Unlike earlier versions of this patch, the consumer of otto does not need parse the source map on their own - it's now handled similarly to parsing JavaScript content. To use a source map, the consumer must explicitly parse their source into a `Script` object with `Otto.CompileWithSourceMap`. The script record returned from that call will carry source map information with it, and all location-related functions should reflect the original source positions.
534 lines
15 KiB
Go
534 lines
15 KiB
Go
package otto
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"math"
|
|
"reflect"
|
|
"sync"
|
|
|
|
"github.com/robertkrimen/otto/ast"
|
|
"github.com/robertkrimen/otto/parser"
|
|
)
|
|
|
|
type _global struct {
|
|
Object *_object // Object( ... ), new Object( ... ) - 1 (length)
|
|
Function *_object // Function( ... ), new Function( ... ) - 1
|
|
Array *_object // Array( ... ), new Array( ... ) - 1
|
|
String *_object // String( ... ), new String( ... ) - 1
|
|
Boolean *_object // Boolean( ... ), new Boolean( ... ) - 1
|
|
Number *_object // Number( ... ), new Number( ... ) - 1
|
|
Math *_object
|
|
Date *_object // Date( ... ), new Date( ... ) - 7
|
|
RegExp *_object // RegExp( ... ), new RegExp( ... ) - 2
|
|
Error *_object // Error( ... ), new Error( ... ) - 1
|
|
EvalError *_object
|
|
TypeError *_object
|
|
RangeError *_object
|
|
ReferenceError *_object
|
|
SyntaxError *_object
|
|
URIError *_object
|
|
JSON *_object
|
|
|
|
ObjectPrototype *_object // Object.prototype
|
|
FunctionPrototype *_object // Function.prototype
|
|
ArrayPrototype *_object // Array.prototype
|
|
StringPrototype *_object // String.prototype
|
|
BooleanPrototype *_object // Boolean.prototype
|
|
NumberPrototype *_object // Number.prototype
|
|
DatePrototype *_object // Date.prototype
|
|
RegExpPrototype *_object // RegExp.prototype
|
|
ErrorPrototype *_object // Error.prototype
|
|
EvalErrorPrototype *_object
|
|
TypeErrorPrototype *_object
|
|
RangeErrorPrototype *_object
|
|
ReferenceErrorPrototype *_object
|
|
SyntaxErrorPrototype *_object
|
|
URIErrorPrototype *_object
|
|
}
|
|
|
|
type _runtime struct {
|
|
global _global
|
|
globalObject *_object
|
|
globalStash *_objectStash
|
|
scope *_scope
|
|
otto *Otto
|
|
eval *_object // The builtin eval, for determine indirect versus direct invocation
|
|
debugger func(*Otto)
|
|
random func() float64
|
|
stackLimit int
|
|
|
|
labels []string // FIXME
|
|
lck sync.Mutex
|
|
}
|
|
|
|
func (self *_runtime) enterScope(scope *_scope) {
|
|
scope.outer = self.scope
|
|
if self.scope != nil {
|
|
if self.stackLimit != 0 && self.scope.depth+1 >= self.stackLimit {
|
|
panic(self.panicRangeError("Maximum call stack size exceeded"))
|
|
}
|
|
|
|
scope.depth = self.scope.depth + 1
|
|
}
|
|
|
|
self.scope = scope
|
|
}
|
|
|
|
func (self *_runtime) leaveScope() {
|
|
self.scope = self.scope.outer
|
|
}
|
|
|
|
// FIXME This is used in two places (cloning)
|
|
func (self *_runtime) enterGlobalScope() {
|
|
self.enterScope(newScope(self.globalStash, self.globalStash, self.globalObject))
|
|
}
|
|
|
|
func (self *_runtime) enterFunctionScope(outer _stash, this Value) *_fnStash {
|
|
if outer == nil {
|
|
outer = self.globalStash
|
|
}
|
|
stash := self.newFunctionStash(outer)
|
|
var thisObject *_object
|
|
switch this.kind {
|
|
case valueUndefined, valueNull:
|
|
thisObject = self.globalObject
|
|
default:
|
|
thisObject = self.toObject(this)
|
|
}
|
|
self.enterScope(newScope(stash, stash, thisObject))
|
|
return stash
|
|
}
|
|
|
|
func (self *_runtime) putValue(reference _reference, value Value) {
|
|
name := reference.putValue(value)
|
|
if name != "" {
|
|
// Why? -- If reference.base == nil
|
|
// strict = false
|
|
self.globalObject.defineProperty(name, value, 0111, false)
|
|
}
|
|
}
|
|
|
|
func (self *_runtime) tryCatchEvaluate(inner func() Value) (tryValue Value, exception bool) {
|
|
// resultValue = The value of the block (e.g. the last statement)
|
|
// throw = Something was thrown
|
|
// throwValue = The value of what was thrown
|
|
// other = Something that changes flow (return, break, continue) that is not a throw
|
|
// Otherwise, some sort of unknown panic happened, we'll just propagate it
|
|
defer func() {
|
|
if caught := recover(); caught != nil {
|
|
if exception, ok := caught.(*_exception); ok {
|
|
caught = exception.eject()
|
|
}
|
|
switch caught := caught.(type) {
|
|
case _error:
|
|
exception = true
|
|
tryValue = toValue_object(self.newError(caught.name, caught.messageValue()))
|
|
case Value:
|
|
exception = true
|
|
tryValue = caught
|
|
default:
|
|
panic(caught)
|
|
}
|
|
}
|
|
}()
|
|
|
|
tryValue = inner()
|
|
return
|
|
}
|
|
|
|
// toObject
|
|
|
|
func (self *_runtime) toObject(value Value) *_object {
|
|
switch value.kind {
|
|
case valueEmpty, valueUndefined, valueNull:
|
|
panic(self.panicTypeError())
|
|
case valueBoolean:
|
|
return self.newBoolean(value)
|
|
case valueString:
|
|
return self.newString(value)
|
|
case valueNumber:
|
|
return self.newNumber(value)
|
|
case valueObject:
|
|
return value._object()
|
|
}
|
|
panic(self.panicTypeError())
|
|
}
|
|
|
|
func (self *_runtime) objectCoerce(value Value) (*_object, error) {
|
|
switch value.kind {
|
|
case valueUndefined:
|
|
return nil, errors.New("undefined")
|
|
case valueNull:
|
|
return nil, errors.New("null")
|
|
case valueBoolean:
|
|
return self.newBoolean(value), nil
|
|
case valueString:
|
|
return self.newString(value), nil
|
|
case valueNumber:
|
|
return self.newNumber(value), nil
|
|
case valueObject:
|
|
return value._object(), nil
|
|
}
|
|
panic(self.panicTypeError())
|
|
}
|
|
|
|
func checkObjectCoercible(rt *_runtime, value Value) {
|
|
isObject, mustCoerce := testObjectCoercible(value)
|
|
if !isObject && !mustCoerce {
|
|
panic(rt.panicTypeError())
|
|
}
|
|
}
|
|
|
|
// testObjectCoercible
|
|
|
|
func testObjectCoercible(value Value) (isObject bool, mustCoerce bool) {
|
|
switch value.kind {
|
|
case valueReference, valueEmpty, valueNull, valueUndefined:
|
|
return false, false
|
|
case valueNumber, valueString, valueBoolean:
|
|
isObject = false
|
|
mustCoerce = true
|
|
case valueObject:
|
|
isObject = true
|
|
mustCoerce = false
|
|
}
|
|
return
|
|
}
|
|
|
|
func (self *_runtime) safeToValue(value interface{}) (Value, error) {
|
|
result := Value{}
|
|
err := catchPanic(func() {
|
|
result = self.toValue(value)
|
|
})
|
|
return result, err
|
|
}
|
|
|
|
// convertNumeric converts numeric parameter val from js to that of type t if it is safe to do so, otherwise it panics.
|
|
// This allows literals (int64), bitwise values (int32) and the general form (float64) of javascript numerics to be passed as parameters to go functions easily.
|
|
func convertNumeric(val reflect.Value, t reflect.Type) reflect.Value {
|
|
if val.Kind() == t.Kind() {
|
|
return val
|
|
}
|
|
|
|
if val.Kind() == reflect.Interface {
|
|
val = reflect.ValueOf(val.Interface())
|
|
}
|
|
|
|
switch val.Kind() {
|
|
case reflect.Float32, reflect.Float64:
|
|
f64 := val.Float()
|
|
switch t.Kind() {
|
|
case reflect.Float64:
|
|
return reflect.ValueOf(f64)
|
|
case reflect.Float32:
|
|
if reflect.Zero(t).OverflowFloat(f64) {
|
|
panic("converting float64 to float32 would overflow")
|
|
}
|
|
|
|
return val.Convert(t)
|
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
|
i64 := int64(f64)
|
|
if float64(i64) != f64 {
|
|
panic(fmt.Sprintf("converting %v to %v would cause loss of precision", val.Type(), t))
|
|
}
|
|
|
|
// The float represents an integer
|
|
val = reflect.ValueOf(i64)
|
|
default:
|
|
panic(fmt.Sprintf("cannot convert %v to %v", val.Type(), t))
|
|
}
|
|
}
|
|
|
|
switch val.Kind() {
|
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
i64 := val.Int()
|
|
switch t.Kind() {
|
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
if reflect.Zero(t).OverflowInt(i64) {
|
|
panic(fmt.Sprintf("converting %v to %v would overflow", val.Type(), t))
|
|
}
|
|
return val.Convert(t)
|
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
|
if i64 < 0 {
|
|
panic(fmt.Sprintf("converting %v to %v would underflow", val.Type(), t))
|
|
}
|
|
if reflect.Zero(t).OverflowUint(uint64(i64)) {
|
|
panic(fmt.Sprintf("converting %v to %v would overflow", val.Type(), t))
|
|
}
|
|
return val.Convert(t)
|
|
}
|
|
|
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
|
u64 := val.Uint()
|
|
switch t.Kind() {
|
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
if u64 > math.MaxInt64 || reflect.Zero(t).OverflowInt(int64(u64)) {
|
|
panic(fmt.Sprintf("converting %v to %v would overflow", val.Type(), t))
|
|
}
|
|
return val.Convert(t)
|
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
|
if reflect.Zero(t).OverflowUint(u64) {
|
|
panic(fmt.Sprintf("converting %v to %v would overflow", val.Type(), t))
|
|
}
|
|
return val.Convert(t)
|
|
}
|
|
}
|
|
|
|
panic(fmt.Sprintf("unsupported type %v for numeric conversion", val.Type()))
|
|
}
|
|
|
|
// callParamConvert converts request val to type t if possible.
|
|
// If the conversion fails due to overflow or type miss-match then it panics.
|
|
// If no conversion is known then the original value is returned.
|
|
func callParamConvert(val reflect.Value, t reflect.Type) reflect.Value {
|
|
if val.Kind() == reflect.Interface {
|
|
val = reflect.ValueOf(val.Interface())
|
|
}
|
|
|
|
switch t.Kind() {
|
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Float32, reflect.Float64:
|
|
if val.Kind() == t.Kind() {
|
|
// Types already match
|
|
return val
|
|
}
|
|
return convertNumeric(val, t)
|
|
case reflect.Slice:
|
|
if val.Kind() != reflect.Slice {
|
|
// Conversion from none slice type to slice not possible
|
|
panic(fmt.Sprintf("cannot use %v as type %v", val, t))
|
|
}
|
|
default:
|
|
// No supported conversion
|
|
return val
|
|
}
|
|
|
|
elemType := t.Elem()
|
|
switch elemType.Kind() {
|
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Float32, reflect.Float64, reflect.Slice:
|
|
// Attempt to convert to slice of the type t
|
|
s := reflect.MakeSlice(reflect.SliceOf(elemType), val.Len(), val.Len())
|
|
for i := 0; i < val.Len(); i++ {
|
|
s.Index(i).Set(callParamConvert(val.Index(i), elemType))
|
|
}
|
|
|
|
return s
|
|
}
|
|
|
|
// Not a slice type we can convert
|
|
return val
|
|
}
|
|
|
|
// callSliceRequired returns true if CallSlice is required instead of Call.
|
|
func callSliceRequired(param reflect.Type, val reflect.Value) bool {
|
|
vt := val.Type()
|
|
for param.Kind() == reflect.Slice {
|
|
if val.Kind() == reflect.Interface {
|
|
val = reflect.ValueOf(val.Interface())
|
|
vt = val.Type()
|
|
}
|
|
|
|
if vt.Kind() != reflect.Slice {
|
|
return false
|
|
}
|
|
|
|
vt = vt.Elem()
|
|
if val.Kind() != reflect.Invalid {
|
|
if val.Len() > 0 {
|
|
val = val.Index(0)
|
|
} else {
|
|
val = reflect.Value{}
|
|
}
|
|
}
|
|
param = param.Elem()
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
func (self *_runtime) toValue(value interface{}) Value {
|
|
switch value := value.(type) {
|
|
case Value:
|
|
return value
|
|
case func(FunctionCall) Value:
|
|
return toValue_object(self.newNativeFunction("", value))
|
|
case _nativeFunction:
|
|
return toValue_object(self.newNativeFunction("", value))
|
|
case Object, *Object, _object, *_object:
|
|
// Nothing happens.
|
|
// FIXME We should really figure out what can come here.
|
|
// This catch-all is ugly.
|
|
default:
|
|
{
|
|
value := reflect.ValueOf(value)
|
|
switch value.Kind() {
|
|
case reflect.Ptr:
|
|
switch reflect.Indirect(value).Kind() {
|
|
case reflect.Struct:
|
|
return toValue_object(self.newGoStructObject(value))
|
|
case reflect.Array:
|
|
return toValue_object(self.newGoArray(value))
|
|
}
|
|
case reflect.Func:
|
|
// TODO Maybe cache this?
|
|
return toValue_object(self.newNativeFunction("", func(call FunctionCall) Value {
|
|
argsCount := len(call.ArgumentList)
|
|
in := make([]reflect.Value, argsCount)
|
|
t := value.Type()
|
|
callSlice := false
|
|
paramsCount := t.NumIn()
|
|
lastParam := paramsCount - 1
|
|
lastArg := argsCount - 1
|
|
isVariadic := t.IsVariadic()
|
|
for i, value := range call.ArgumentList {
|
|
var paramType reflect.Type
|
|
if isVariadic && i == lastArg && argsCount == paramsCount {
|
|
// Variadic functions last parameter and parameter numbers match incoming args
|
|
paramType = t.In(lastArg)
|
|
val := reflect.ValueOf(value.export())
|
|
callSlice = callSliceRequired(paramType, val)
|
|
if callSlice {
|
|
in[i] = callParamConvert(reflect.ValueOf(value.export()), paramType)
|
|
continue
|
|
}
|
|
}
|
|
|
|
if i >= lastParam {
|
|
if isVariadic {
|
|
paramType = t.In(lastParam).Elem()
|
|
} else {
|
|
paramType = t.In(lastParam)
|
|
}
|
|
} else {
|
|
paramType = t.In(i)
|
|
}
|
|
in[i] = callParamConvert(reflect.ValueOf(value.export()), paramType)
|
|
}
|
|
|
|
var out []reflect.Value
|
|
if callSlice {
|
|
out = value.CallSlice(in)
|
|
} else {
|
|
out = value.Call(in)
|
|
}
|
|
|
|
l := len(out)
|
|
switch l {
|
|
case 0:
|
|
return Value{}
|
|
case 1:
|
|
return self.toValue(out[0].Interface())
|
|
}
|
|
|
|
// Return an array of the values to emulate multi value return.
|
|
// In the future this can be used along side destructuring assignment.
|
|
s := make([]interface{}, l)
|
|
for i, v := range out {
|
|
s[i] = self.toValue(v.Interface())
|
|
}
|
|
return self.toValue(s)
|
|
}))
|
|
case reflect.Struct:
|
|
return toValue_object(self.newGoStructObject(value))
|
|
case reflect.Map:
|
|
return toValue_object(self.newGoMapObject(value))
|
|
case reflect.Slice:
|
|
return toValue_object(self.newGoSlice(value))
|
|
case reflect.Array:
|
|
return toValue_object(self.newGoArray(value))
|
|
}
|
|
}
|
|
}
|
|
return toValue(value)
|
|
}
|
|
|
|
func (runtime *_runtime) newGoSlice(value reflect.Value) *_object {
|
|
self := runtime.newGoSliceObject(value)
|
|
self.prototype = runtime.global.ArrayPrototype
|
|
return self
|
|
}
|
|
|
|
func (runtime *_runtime) newGoArray(value reflect.Value) *_object {
|
|
self := runtime.newGoArrayObject(value)
|
|
self.prototype = runtime.global.ArrayPrototype
|
|
return self
|
|
}
|
|
|
|
func (runtime *_runtime) parse(filename string, src, sm interface{}) (*ast.Program, error) {
|
|
return parser.ParseFileWithSourceMap(nil, filename, src, sm, 0)
|
|
}
|
|
|
|
func (runtime *_runtime) cmpl_parse(filename string, src, sm interface{}) (*_nodeProgram, error) {
|
|
program, err := parser.ParseFileWithSourceMap(nil, filename, src, sm, 0)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return cmpl_parse(program), nil
|
|
}
|
|
|
|
func (self *_runtime) parseSource(src, sm interface{}) (*_nodeProgram, *ast.Program, error) {
|
|
switch src := src.(type) {
|
|
case *ast.Program:
|
|
return nil, src, nil
|
|
case *Script:
|
|
return src.program, nil, nil
|
|
}
|
|
|
|
program, err := self.parse("", src, sm)
|
|
|
|
return nil, program, err
|
|
}
|
|
|
|
func (self *_runtime) cmpl_runOrEval(src, sm interface{}, eval bool) (Value, error) {
|
|
result := Value{}
|
|
cmpl_program, program, err := self.parseSource(src, sm)
|
|
if err != nil {
|
|
return result, err
|
|
}
|
|
if cmpl_program == nil {
|
|
cmpl_program = cmpl_parse(program)
|
|
}
|
|
err = catchPanic(func() {
|
|
result = self.cmpl_evaluate_nodeProgram(cmpl_program, eval)
|
|
})
|
|
switch result.kind {
|
|
case valueEmpty:
|
|
result = Value{}
|
|
case valueReference:
|
|
result = result.resolve()
|
|
}
|
|
return result, err
|
|
}
|
|
|
|
func (self *_runtime) cmpl_run(src, sm interface{}) (Value, error) {
|
|
return self.cmpl_runOrEval(src, sm, false)
|
|
}
|
|
|
|
func (self *_runtime) cmpl_eval(src, sm interface{}) (Value, error) {
|
|
return self.cmpl_runOrEval(src, sm, true)
|
|
}
|
|
|
|
func (self *_runtime) parseThrow(err error) {
|
|
if err == nil {
|
|
return
|
|
}
|
|
switch err := err.(type) {
|
|
case parser.ErrorList:
|
|
{
|
|
err := err[0]
|
|
if err.Message == "Invalid left-hand side in assignment" {
|
|
panic(self.panicReferenceError(err.Message))
|
|
}
|
|
panic(self.panicSyntaxError(err.Message))
|
|
}
|
|
}
|
|
panic(self.panicSyntaxError(err.Error()))
|
|
}
|
|
|
|
func (self *_runtime) cmpl_parseOrThrow(src, sm interface{}) *_nodeProgram {
|
|
program, err := self.cmpl_parse("", src, sm)
|
|
self.parseThrow(err) // Will panic/throw appropriately
|
|
return program
|
|
}
|