package otto import ( "errors" "fmt" "math" "path" "reflect" "runtime" "strconv" "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 traceLimit 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(), 0)) 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: return false, true case valueObject: return true, false default: panic("this should never happen") } } 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 (self *_runtime) convertNumeric(v Value, t reflect.Type) reflect.Value { val := reflect.ValueOf(v.export()) 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(self.panicRangeError("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(self.panicRangeError(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(self.panicTypeError(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(self.panicRangeError(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(self.panicRangeError(fmt.Sprintf("converting %v to %v would underflow", val.Type(), t))) } if reflect.Zero(t).OverflowUint(uint64(i64)) { panic(self.panicRangeError(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(self.panicRangeError(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(self.panicRangeError(fmt.Sprintf("converting %v to %v would overflow", val.Type(), t))) } return val.Convert(t) } } panic(self.panicTypeError(fmt.Sprintf("unsupported type %v for numeric conversion", val.Type()))) } var typeOfValue = reflect.TypeOf(Value{}) // convertCallParameter 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 (self *_runtime) convertCallParameter(v Value, t reflect.Type) reflect.Value { if t == typeOfValue { return reflect.ValueOf(v) } if v.kind == valueObject { if gso, ok := v._object().value.(*_goStructObject); ok { if gso.value.Type().AssignableTo(t) { return gso.value } } } if t.Kind() == reflect.Interface { iv := reflect.ValueOf(v.export()) if iv.Type().AssignableTo(t) { return iv } } tk := t.Kind() if tk == reflect.Ptr { switch v.kind { case valueEmpty, valueNull, valueUndefined: return reflect.Zero(t) default: var vv reflect.Value if err := catchPanic(func() { vv = self.convertCallParameter(v, t.Elem()) }); err == nil { if vv.CanAddr() { return vv.Addr() } pv := reflect.New(vv.Type()) pv.Elem().Set(vv) return pv } } } switch tk { case reflect.Bool: return reflect.ValueOf(v.bool()) case reflect.String: switch v.kind { case valueString: return reflect.ValueOf(v.value) case valueNumber: return reflect.ValueOf(fmt.Sprintf("%v", v.value)) } case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Float32, reflect.Float64: switch v.kind { case valueNumber: return self.convertNumeric(v, t) } case reflect.Slice: if o := v._object(); o != nil { if lv := o.get("length"); lv.IsNumber() { l := lv.number().int64 s := reflect.MakeSlice(t, int(l), int(l)) tt := t.Elem() for i := int64(0); i < l; i++ { p, ok := o.property[strconv.FormatInt(i, 10)] if !ok { continue } e, ok := p.value.(Value) if !ok { continue } ev := self.convertCallParameter(e, tt) s.Index(int(i)).Set(ev) } return s } } case reflect.Map: if o := v._object(); o != nil && t.Key().Kind() == reflect.String { m := reflect.MakeMap(t) o.enumerate(false, func(k string) bool { m.SetMapIndex(reflect.ValueOf(k), self.convertCallParameter(o.get(k), t.Elem())) return true }) return m } case reflect.Func: if t.NumOut() > 1 { panic(self.panicTypeError("converting JavaScript values to Go functions with more than one return value is currently not supported")) } if o := v._object(); o != nil && o.class == "Function" { return reflect.MakeFunc(t, func(args []reflect.Value) []reflect.Value { l := make([]interface{}, len(args)) for i, a := range args { if a.CanInterface() { l[i] = a.Interface() } } rv, err := v.Call(nullValue, l...) if err != nil { panic(err) } if t.NumOut() == 0 { return nil } return []reflect.Value{self.convertCallParameter(rv, t.Out(0))} }) } } if tk == reflect.String { if o := v._object(); o != nil && o.hasProperty("toString") { if fn := o.get("toString"); fn.IsFunction() { sv, err := fn.Call(v) if err != nil { panic(err) } var r reflect.Value if err := catchPanic(func() { r = self.convertCallParameter(sv, t) }); err == nil { return r } } } return reflect.ValueOf(v.String()) } s := "OTTO DOES NOT UNDERSTAND THIS TYPE" switch v.kind { case valueBoolean: s = "boolean" case valueNull: s = "null" case valueNumber: s = "number" case valueString: s = "string" case valueUndefined: s = "undefined" case valueObject: s = v.Class() } panic(self.panicTypeError("can't convert from %q to %q", s, t.String())) } func (self *_runtime) toValue(value interface{}) Value { switch value := value.(type) { case Value: return value case func(FunctionCall) 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: 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. // 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.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)) case reflect.Func: var name, file string var line int if v := reflect.ValueOf(value); 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) } } typ := value.Type() return toValue_object(self.newNativeFunction(name, file, line, func(c FunctionCall) Value { nargs := typ.NumIn() if len(c.ArgumentList) != nargs { if typ.IsVariadic() { if len(c.ArgumentList) < nargs-1 { panic(self.panicRangeError(fmt.Sprintf("expected at least %d arguments; got %d", nargs-1, len(c.ArgumentList)))) } } else { panic(self.panicRangeError(fmt.Sprintf("expected %d argument(s); got %d", nargs, len(c.ArgumentList)))) } } in := make([]reflect.Value, len(c.ArgumentList)) callSlice := false for i, a := range c.ArgumentList { var t reflect.Type n := i if n >= nargs-1 && typ.IsVariadic() { if n > nargs-1 { n = nargs - 1 } t = typ.In(n).Elem() } else { t = typ.In(n) } // if this is a variadic Go function, and the caller has supplied // exactly the number of JavaScript arguments required, and this // is the last JavaScript argument, try treating the it as the // actual set of variadic Go arguments. if that succeeds, break // out of the loop. if typ.IsVariadic() && len(c.ArgumentList) == nargs && i == nargs-1 { var v reflect.Value if err := catchPanic(func() { v = self.convertCallParameter(a, typ.In(n)) }); err == nil { in[i] = v callSlice = true break } } in[i] = self.convertCallParameter(a, t) } var out []reflect.Value if callSlice { out = value.CallSlice(in) } else { out = value.Call(in) } switch len(out) { case 0: return Value{} case 1: return self.toValue(out[0].Interface()) default: s := make([]interface{}, len(out)) for i, v := range out { s[i] = self.toValue(v.Interface()) } return self.toValue(s) } })) } } } 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 }