package otto import ( "encoding" "encoding/json" "errors" "fmt" "math" "path" "reflect" "runtime" "strconv" "strings" "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.Float32, reflect.Float64: 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) case reflect.Float32, reflect.Float64: return val.Convert(t) } } panic(self.panicTypeError(fmt.Sprintf("unsupported type %v -> %v for numeric conversion", val.Type(), t))) } func fieldIndexByName(t reflect.Type, name string) []int { for i := 0; i < t.NumField(); i++ { f := t.Field(i) if !validGoStructName(f.Name) { continue } if f.Anonymous { if a := fieldIndexByName(f.Type, name); a != nil { return append([]int{i}, a...) } } if a := strings.SplitN(f.Tag.Get("json"), ",", 2); a[0] != "" { if a[0] == "-" { continue } if a[0] == name { return []int{i} } } if f.Name == name { return []int{i} } } return nil } var typeOfValue = reflect.TypeOf(Value{}) var typeOfJSONRawMessage = reflect.TypeOf(json.RawMessage{}) // 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, error) { if t == typeOfValue { return reflect.ValueOf(v), nil } if t == typeOfJSONRawMessage { if d, err := json.Marshal(v.export()); err == nil { return reflect.ValueOf(d), nil } } if v.kind == valueObject { if gso, ok := v._object().value.(*_goStructObject); ok { if gso.value.Type().AssignableTo(t) { // please see TestDynamicFunctionReturningInterface for why this exists if t.Kind() == reflect.Interface && gso.value.Type().ConvertibleTo(t) { return gso.value.Convert(t), nil } else { return gso.value, nil } } } if gao, ok := v._object().value.(*_goArrayObject); ok { if gao.value.Type().AssignableTo(t) { // please see TestDynamicFunctionReturningInterface for why this exists if t.Kind() == reflect.Interface && gao.value.Type().ConvertibleTo(t) { return gao.value.Convert(t), nil } else { return gao.value, nil } } } } tk := t.Kind() if tk == reflect.Interface { e := v.export() if e == nil { return reflect.Zero(t), nil } iv := reflect.ValueOf(e) if iv.Type().AssignableTo(t) { return iv, nil } } if tk == reflect.Ptr { switch v.kind { case valueEmpty, valueNull, valueUndefined: return reflect.Zero(t), nil default: var vv reflect.Value vv, err := self.convertCallParameter(v, t.Elem()) if err != nil { return reflect.Zero(t), fmt.Errorf("can't convert to %s: %s", t, err.Error()) } if vv.CanAddr() { return vv.Addr(), nil } pv := reflect.New(vv.Type()) pv.Elem().Set(vv) return pv, nil } } switch tk { case reflect.Bool: return reflect.ValueOf(v.bool()), nil case reflect.String: switch v.kind { case valueString: return reflect.ValueOf(v.value), nil case valueNumber: return reflect.ValueOf(fmt.Sprintf("%v", v.value)), nil } 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), nil } case reflect.Slice: if o := v._object(); o != nil { if lv := o.get(propertyLength); lv.IsNumber() { l := lv.number().int64 s := reflect.MakeSlice(t, int(l), int(l)) tt := t.Elem() if o.class == classArray { 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, err := self.convertCallParameter(e, tt) if err != nil { return reflect.Zero(t), fmt.Errorf("couldn't convert element %d of %s: %s", i, t, err.Error()) } s.Index(int(i)).Set(ev) } } else if o.class == classGoArray { var gslice bool switch o.value.(type) { case *_goSliceObject: gslice = true case *_goArrayObject: gslice = false } for i := int64(0); i < l; i++ { var p *_property if gslice { p = goSliceGetOwnProperty(o, strconv.FormatInt(i, 10)) } else { p = goArrayGetOwnProperty(o, strconv.FormatInt(i, 10)) } if p == nil { continue } e, ok := p.value.(Value) if !ok { continue } ev, err := self.convertCallParameter(e, tt) if err != nil { return reflect.Zero(t), fmt.Errorf("couldn't convert element %d of %s: %s", i, t, err.Error()) } s.Index(int(i)).Set(ev) } } return s, nil } } case reflect.Map: if o := v._object(); o != nil && t.Key().Kind() == reflect.String { m := reflect.MakeMap(t) var err error o.enumerate(false, func(k string) bool { v, verr := self.convertCallParameter(o.get(k), t.Elem()) if verr != nil { err = fmt.Errorf("couldn't convert property %q of %s: %s", k, t, verr.Error()) return false } m.SetMapIndex(reflect.ValueOf(k), v) return true }) if err != nil { return reflect.Zero(t), err } return m, nil } case reflect.Func: if t.NumOut() > 1 { return reflect.Zero(t), fmt.Errorf("converting JavaScript values to Go functions with more than one return value is currently not supported") } if o := v._object(); o != nil && o.class == classFunction { 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 } r, err := self.convertCallParameter(rv, t.Out(0)) if err != nil { panic(self.panicTypeError(err.Error())) } return []reflect.Value{r} }), nil } case reflect.Struct: if o := v._object(); o != nil && o.class == classObject { s := reflect.New(t) for _, k := range o.propertyOrder { idx := fieldIndexByName(t, k) if idx == nil { return reflect.Zero(t), fmt.Errorf("can't convert property %q of %s: field does not exist", k, t) } ss := s for _, i := range idx { if ss.Kind() == reflect.Ptr { if ss.IsNil() { if !ss.CanSet() { return reflect.Zero(t), fmt.Errorf("can't convert property %q of %s: %s is unexported", k, t, ss.Type().Elem()) } ss.Set(reflect.New(ss.Type().Elem())) } ss = ss.Elem() } ss = ss.Field(i) } v, err := self.convertCallParameter(o.get(k), ss.Type()) if err != nil { return reflect.Zero(t), fmt.Errorf("couldn't convert property %q of %s: %s", k, t, err.Error()) } ss.Set(v) } return s.Elem(), nil } } 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 { return reflect.Zero(t), fmt.Errorf("couldn't call toString: %s", err.Error()) } r, err := self.convertCallParameter(sv, t) if err != nil { return reflect.Zero(t), fmt.Errorf("couldn't convert toString result: %s", err.Error()) } return r, nil } } return reflect.ValueOf(v.String()), nil } if v.kind == valueString { var s encoding.TextUnmarshaler if reflect.PtrTo(t).Implements(reflect.TypeOf(&s).Elem()) { r := reflect.New(t) if err := r.Interface().(encoding.TextUnmarshaler).UnmarshalText([]byte(v.string())); err != nil { return reflect.Zero(t), fmt.Errorf("can't convert to %s as TextUnmarshaller: %s", t.String(), err.Error()) } return r.Elem(), nil } } 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() } return reflect.Zero(t), fmt.Errorf("can't convert from %q to %q", s, t) } 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 { if v, err := self.convertCallParameter(a, typ.In(n)); err == nil { in[i] = v callSlice = true break } } v, err := self.convertCallParameter(a, t) if err != nil { panic(self.panicTypeError(err.Error())) } in[i] = v } 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 }