package goja import ( "fmt" "math" "math/big" "strconv" "strings" "sync" "sync/atomic" "time" "github.com/dop251/goja/unistring" ) const ( maxInt = 1 << 53 tryPanicMarker = -2 ) type valueStack []Value type stash struct { values []Value extraArgs []Value names map[unistring.String]uint32 obj *Object outer *stash // If this is a top-level function stash, sets the type of the function. If set, dynamic var declarations // created by direct eval go here. funcType funcType } type context struct { prg *Program stash *stash privEnv *privateEnv newTarget Value result Value pc, sb int args int } type tryFrame struct { // holds an uncaught exception for the 'finally' block exception *Exception callStackLen, iterLen, refLen uint32 sp int32 stash *stash privEnv *privateEnv catchPos, finallyPos, finallyRet int32 } type execCtx struct { context stack []Value tryStack []tryFrame iterStack []iterStackItem refStack []ref } func (vm *vm) suspend(ectx *execCtx, tryStackLen, iterStackLen, refStackLen uint32) { vm.saveCtx(&ectx.context) ectx.stack = append(ectx.stack[:0], vm.stack[vm.sb-1:vm.sp]...) if len(vm.tryStack) > int(tryStackLen) { ectx.tryStack = append(ectx.tryStack[:0], vm.tryStack[tryStackLen:]...) vm.tryStack = vm.tryStack[:tryStackLen] sp := int32(vm.sb - 1) for i := range ectx.tryStack { tf := &ectx.tryStack[i] tf.iterLen -= iterStackLen tf.refLen -= refStackLen tf.sp -= sp } } if len(vm.iterStack) > int(iterStackLen) { ectx.iterStack = append(ectx.iterStack[:0], vm.iterStack[iterStackLen:]...) vm.iterStack = vm.iterStack[:iterStackLen] } if len(vm.refStack) > int(refStackLen) { ectx.refStack = append(ectx.refStack[:0], vm.refStack[refStackLen:]...) vm.refStack = vm.refStack[:refStackLen] } } func (vm *vm) resume(ctx *execCtx) { vm.restoreCtx(&ctx.context) sp := vm.sp vm.sb = sp + 1 vm.stack.expand(sp + len(ctx.stack)) copy(vm.stack[sp:], ctx.stack) vm.sp += len(ctx.stack) for i := range ctx.tryStack { tf := &ctx.tryStack[i] tf.callStackLen = uint32(len(vm.callStack)) tf.iterLen += uint32(len(vm.iterStack)) tf.refLen += uint32(len(vm.refStack)) tf.sp += int32(sp) } vm.tryStack = append(vm.tryStack, ctx.tryStack...) vm.iterStack = append(vm.iterStack, ctx.iterStack...) vm.refStack = append(vm.refStack, ctx.refStack...) } type iterStackItem struct { val Value f iterNextFunc iter *iteratorRecord } type ref interface { get() Value set(Value) init(Value) refname() unistring.String } type stashRef struct { n unistring.String v *[]Value idx int } func (r *stashRef) get() Value { return nilSafe((*r.v)[r.idx]) } func (r *stashRef) set(v Value) { (*r.v)[r.idx] = v } func (r *stashRef) init(v Value) { r.set(v) } func (r *stashRef) refname() unistring.String { return r.n } type thisRef struct { v *[]Value idx int } func (r *thisRef) get() Value { v := (*r.v)[r.idx] if v == nil { panic(referenceError("Must call super constructor in derived class before accessing 'this'")) } return v } func (r *thisRef) set(v Value) { ptr := &(*r.v)[r.idx] if *ptr != nil { panic(referenceError("Super constructor may only be called once")) } *ptr = v } func (r *thisRef) init(v Value) { r.set(v) } func (r *thisRef) refname() unistring.String { return thisBindingName } type stashRefLex struct { stashRef } func (r *stashRefLex) get() Value { v := (*r.v)[r.idx] if v == nil { panic(errAccessBeforeInit) } return v } func (r *stashRefLex) set(v Value) { p := &(*r.v)[r.idx] if *p == nil { panic(errAccessBeforeInit) } *p = v } func (r *stashRefLex) init(v Value) { (*r.v)[r.idx] = v } type stashRefConst struct { stashRefLex strictConst bool } func (r *stashRefConst) set(v Value) { if r.strictConst { panic(errAssignToConst) } } type objRef struct { base *Object name Value this Value strict bool nameConverted bool } func (r *objRef) getKey() Value { if !r.nameConverted { r.name = toPropertyKey(r.name) r.nameConverted = true } return r.name } func (r *objRef) get() Value { return r.base.get(r.getKey(), r.this) } func (r *objRef) set(v Value) { key := r.getKey() if r.this != nil { r.base.set(key, v, r.this, r.strict) } else { r.base.setOwn(key, v, r.strict) } } func (r *objRef) init(v Value) { if r.this != nil { r.base.set(r.getKey(), v, r.this, r.strict) } else { r.base.setOwn(r.getKey(), v, r.strict) } } func (r *objRef) refname() unistring.String { return r.getKey().string() } type objStrRef struct { base *Object name unistring.String this Value strict bool binding bool } func (r *objStrRef) get() Value { return r.base.self.getStr(r.name, r.this) } func (r *objStrRef) set(v Value) { if r.strict && r.binding && !r.base.self.hasOwnPropertyStr(r.name) { panic(referenceError(fmt.Sprintf("%s is not defined", r.name))) } if r.this != nil { r.base.setStr(r.name, v, r.this, r.strict) } else { r.base.self.setOwnStr(r.name, v, r.strict) } } func (r *objStrRef) init(v Value) { if r.this != nil { r.base.setStr(r.name, v, r.this, r.strict) } else { r.base.self.setOwnStr(r.name, v, r.strict) } } func (r *objStrRef) refname() unistring.String { return r.name } type privateRefRes struct { base *Object name *resolvedPrivateName } func (p *privateRefRes) get() Value { return (*getPrivatePropRes)(p.name)._get(p.base, p.base.runtime.vm) } func (p *privateRefRes) set(value Value) { (*setPrivatePropRes)(p.name)._set(p.base, value, p.base.runtime.vm) } func (p *privateRefRes) init(value Value) { panic("not supported") } func (p *privateRefRes) refname() unistring.String { return p.name.string() } type privateRefId struct { base *Object id *privateId } func (p *privateRefId) get() Value { return p.base.runtime.vm.getPrivateProp(p.base, p.id.name, p.id.typ, p.id.idx, p.id.isMethod) } func (p *privateRefId) set(value Value) { p.base.runtime.vm.setPrivateProp(p.base, p.id.name, p.id.typ, p.id.idx, p.id.isMethod, value) } func (p *privateRefId) init(value Value) { panic("not supported") } func (p *privateRefId) refname() unistring.String { return p.id.string() } type unresolvedRef struct { runtime *Runtime name unistring.String } func (r *unresolvedRef) get() Value { r.runtime.throwReferenceError(r.name) panic("Unreachable") } func (r *unresolvedRef) set(Value) { r.get() } func (r *unresolvedRef) init(Value) { r.get() } func (r *unresolvedRef) refname() unistring.String { return r.name } type vm struct { r *Runtime prg *Program pc int stack valueStack sp, sb, args int stash *stash privEnv *privateEnv callStack []context iterStack []iterStackItem refStack []ref tryStack []tryFrame newTarget Value result Value maxCallStackSize int stashAllocs int interrupted uint32 interruptVal interface{} interruptLock sync.Mutex curAsyncRunner *asyncRunner profTracker *profTracker } type instruction interface { exec(*vm) } func intToValue(i int64) Value { if idx := 256 + i; idx >= 0 && idx < 256 { return intCache[idx] } if i >= -maxInt && i <= maxInt { return valueInt(i) } return valueFloat(i) } func floatToInt(f float64) (result int64, ok bool) { if (f != 0 || !math.Signbit(f)) && !math.IsInf(f, 0) && f == math.Trunc(f) && f >= -maxInt && f <= maxInt { return int64(f), true } return 0, false } func floatToValue(f float64) (result Value) { if i, ok := floatToInt(f); ok { return intToValue(i) } switch { case f == 0: return _negativeZero case math.IsNaN(f): return _NaN case math.IsInf(f, 1): return _positiveInf case math.IsInf(f, -1): return _negativeInf } return valueFloat(f) } func toNumeric(value Value) Value { switch v := value.(type) { case valueInt, *valueBigInt: return v case valueFloat: return floatToValue(float64(v)) case *Object: primValue := v.toPrimitiveNumber() if bigint, ok := primValue.(*valueBigInt); ok { return bigint } return primValue.ToNumber() } return value.ToNumber() } func (s *valueStack) expand(idx int) { if idx < len(*s) { return } idx++ if idx < cap(*s) { *s = (*s)[:idx] } else { var newCap int if idx < 1024 { newCap = idx * 2 } else { newCap = (idx + 1025) &^ 1023 } n := make([]Value, idx, newCap) copy(n, *s) *s = n } } func stashObjHas(obj *Object, name unistring.String) bool { if obj.self.hasPropertyStr(name) { if unscopables, ok := obj.self.getSym(SymUnscopables, nil).(*Object); ok { if b := unscopables.self.getStr(name, nil); b != nil { return !b.ToBoolean() } } return true } return false } func (s *stash) isVariable() bool { return s.funcType != funcNone } func (s *stash) initByIdx(idx uint32, v Value) { if s.obj != nil { panic("Attempt to init by idx into an object scope") } s.values[idx] = v } func (s *stash) initByName(name unistring.String, v Value) { if idx, exists := s.names[name]; exists { s.values[idx&^maskTyp] = v } else { panic(referenceError(fmt.Sprintf("%s is not defined", name))) } } func (s *stash) getByIdx(idx uint32) Value { return s.values[idx] } func (s *stash) getByName(name unistring.String) (v Value, exists bool) { if s.obj != nil { if stashObjHas(s.obj, name) { return nilSafe(s.obj.self.getStr(name, nil)), true } return nil, false } if idx, exists := s.names[name]; exists { v := s.values[idx&^maskTyp] if v == nil { if idx&maskVar == 0 { panic(errAccessBeforeInit) } else { v = _undefined } } return v, true } return nil, false } func (s *stash) getRefByName(name unistring.String, strict bool) ref { if obj := s.obj; obj != nil { if stashObjHas(obj, name) { return &objStrRef{ base: obj, name: name, strict: strict, binding: true, } } } else { if idx, exists := s.names[name]; exists { if idx&maskVar == 0 { if idx&maskConst == 0 { return &stashRefLex{ stashRef: stashRef{ n: name, v: &s.values, idx: int(idx &^ maskTyp), }, } } else { return &stashRefConst{ stashRefLex: stashRefLex{ stashRef: stashRef{ n: name, v: &s.values, idx: int(idx &^ maskTyp), }, }, strictConst: strict || (idx&maskStrict != 0), } } } else { return &stashRef{ n: name, v: &s.values, idx: int(idx &^ maskTyp), } } } } return nil } func (s *stash) createBinding(name unistring.String, deletable bool) { if s.names == nil { s.names = make(map[unistring.String]uint32) } if _, exists := s.names[name]; !exists { idx := uint32(len(s.names)) | maskVar if deletable { idx |= maskDeletable } s.names[name] = idx s.values = append(s.values, _undefined) } } func (s *stash) createLexBinding(name unistring.String, isConst bool) { if s.names == nil { s.names = make(map[unistring.String]uint32) } if _, exists := s.names[name]; !exists { idx := uint32(len(s.names)) if isConst { idx |= maskConst | maskStrict } s.names[name] = idx s.values = append(s.values, nil) } } func (s *stash) deleteBinding(name unistring.String) { delete(s.names, name) } func (vm *vm) newStash() { vm.stash = &stash{ outer: vm.stash, } vm.stashAllocs++ } func (vm *vm) init() { vm.sb = -1 vm.stash = &vm.r.global.stash vm.maxCallStackSize = math.MaxInt32 } func (vm *vm) halted() bool { pc := vm.pc return pc < 0 || pc >= len(vm.prg.code) } func (vm *vm) run() { if vm.profTracker != nil && !vm.runWithProfiler() { return } count := 0 interrupted := false for { if count == 0 { if atomic.LoadInt32(&globalProfiler.enabled) == 1 && !vm.runWithProfiler() { return } count = 100 } else { count-- } if interrupted = atomic.LoadUint32(&vm.interrupted) != 0; interrupted { break } pc := vm.pc if pc < 0 || pc >= len(vm.prg.code) { break } vm.prg.code[pc].exec(vm) } if interrupted { vm.interruptLock.Lock() v := &InterruptedError{ iface: vm.interruptVal, } v.stack = vm.captureStack(nil, 0) vm.interruptLock.Unlock() panic(v) } } func (vm *vm) runWithProfiler() bool { pt := vm.profTracker if pt == nil { pt = globalProfiler.p.registerVm() vm.profTracker = pt defer func() { atomic.StoreInt32(&vm.profTracker.finished, 1) vm.profTracker = nil }() } interrupted := false for { if interrupted = atomic.LoadUint32(&vm.interrupted) != 0; interrupted { return true } pc := vm.pc if pc < 0 || pc >= len(vm.prg.code) { break } vm.prg.code[pc].exec(vm) req := atomic.LoadInt32(&pt.req) if req == profReqStop { return true } if req == profReqDoSample { pt.stop = time.Now() pt.numFrames = len(vm.r.CaptureCallStack(len(pt.frames), pt.frames[:0])) pt.frames[0].pc = pc atomic.StoreInt32(&pt.req, profReqSampleReady) } } return false } func (vm *vm) Interrupt(v interface{}) { vm.interruptLock.Lock() vm.interruptVal = v atomic.StoreUint32(&vm.interrupted, 1) vm.interruptLock.Unlock() } func (vm *vm) ClearInterrupt() { atomic.StoreUint32(&vm.interrupted, 0) } func getFuncName(stack []Value, sb int) unistring.String { if sb > 0 { if f, ok := stack[sb-1].(*Object); ok { if _, isProxy := f.self.(*proxyObject); isProxy { return "proxy" } return nilSafe(f.self.getStr("name", nil)).string() } } return "" } func (vm *vm) captureStack(stack []StackFrame, ctxOffset int) []StackFrame { // Unroll the context stack if vm.prg != nil || vm.sb > 0 { var funcName unistring.String if vm.prg != nil { funcName = vm.prg.funcName } else { funcName = getFuncName(vm.stack, vm.sb) } stack = append(stack, StackFrame{prg: vm.prg, pc: vm.pc, funcName: funcName}) } for i := len(vm.callStack) - 1; i > ctxOffset-1; i-- { frame := &vm.callStack[i] if frame.prg != nil || frame.sb > 0 { var funcName unistring.String if prg := frame.prg; prg != nil { funcName = prg.funcName } else { funcName = getFuncName(vm.stack, frame.sb) } stack = append(stack, StackFrame{prg: vm.callStack[i].prg, pc: frame.pc, funcName: funcName}) } } if ctxOffset == 0 && vm.curAsyncRunner != nil { stack = vm.captureAsyncStack(stack, vm.curAsyncRunner) } return stack } func (vm *vm) captureAsyncStack(stack []StackFrame, runner *asyncRunner) []StackFrame { if promise, _ := runner.promiseCap.promise.self.(*Promise); promise != nil { if len(promise.fulfillReactions) == 1 { if r := promise.fulfillReactions[0].asyncRunner; r != nil { ctx := &r.gen.ctx if ctx.prg != nil || ctx.sb > 0 { var funcName unistring.String if prg := ctx.prg; prg != nil { funcName = prg.funcName } else { funcName = getFuncName(ctx.stack, 1) } stack = append(stack, StackFrame{prg: ctx.prg, pc: ctx.pc, funcName: funcName}) } stack = vm.captureAsyncStack(stack, r) } } } return stack } func (vm *vm) pushTryFrame(catchPos, finallyPos int32) { vm.tryStack = append(vm.tryStack, tryFrame{ callStackLen: uint32(len(vm.callStack)), iterLen: uint32(len(vm.iterStack)), refLen: uint32(len(vm.refStack)), sp: int32(vm.sp), stash: vm.stash, privEnv: vm.privEnv, catchPos: catchPos, finallyPos: finallyPos, finallyRet: -1, }) } func (vm *vm) popTryFrame() { vm.tryStack = vm.tryStack[:len(vm.tryStack)-1] } func (vm *vm) restoreStacks(iterLen, refLen uint32) (ex *Exception) { // Restore other stacks iterTail := vm.iterStack[iterLen:] for i := len(iterTail) - 1; i >= 0; i-- { if iter := iterTail[i].iter; iter != nil { ex1 := vm.try(func() { iter.returnIter() }) if ex1 != nil && ex == nil { ex = ex1 } } iterTail[i] = iterStackItem{} } vm.iterStack = vm.iterStack[:iterLen] refTail := vm.refStack[refLen:] for i := range refTail { refTail[i] = nil } vm.refStack = vm.refStack[:refLen] return } func (vm *vm) handleThrow(arg interface{}) *Exception { ex := vm.exceptionFromValue(arg) for len(vm.tryStack) > 0 { tf := &vm.tryStack[len(vm.tryStack)-1] if tf.catchPos == -1 && tf.finallyPos == -1 || ex == nil && tf.catchPos != tryPanicMarker { tf.exception = nil vm.popTryFrame() continue } if int(tf.callStackLen) < len(vm.callStack) { ctx := &vm.callStack[tf.callStackLen] vm.prg, vm.newTarget, vm.result, vm.pc, vm.sb, vm.args = ctx.prg, ctx.newTarget, ctx.result, ctx.pc, ctx.sb, ctx.args vm.callStack = vm.callStack[:tf.callStackLen] } vm.sp = int(tf.sp) vm.stash = tf.stash vm.privEnv = tf.privEnv _ = vm.restoreStacks(tf.iterLen, tf.refLen) if tf.catchPos == tryPanicMarker { break } if tf.catchPos >= 0 { // exception is caught vm.push(ex.val) vm.pc = int(tf.catchPos) tf.catchPos = -1 return nil } if tf.finallyPos >= 0 { // no 'catch' block, but there is a 'finally' block tf.exception = ex vm.pc = int(tf.finallyPos) tf.finallyPos = -1 tf.finallyRet = -1 return nil } } if ex == nil { panic(arg) } return ex } // Calls to this method must be made from the run() loop and must be the last statement before 'return'. // In all other cases exceptions must be thrown using panic(). func (vm *vm) throw(v interface{}) { if ex := vm.handleThrow(v); ex != nil { panic(ex) } } func (vm *vm) try(f func()) (ex *Exception) { vm.pushTryFrame(tryPanicMarker, -1) defer vm.popTryFrame() defer func() { if x := recover(); x != nil { ex = vm.handleThrow(x) } }() f() return } func (vm *vm) runTry() (ex *Exception) { vm.pushTryFrame(tryPanicMarker, -1) defer vm.popTryFrame() for { ex = vm.runTryInner() if ex != nil || vm.halted() { return } } } func (vm *vm) runTryInner() (ex *Exception) { defer func() { if x := recover(); x != nil { ex = vm.handleThrow(x) } }() vm.run() return } func (vm *vm) push(v Value) { vm.stack.expand(vm.sp) vm.stack[vm.sp] = v vm.sp++ } func (vm *vm) pop() Value { vm.sp-- return vm.stack[vm.sp] } func (vm *vm) peek() Value { return vm.stack[vm.sp-1] } func (vm *vm) saveCtx(ctx *context) { ctx.prg, ctx.stash, ctx.privEnv, ctx.newTarget, ctx.result, ctx.pc, ctx.sb, ctx.args = vm.prg, vm.stash, vm.privEnv, vm.newTarget, vm.result, vm.pc, vm.sb, vm.args } func (vm *vm) pushCtx() { if len(vm.callStack) > vm.maxCallStackSize { ex := &StackOverflowError{} ex.stack = vm.captureStack(nil, 0) panic(ex) } vm.callStack = append(vm.callStack, context{}) ctx := &vm.callStack[len(vm.callStack)-1] vm.saveCtx(ctx) } func (vm *vm) restoreCtx(ctx *context) { vm.prg, vm.stash, vm.privEnv, vm.newTarget, vm.result, vm.pc, vm.sb, vm.args = ctx.prg, ctx.stash, ctx.privEnv, ctx.newTarget, ctx.result, ctx.pc, ctx.sb, ctx.args } func (vm *vm) popCtx() { l := len(vm.callStack) - 1 ctx := &vm.callStack[l] vm.restoreCtx(ctx) if ctx.prg != nil { *ctx = context{} } vm.callStack = vm.callStack[:l] } func (vm *vm) toCallee(v Value) *Object { if obj, ok := v.(*Object); ok { return obj } switch unresolved := v.(type) { case valueUnresolved: unresolved.throw() panic("Unreachable") case memberUnresolved: panic(vm.r.NewTypeError("Object has no member '%s'", unresolved.ref)) } panic(vm.r.NewTypeError("Value is not an object: %s", v.toString())) } type loadVal struct { v Value } func (l loadVal) exec(vm *vm) { vm.push(l.v) vm.pc++ } type _loadUndef struct{} var loadUndef _loadUndef func (_loadUndef) exec(vm *vm) { vm.push(_undefined) vm.pc++ } type _loadNil struct{} var loadNil _loadNil func (_loadNil) exec(vm *vm) { vm.push(nil) vm.pc++ } type _saveResult struct{} var saveResult _saveResult func (_saveResult) exec(vm *vm) { vm.sp-- vm.result = vm.stack[vm.sp] vm.pc++ } type _loadResult struct{} var loadResult _loadResult func (_loadResult) exec(vm *vm) { vm.push(vm.result) vm.pc++ } type _clearResult struct{} var clearResult _clearResult func (_clearResult) exec(vm *vm) { vm.result = _undefined vm.pc++ } type _loadGlobalObject struct{} var loadGlobalObject _loadGlobalObject func (_loadGlobalObject) exec(vm *vm) { vm.push(vm.r.globalObject) vm.pc++ } type loadStack int func (l loadStack) exec(vm *vm) { // l > 0 -- var // l == 0 -- this if l > 0 { vm.push(nilSafe(vm.stack[vm.sb+vm.args+int(l)])) } else { vm.push(vm.stack[vm.sb]) } vm.pc++ } type loadStack1 int func (l loadStack1) exec(vm *vm) { // args are in stash // l > 0 -- var // l == 0 -- this if l > 0 { vm.push(nilSafe(vm.stack[vm.sb+int(l)])) } else { vm.push(vm.stack[vm.sb]) } vm.pc++ } type loadStackLex int func (l loadStackLex) exec(vm *vm) { // l < 0 -- arg<-l-1> // l > 0 -- var // l == 0 -- this var p *Value if l <= 0 { arg := int(-l) if arg > vm.args { vm.push(_undefined) vm.pc++ return } else { p = &vm.stack[vm.sb+arg] } } else { p = &vm.stack[vm.sb+vm.args+int(l)] } if *p == nil { vm.throw(errAccessBeforeInit) return } vm.push(*p) vm.pc++ } type loadStack1Lex int func (l loadStack1Lex) exec(vm *vm) { p := &vm.stack[vm.sb+int(l)] if *p == nil { vm.throw(errAccessBeforeInit) return } vm.push(*p) vm.pc++ } type _loadCallee struct{} var loadCallee _loadCallee func (_loadCallee) exec(vm *vm) { vm.push(vm.stack[vm.sb-1]) vm.pc++ } func (vm *vm) storeStack(s int) { // l > 0 -- var if s > 0 { vm.stack[vm.sb+vm.args+s] = vm.stack[vm.sp-1] } else { panic("Illegal stack var index") } vm.pc++ } func (vm *vm) storeStack1(s int) { // args are in stash // l > 0 -- var if s > 0 { vm.stack[vm.sb+s] = vm.stack[vm.sp-1] } else { panic("Illegal stack var index") } vm.pc++ } func (vm *vm) storeStackLex(s int) { // l < 0 -- arg<-l-1> // l > 0 -- var var p *Value if s < 0 { p = &vm.stack[vm.sb-s] } else { p = &vm.stack[vm.sb+vm.args+s] } if *p != nil { *p = vm.stack[vm.sp-1] } else { panic(errAccessBeforeInit) } vm.pc++ } func (vm *vm) storeStack1Lex(s int) { // args are in stash // s > 0 -- var if s <= 0 { panic("Illegal stack var index") } p := &vm.stack[vm.sb+s] if *p != nil { *p = vm.stack[vm.sp-1] } else { panic(errAccessBeforeInit) } vm.pc++ } func (vm *vm) initStack(s int) { if s <= 0 { vm.stack[vm.sb-s] = vm.stack[vm.sp-1] } else { vm.stack[vm.sb+vm.args+s] = vm.stack[vm.sp-1] } vm.pc++ } func (vm *vm) initStack1(s int) { if s <= 0 { panic("Illegal stack var index") } vm.stack[vm.sb+s] = vm.stack[vm.sp-1] vm.pc++ } type storeStack int func (s storeStack) exec(vm *vm) { vm.storeStack(int(s)) } type storeStack1 int func (s storeStack1) exec(vm *vm) { vm.storeStack1(int(s)) } type storeStackLex int func (s storeStackLex) exec(vm *vm) { vm.storeStackLex(int(s)) } type storeStack1Lex int func (s storeStack1Lex) exec(vm *vm) { vm.storeStack1Lex(int(s)) } type initStack int func (s initStack) exec(vm *vm) { vm.initStack(int(s)) } type initStackP int func (s initStackP) exec(vm *vm) { vm.initStack(int(s)) vm.sp-- } type initStack1 int func (s initStack1) exec(vm *vm) { vm.initStack1(int(s)) } type initStack1P int func (s initStack1P) exec(vm *vm) { vm.initStack1(int(s)) vm.sp-- } type storeStackP int func (s storeStackP) exec(vm *vm) { vm.storeStack(int(s)) vm.sp-- } type storeStack1P int func (s storeStack1P) exec(vm *vm) { vm.storeStack1(int(s)) vm.sp-- } type storeStackLexP int func (s storeStackLexP) exec(vm *vm) { vm.storeStackLex(int(s)) vm.sp-- } type storeStack1LexP int func (s storeStack1LexP) exec(vm *vm) { vm.storeStack1Lex(int(s)) vm.sp-- } type _toNumber struct{} var toNumber _toNumber func (_toNumber) exec(vm *vm) { vm.stack[vm.sp-1] = toNumeric(vm.stack[vm.sp-1]) vm.pc++ } type _add struct{} var add _add func (_add) exec(vm *vm) { right := vm.stack[vm.sp-1] left := vm.stack[vm.sp-2] if o, ok := left.(*Object); ok { left = o.toPrimitive() } if o, ok := right.(*Object); ok { right = o.toPrimitive() } var ret Value leftString, isLeftString := left.(String) rightString, isRightString := right.(String) if isLeftString || isRightString { if !isLeftString { leftString = left.toString() } if !isRightString { rightString = right.toString() } ret = leftString.Concat(rightString) } else { switch left := left.(type) { case valueInt: switch right := right.(type) { case valueInt: ret = intToValue(int64(left) + int64(right)) case *valueBigInt: panic(errMixBigIntType) default: ret = floatToValue(float64(left) + right.ToFloat()) } case *valueBigInt: if right, ok := right.(*valueBigInt); ok { ret = (*valueBigInt)(new(big.Int).Add((*big.Int)(left), (*big.Int)(right))) } else { panic(errMixBigIntType) } default: if _, ok := right.(*valueBigInt); ok { panic(errMixBigIntType) } ret = floatToValue(left.ToFloat() + right.ToFloat()) } } vm.stack[vm.sp-2] = ret vm.sp-- vm.pc++ } type _sub struct{} var sub _sub func (_sub) exec(vm *vm) { right := vm.stack[vm.sp-1] left := vm.stack[vm.sp-2] left = toNumeric(left) right = toNumeric(right) var result Value switch left := left.(type) { case valueInt: switch right := right.(type) { case valueInt: result = intToValue(int64(left) - int64(right)) goto end case *valueBigInt: panic(errMixBigIntType) } case valueFloat: if _, ok := right.(*valueBigInt); ok { panic(errMixBigIntType) } case *valueBigInt: if right, ok := right.(*valueBigInt); ok { result = (*valueBigInt)(new(big.Int).Sub((*big.Int)(left), (*big.Int)(right))) goto end } panic(errMixBigIntType) } result = floatToValue(left.ToFloat() - right.ToFloat()) end: vm.sp-- vm.stack[vm.sp-1] = result vm.pc++ } type _mul struct{} var mul _mul func (_mul) exec(vm *vm) { left := toNumeric(vm.stack[vm.sp-2]) right := toNumeric(vm.stack[vm.sp-1]) var result Value switch left := left.(type) { case valueInt: switch right := right.(type) { case valueInt: if left == 0 && right == -1 || left == -1 && right == 0 { result = _negativeZero goto end } res := left * right // check for overflow if left == 0 || right == 0 || res/left == right { result = intToValue(int64(res)) goto end } case *valueBigInt: panic(errMixBigIntType) } case valueFloat: if _, ok := right.(*valueBigInt); ok { panic(errMixBigIntType) } case *valueBigInt: if right, ok := right.(*valueBigInt); ok { result = (*valueBigInt)(new(big.Int).Mul((*big.Int)(left), (*big.Int)(right))) goto end } panic(errMixBigIntType) } result = floatToValue(left.ToFloat() * right.ToFloat()) end: vm.sp-- vm.stack[vm.sp-1] = result vm.pc++ } type _exp struct{} var exp _exp func (_exp) exec(vm *vm) { vm.sp-- x := vm.stack[vm.sp-1] y := vm.stack[vm.sp] x = toNumeric(x) y = toNumeric(y) var result Value if x, ok := x.(*valueBigInt); ok { if y, ok := y.(*valueBigInt); ok { if (*big.Int)(y).Cmp(big.NewInt(0)) < 0 { panic(vm.r.newError(vm.r.getRangeError(), "exponent must be positive")) } result = (*valueBigInt)(new(big.Int).Exp((*big.Int)(x), (*big.Int)(y), nil)) goto end } panic(errMixBigIntType) } else if _, ok := y.(*valueBigInt); ok { panic(errMixBigIntType) } result = pow(x, y) end: vm.stack[vm.sp-1] = result vm.pc++ } type _div struct{} var div _div func (_div) exec(vm *vm) { leftValue := toNumeric(vm.stack[vm.sp-2]) rightValue := toNumeric(vm.stack[vm.sp-1]) var ( result Value left, right float64 ) if left, ok := leftValue.(*valueBigInt); ok { if right, ok := rightValue.(*valueBigInt); ok { if (*big.Int)(right).Cmp(big.NewInt(0)) == 0 { panic(vm.r.newError(vm.r.getRangeError(), "Division by zero")) } if (*big.Int)(left).CmpAbs((*big.Int)(right)) < 0 { result = (*valueBigInt)(big.NewInt(0)) } else { i, _ := new(big.Int).QuoRem((*big.Int)(left), (*big.Int)(right), big.NewInt(0)) result = (*valueBigInt)(i) } goto end } panic(errMixBigIntType) } else if _, ok := rightValue.(*valueBigInt); ok { panic(errMixBigIntType) } left, right = leftValue.ToFloat(), rightValue.ToFloat() if math.IsNaN(left) || math.IsNaN(right) { result = _NaN goto end } if math.IsInf(left, 0) && math.IsInf(right, 0) { result = _NaN goto end } if left == 0 && right == 0 { result = _NaN goto end } if math.IsInf(left, 0) { if math.Signbit(left) == math.Signbit(right) { result = _positiveInf goto end } else { result = _negativeInf goto end } } if math.IsInf(right, 0) { if math.Signbit(left) == math.Signbit(right) { result = _positiveZero goto end } else { result = _negativeZero goto end } } if right == 0 { if math.Signbit(left) == math.Signbit(right) { result = _positiveInf goto end } else { result = _negativeInf goto end } } result = floatToValue(left / right) end: vm.sp-- vm.stack[vm.sp-1] = result vm.pc++ } type _mod struct{} var mod _mod func (_mod) exec(vm *vm) { left := toNumeric(vm.stack[vm.sp-2]) right := toNumeric(vm.stack[vm.sp-1]) var result Value switch left := left.(type) { case valueInt: switch right := right.(type) { case valueInt: if right == 0 { result = _NaN goto end } r := left % right if r == 0 && left < 0 { result = _negativeZero } else { result = intToValue(int64(left % right)) } goto end case *valueBigInt: panic(errMixBigIntType) } case valueFloat: if _, ok := right.(*valueBigInt); ok { panic(errMixBigIntType) } case *valueBigInt: if right, ok := right.(*valueBigInt); ok { switch { case (*big.Int)(right).Cmp(big.NewInt(0)) == 0: panic(vm.r.newError(vm.r.getRangeError(), "Division by zero")) case (*big.Int)(left).Cmp(big.NewInt(0)) < 0: abs := new(big.Int).Abs((*big.Int)(left)) v := new(big.Int).Mod(abs, (*big.Int)(right)) result = (*valueBigInt)(v.Neg(v)) default: result = (*valueBigInt)(new(big.Int).Mod((*big.Int)(left), (*big.Int)(right))) } goto end } panic(errMixBigIntType) } result = floatToValue(math.Mod(left.ToFloat(), right.ToFloat())) end: vm.sp-- vm.stack[vm.sp-1] = result vm.pc++ } type _neg struct{} var neg _neg func (_neg) exec(vm *vm) { operand := vm.stack[vm.sp-1] var result Value switch n := toNumeric(operand).(type) { case *valueBigInt: result = (*valueBigInt)(new(big.Int).Neg((*big.Int)(n))) case valueInt: if n == 0 { result = _negativeZero } else { result = -n } default: f := operand.ToFloat() if !math.IsNaN(f) { f = -f } result = valueFloat(f) } vm.stack[vm.sp-1] = result vm.pc++ } type _plus struct{} var plus _plus func (_plus) exec(vm *vm) { vm.stack[vm.sp-1] = vm.stack[vm.sp-1].ToNumber() vm.pc++ } type _inc struct{} var inc _inc func (_inc) exec(vm *vm) { v := vm.stack[vm.sp-1] switch n := v.(type) { case *valueBigInt: v = (*valueBigInt)(new(big.Int).Add((*big.Int)(n), big.NewInt(1))) case valueInt: v = intToValue(int64(n + 1)) default: v = valueFloat(n.ToFloat() + 1) } vm.stack[vm.sp-1] = v vm.pc++ } type _dec struct{} var dec _dec func (_dec) exec(vm *vm) { v := vm.stack[vm.sp-1] switch n := v.(type) { case *valueBigInt: v = (*valueBigInt)(new(big.Int).Sub((*big.Int)(n), big.NewInt(1))) case valueInt: v = intToValue(int64(n - 1)) default: v = valueFloat(n.ToFloat() - 1) } vm.stack[vm.sp-1] = v vm.pc++ } type _and struct{} var and _and func (_and) exec(vm *vm) { left := toNumeric(vm.stack[vm.sp-2]) right := toNumeric(vm.stack[vm.sp-1]) var result Value if left, ok := left.(*valueBigInt); ok { if right, ok := right.(*valueBigInt); ok { result = (*valueBigInt)(new(big.Int).And((*big.Int)(left), (*big.Int)(right))) goto end } panic(errMixBigIntType) } else if _, ok := right.(*valueBigInt); ok { panic(errMixBigIntType) } result = intToValue(int64(toInt32(left) & toInt32(right))) end: vm.stack[vm.sp-2] = result vm.sp-- vm.pc++ } type _or struct{} var or _or func (_or) exec(vm *vm) { left := toNumeric(vm.stack[vm.sp-2]) right := toNumeric(vm.stack[vm.sp-1]) var result Value if left, ok := left.(*valueBigInt); ok { if right, ok := right.(*valueBigInt); ok { result = (*valueBigInt)(new(big.Int).Or((*big.Int)(left), (*big.Int)(right))) goto end } panic(errMixBigIntType) } else if _, ok := right.(*valueBigInt); ok { panic(errMixBigIntType) } result = intToValue(int64(toInt32(left) | toInt32(right))) end: vm.stack[vm.sp-2] = result vm.sp-- vm.pc++ } type _xor struct{} var xor _xor func (_xor) exec(vm *vm) { left := toNumeric(vm.stack[vm.sp-2]) right := toNumeric(vm.stack[vm.sp-1]) var result Value if left, ok := left.(*valueBigInt); ok { if right, ok := right.(*valueBigInt); ok { result = (*valueBigInt)(new(big.Int).Xor((*big.Int)(left), (*big.Int)(right))) goto end } panic(errMixBigIntType) } else if _, ok := right.(*valueBigInt); ok { panic(errMixBigIntType) } result = intToValue(int64(toInt32(left) ^ toInt32(right))) end: vm.stack[vm.sp-2] = result vm.sp-- vm.pc++ } type _bnot struct{} var bnot _bnot func (_bnot) exec(vm *vm) { v := vm.stack[vm.sp-1] switch n := toNumeric(v).(type) { case *valueBigInt: v = (*valueBigInt)(new(big.Int).Not((*big.Int)(n))) default: v = intToValue(int64(^toInt32(n))) } vm.stack[vm.sp-1] = v vm.pc++ } type _sal struct{} var sal _sal func (_sal) exec(vm *vm) { left := toNumeric(vm.stack[vm.sp-2]) right := toNumeric(vm.stack[vm.sp-1]) var result Value if left, ok := left.(*valueBigInt); ok { if right, ok := right.(*valueBigInt); ok { n := uint((*big.Int)(right).Uint64()) if (*big.Int)(right).Sign() < 0 { result = (*valueBigInt)(new(big.Int).Rsh((*big.Int)(left), n)) } else { result = (*valueBigInt)(new(big.Int).Lsh((*big.Int)(left), n)) } goto end } panic(errMixBigIntType) } else if _, ok := right.(*valueBigInt); ok { panic(errMixBigIntType) } result = intToValue(int64(toInt32(left) << (toUint32(right) & 0x1F))) end: vm.stack[vm.sp-2] = result vm.sp-- vm.pc++ } type _sar struct{} var sar _sar func (_sar) exec(vm *vm) { left := toNumeric(vm.stack[vm.sp-2]) right := toNumeric(vm.stack[vm.sp-1]) var result Value if left, ok := left.(*valueBigInt); ok { if right, ok := right.(*valueBigInt); ok { n := uint((*big.Int)(right).Uint64()) if (*big.Int)(right).Sign() < 0 { result = (*valueBigInt)(new(big.Int).Lsh((*big.Int)(left), n)) } else { result = (*valueBigInt)(new(big.Int).Rsh((*big.Int)(left), n)) } goto end } panic(errMixBigIntType) } else if _, ok := right.(*valueBigInt); ok { panic(errMixBigIntType) } result = intToValue(int64(toInt32(left) >> (toUint32(right) & 0x1F))) end: vm.stack[vm.sp-2] = result vm.sp-- vm.pc++ } type _shr struct{} var shr _shr func (_shr) exec(vm *vm) { left := toNumeric(vm.stack[vm.sp-2]) right := toNumeric(vm.stack[vm.sp-1]) if _, ok := left.(*valueBigInt); ok { _ = toNumeric(right) panic(vm.r.NewTypeError("BigInts have no unsigned right shift, use >> instead")) } else if _, ok := right.(*valueBigInt); ok { panic(vm.r.NewTypeError("BigInts have no unsigned right shift, use >> instead")) } vm.stack[vm.sp-2] = intToValue(int64(toUint32(left) >> (toUint32(right) & 0x1F))) vm.sp-- vm.pc++ } type jump int32 func (j jump) exec(vm *vm) { vm.pc += int(j) } type _toPropertyKey struct{} func (_toPropertyKey) exec(vm *vm) { p := vm.sp - 1 vm.stack[p] = toPropertyKey(vm.stack[p]) vm.pc++ } type _toString struct{} func (_toString) exec(vm *vm) { p := vm.sp - 1 vm.stack[p] = vm.stack[p].toString() vm.pc++ } type _getElemRef struct{} var getElemRef _getElemRef func (_getElemRef) exec(vm *vm) { obj := vm.stack[vm.sp-2].ToObject(vm.r) propName := vm.stack[vm.sp-1] vm.refStack = append(vm.refStack, &objRef{ base: obj, name: propName, }) vm.sp -= 2 vm.pc++ } type _getElemRefRecv struct{} var getElemRefRecv _getElemRefRecv func (_getElemRefRecv) exec(vm *vm) { obj := vm.stack[vm.sp-1].ToObject(vm.r) propName := vm.stack[vm.sp-2] vm.refStack = append(vm.refStack, &objRef{ base: obj, name: propName, this: vm.stack[vm.sp-3], }) vm.sp -= 3 vm.pc++ } type _getElemRefStrict struct{} var getElemRefStrict _getElemRefStrict func (_getElemRefStrict) exec(vm *vm) { obj := vm.stack[vm.sp-2].ToObject(vm.r) propName := vm.stack[vm.sp-1] vm.refStack = append(vm.refStack, &objRef{ base: obj, name: propName, strict: true, }) vm.sp -= 2 vm.pc++ } type _getElemRefRecvStrict struct{} var getElemRefRecvStrict _getElemRefRecvStrict func (_getElemRefRecvStrict) exec(vm *vm) { obj := vm.stack[vm.sp-1].ToObject(vm.r) propName := vm.stack[vm.sp-2] vm.refStack = append(vm.refStack, &objRef{ base: obj, name: propName, this: vm.stack[vm.sp-3], strict: true, }) vm.sp -= 3 vm.pc++ } type _setElem struct{} var setElem _setElem func (_setElem) exec(vm *vm) { obj := vm.stack[vm.sp-3].ToObject(vm.r) propName := toPropertyKey(vm.stack[vm.sp-2]) val := vm.stack[vm.sp-1] obj.setOwn(propName, val, false) vm.sp -= 2 vm.stack[vm.sp-1] = val vm.pc++ } type _setElem1 struct{} var setElem1 _setElem1 func (_setElem1) exec(vm *vm) { obj := vm.stack[vm.sp-3].ToObject(vm.r) propName := vm.stack[vm.sp-2] val := vm.stack[vm.sp-1] obj.setOwn(propName, val, true) vm.sp -= 2 vm.pc++ } type _setElem1Named struct{} var setElem1Named _setElem1Named func (_setElem1Named) exec(vm *vm) { receiver := vm.stack[vm.sp-3] base := receiver.ToObject(vm.r) propName := vm.stack[vm.sp-2] val := vm.stack[vm.sp-1] vm.r.toObject(val).self.defineOwnPropertyStr("name", PropertyDescriptor{ Value: funcName("", propName), Configurable: FLAG_TRUE, }, true) base.set(propName, val, receiver, true) vm.sp -= 2 vm.pc++ } type defineMethod struct { enumerable bool } func (d *defineMethod) exec(vm *vm) { obj := vm.r.toObject(vm.stack[vm.sp-3]) propName := vm.stack[vm.sp-2] method := vm.r.toObject(vm.stack[vm.sp-1]) method.self.defineOwnPropertyStr("name", PropertyDescriptor{ Value: funcName("", propName), Configurable: FLAG_TRUE, }, true) obj.defineOwnProperty(propName, PropertyDescriptor{ Value: method, Writable: FLAG_TRUE, Configurable: FLAG_TRUE, Enumerable: ToFlag(d.enumerable), }, true) vm.sp -= 2 vm.pc++ } type _setElemP struct{} var setElemP _setElemP func (_setElemP) exec(vm *vm) { obj := vm.stack[vm.sp-3].ToObject(vm.r) propName := toPropertyKey(vm.stack[vm.sp-2]) val := vm.stack[vm.sp-1] obj.setOwn(propName, val, false) vm.sp -= 3 vm.pc++ } type _setElemStrict struct{} var setElemStrict _setElemStrict func (_setElemStrict) exec(vm *vm) { propName := toPropertyKey(vm.stack[vm.sp-2]) receiver := vm.stack[vm.sp-3] val := vm.stack[vm.sp-1] if receiverObj, ok := receiver.(*Object); ok { receiverObj.setOwn(propName, val, true) } else { base := receiver.ToObject(vm.r) base.set(propName, val, receiver, true) } vm.sp -= 2 vm.stack[vm.sp-1] = val vm.pc++ } type _setElemRecv struct{} var setElemRecv _setElemRecv func (_setElemRecv) exec(vm *vm) { receiver := vm.stack[vm.sp-4] propName := toPropertyKey(vm.stack[vm.sp-3]) o := vm.stack[vm.sp-2] val := vm.stack[vm.sp-1] if obj, ok := o.(*Object); ok { obj.set(propName, val, receiver, false) } else { base := o.ToObject(vm.r) base.set(propName, val, receiver, false) } vm.sp -= 3 vm.stack[vm.sp-1] = val vm.pc++ } type _setElemRecvStrict struct{} var setElemRecvStrict _setElemRecvStrict func (_setElemRecvStrict) exec(vm *vm) { receiver := vm.stack[vm.sp-4] propName := toPropertyKey(vm.stack[vm.sp-3]) o := vm.stack[vm.sp-2] val := vm.stack[vm.sp-1] if obj, ok := o.(*Object); ok { obj.set(propName, val, receiver, true) } else { base := o.ToObject(vm.r) base.set(propName, val, receiver, true) } vm.sp -= 3 vm.stack[vm.sp-1] = val vm.pc++ } type _setElemStrictP struct{} var setElemStrictP _setElemStrictP func (_setElemStrictP) exec(vm *vm) { propName := toPropertyKey(vm.stack[vm.sp-2]) receiver := vm.stack[vm.sp-3] val := vm.stack[vm.sp-1] if receiverObj, ok := receiver.(*Object); ok { receiverObj.setOwn(propName, val, true) } else { base := receiver.ToObject(vm.r) base.set(propName, val, receiver, true) } vm.sp -= 3 vm.pc++ } type _setElemRecvP struct{} var setElemRecvP _setElemRecvP func (_setElemRecvP) exec(vm *vm) { receiver := vm.stack[vm.sp-4] propName := toPropertyKey(vm.stack[vm.sp-3]) o := vm.stack[vm.sp-2] val := vm.stack[vm.sp-1] if obj, ok := o.(*Object); ok { obj.set(propName, val, receiver, false) } else { base := o.ToObject(vm.r) base.set(propName, val, receiver, false) } vm.sp -= 4 vm.pc++ } type _setElemRecvStrictP struct{} var setElemRecvStrictP _setElemRecvStrictP func (_setElemRecvStrictP) exec(vm *vm) { receiver := vm.stack[vm.sp-4] propName := toPropertyKey(vm.stack[vm.sp-3]) o := vm.stack[vm.sp-2] val := vm.stack[vm.sp-1] if obj, ok := o.(*Object); ok { obj.set(propName, val, receiver, true) } else { base := o.ToObject(vm.r) base.set(propName, val, receiver, true) } vm.sp -= 4 vm.pc++ } type _deleteElem struct{} var deleteElem _deleteElem func (_deleteElem) exec(vm *vm) { obj := vm.stack[vm.sp-2].ToObject(vm.r) propName := toPropertyKey(vm.stack[vm.sp-1]) if obj.delete(propName, false) { vm.stack[vm.sp-2] = valueTrue } else { vm.stack[vm.sp-2] = valueFalse } vm.sp-- vm.pc++ } type _deleteElemStrict struct{} var deleteElemStrict _deleteElemStrict func (_deleteElemStrict) exec(vm *vm) { obj := vm.stack[vm.sp-2].ToObject(vm.r) propName := toPropertyKey(vm.stack[vm.sp-1]) obj.delete(propName, true) vm.stack[vm.sp-2] = valueTrue vm.sp-- vm.pc++ } type deleteProp unistring.String func (d deleteProp) exec(vm *vm) { obj := vm.stack[vm.sp-1].ToObject(vm.r) if obj.self.deleteStr(unistring.String(d), false) { vm.stack[vm.sp-1] = valueTrue } else { vm.stack[vm.sp-1] = valueFalse } vm.pc++ } type deletePropStrict unistring.String func (d deletePropStrict) exec(vm *vm) { obj := vm.stack[vm.sp-1].ToObject(vm.r) obj.self.deleteStr(unistring.String(d), true) vm.stack[vm.sp-1] = valueTrue vm.pc++ } type getPropRef unistring.String func (p getPropRef) exec(vm *vm) { vm.refStack = append(vm.refStack, &objStrRef{ base: vm.stack[vm.sp-1].ToObject(vm.r), name: unistring.String(p), }) vm.sp-- vm.pc++ } type getPropRefRecv unistring.String func (p getPropRefRecv) exec(vm *vm) { vm.refStack = append(vm.refStack, &objStrRef{ this: vm.stack[vm.sp-2], base: vm.stack[vm.sp-1].ToObject(vm.r), name: unistring.String(p), }) vm.sp -= 2 vm.pc++ } type getPropRefStrict unistring.String func (p getPropRefStrict) exec(vm *vm) { vm.refStack = append(vm.refStack, &objStrRef{ base: vm.stack[vm.sp-1].ToObject(vm.r), name: unistring.String(p), strict: true, }) vm.sp-- vm.pc++ } type getPropRefRecvStrict unistring.String func (p getPropRefRecvStrict) exec(vm *vm) { vm.refStack = append(vm.refStack, &objStrRef{ this: vm.stack[vm.sp-2], base: vm.stack[vm.sp-1].ToObject(vm.r), name: unistring.String(p), strict: true, }) vm.sp -= 2 vm.pc++ } type setProp unistring.String func (p setProp) exec(vm *vm) { val := vm.stack[vm.sp-1] vm.stack[vm.sp-2].ToObject(vm.r).self.setOwnStr(unistring.String(p), val, false) vm.stack[vm.sp-2] = val vm.sp-- vm.pc++ } type setPropP unistring.String func (p setPropP) exec(vm *vm) { val := vm.stack[vm.sp-1] vm.stack[vm.sp-2].ToObject(vm.r).self.setOwnStr(unistring.String(p), val, false) vm.sp -= 2 vm.pc++ } type setPropStrict unistring.String func (p setPropStrict) exec(vm *vm) { receiver := vm.stack[vm.sp-2] val := vm.stack[vm.sp-1] propName := unistring.String(p) if receiverObj, ok := receiver.(*Object); ok { receiverObj.self.setOwnStr(propName, val, true) } else { base := receiver.ToObject(vm.r) base.setStr(propName, val, receiver, true) } vm.stack[vm.sp-2] = val vm.sp-- vm.pc++ } type setPropRecv unistring.String func (p setPropRecv) exec(vm *vm) { receiver := vm.stack[vm.sp-3] o := vm.stack[vm.sp-2] val := vm.stack[vm.sp-1] propName := unistring.String(p) if obj, ok := o.(*Object); ok { obj.setStr(propName, val, receiver, false) } else { base := o.ToObject(vm.r) base.setStr(propName, val, receiver, false) } vm.stack[vm.sp-3] = val vm.sp -= 2 vm.pc++ } type setPropRecvStrict unistring.String func (p setPropRecvStrict) exec(vm *vm) { receiver := vm.stack[vm.sp-3] o := vm.stack[vm.sp-2] val := vm.stack[vm.sp-1] propName := unistring.String(p) if obj, ok := o.(*Object); ok { obj.setStr(propName, val, receiver, true) } else { base := o.ToObject(vm.r) base.setStr(propName, val, receiver, true) } vm.stack[vm.sp-3] = val vm.sp -= 2 vm.pc++ } type setPropRecvP unistring.String func (p setPropRecvP) exec(vm *vm) { receiver := vm.stack[vm.sp-3] o := vm.stack[vm.sp-2] val := vm.stack[vm.sp-1] propName := unistring.String(p) if obj, ok := o.(*Object); ok { obj.setStr(propName, val, receiver, false) } else { base := o.ToObject(vm.r) base.setStr(propName, val, receiver, false) } vm.sp -= 3 vm.pc++ } type setPropRecvStrictP unistring.String func (p setPropRecvStrictP) exec(vm *vm) { receiver := vm.stack[vm.sp-3] o := vm.stack[vm.sp-2] val := vm.stack[vm.sp-1] propName := unistring.String(p) if obj, ok := o.(*Object); ok { obj.setStr(propName, val, receiver, true) } else { base := o.ToObject(vm.r) base.setStr(propName, val, receiver, true) } vm.sp -= 3 vm.pc++ } type setPropStrictP unistring.String func (p setPropStrictP) exec(vm *vm) { receiver := vm.stack[vm.sp-2] val := vm.stack[vm.sp-1] propName := unistring.String(p) if receiverObj, ok := receiver.(*Object); ok { receiverObj.self.setOwnStr(propName, val, true) } else { base := receiver.ToObject(vm.r) base.setStr(propName, val, receiver, true) } vm.sp -= 2 vm.pc++ } type putProp unistring.String func (p putProp) exec(vm *vm) { vm.r.toObject(vm.stack[vm.sp-2]).self._putProp(unistring.String(p), vm.stack[vm.sp-1], true, true, true) vm.sp-- vm.pc++ } // used in class declarations instead of putProp because DefineProperty must be observable by Proxy type definePropKeyed unistring.String func (p definePropKeyed) exec(vm *vm) { vm.r.toObject(vm.stack[vm.sp-2]).self.defineOwnPropertyStr(unistring.String(p), PropertyDescriptor{ Value: vm.stack[vm.sp-1], Writable: FLAG_TRUE, Configurable: FLAG_TRUE, Enumerable: FLAG_TRUE, }, true) vm.sp-- vm.pc++ } type defineProp struct{} func (defineProp) exec(vm *vm) { vm.r.toObject(vm.stack[vm.sp-3]).defineOwnProperty(vm.stack[vm.sp-2], PropertyDescriptor{ Value: vm.stack[vm.sp-1], Writable: FLAG_TRUE, Configurable: FLAG_TRUE, Enumerable: FLAG_TRUE, }, true) vm.sp -= 2 vm.pc++ } type defineMethodKeyed struct { key unistring.String enumerable bool } func (d *defineMethodKeyed) exec(vm *vm) { obj := vm.r.toObject(vm.stack[vm.sp-2]) method := vm.r.toObject(vm.stack[vm.sp-1]) obj.self.defineOwnPropertyStr(d.key, PropertyDescriptor{ Value: method, Writable: FLAG_TRUE, Configurable: FLAG_TRUE, Enumerable: ToFlag(d.enumerable), }, true) vm.sp-- vm.pc++ } type _setProto struct{} var setProto _setProto func (_setProto) exec(vm *vm) { vm.r.setObjectProto(vm.stack[vm.sp-2], vm.stack[vm.sp-1]) vm.sp-- vm.pc++ } type defineGetterKeyed struct { key unistring.String enumerable bool } func (s *defineGetterKeyed) exec(vm *vm) { obj := vm.r.toObject(vm.stack[vm.sp-2]) val := vm.stack[vm.sp-1] method := vm.r.toObject(val) method.self.defineOwnPropertyStr("name", PropertyDescriptor{ Value: asciiString("get ").Concat(stringValueFromRaw(s.key)), Configurable: FLAG_TRUE, }, true) descr := PropertyDescriptor{ Getter: val, Configurable: FLAG_TRUE, Enumerable: ToFlag(s.enumerable), } obj.self.defineOwnPropertyStr(s.key, descr, true) vm.sp-- vm.pc++ } type defineSetterKeyed struct { key unistring.String enumerable bool } func (s *defineSetterKeyed) exec(vm *vm) { obj := vm.r.toObject(vm.stack[vm.sp-2]) val := vm.stack[vm.sp-1] method := vm.r.toObject(val) method.self.defineOwnPropertyStr("name", PropertyDescriptor{ Value: asciiString("set ").Concat(stringValueFromRaw(s.key)), Configurable: FLAG_TRUE, }, true) descr := PropertyDescriptor{ Setter: val, Configurable: FLAG_TRUE, Enumerable: ToFlag(s.enumerable), } obj.self.defineOwnPropertyStr(s.key, descr, true) vm.sp-- vm.pc++ } type defineGetter struct { enumerable bool } func (s *defineGetter) exec(vm *vm) { obj := vm.r.toObject(vm.stack[vm.sp-3]) propName := vm.stack[vm.sp-2] val := vm.stack[vm.sp-1] method := vm.r.toObject(val) method.self.defineOwnPropertyStr("name", PropertyDescriptor{ Value: funcName("get ", propName), Configurable: FLAG_TRUE, }, true) descr := PropertyDescriptor{ Getter: val, Configurable: FLAG_TRUE, Enumerable: ToFlag(s.enumerable), } obj.defineOwnProperty(propName, descr, true) vm.sp -= 2 vm.pc++ } type defineSetter struct { enumerable bool } func (s *defineSetter) exec(vm *vm) { obj := vm.r.toObject(vm.stack[vm.sp-3]) propName := vm.stack[vm.sp-2] val := vm.stack[vm.sp-1] method := vm.r.toObject(val) method.self.defineOwnPropertyStr("name", PropertyDescriptor{ Value: funcName("set ", propName), Configurable: FLAG_TRUE, }, true) descr := PropertyDescriptor{ Setter: val, Configurable: FLAG_TRUE, Enumerable: FLAG_TRUE, } obj.defineOwnProperty(propName, descr, true) vm.sp -= 2 vm.pc++ } type getProp unistring.String func (g getProp) exec(vm *vm) { v := vm.stack[vm.sp-1] obj := v.baseObject(vm.r) if obj == nil { vm.throw(vm.r.NewTypeError("Cannot read property '%s' of undefined", g)) return } vm.stack[vm.sp-1] = nilSafe(obj.self.getStr(unistring.String(g), v)) vm.pc++ } type getPropRecv unistring.String func (g getPropRecv) exec(vm *vm) { recv := vm.stack[vm.sp-2] v := vm.stack[vm.sp-1] obj := v.baseObject(vm.r) if obj == nil { vm.throw(vm.r.NewTypeError("Cannot read property '%s' of undefined", g)) return } vm.stack[vm.sp-2] = nilSafe(obj.self.getStr(unistring.String(g), recv)) vm.sp-- vm.pc++ } type getPropRecvCallee unistring.String func (g getPropRecvCallee) exec(vm *vm) { recv := vm.stack[vm.sp-2] v := vm.stack[vm.sp-1] obj := v.baseObject(vm.r) if obj == nil { vm.throw(vm.r.NewTypeError("Cannot read property '%s' of undefined", g)) return } n := unistring.String(g) prop := obj.self.getStr(n, recv) if prop == nil { prop = memberUnresolved{valueUnresolved{r: vm.r, ref: n}} } vm.stack[vm.sp-1] = prop vm.pc++ } type getPropCallee unistring.String func (g getPropCallee) exec(vm *vm) { v := vm.stack[vm.sp-1] obj := v.baseObject(vm.r) n := unistring.String(g) if obj == nil { vm.throw(vm.r.NewTypeError("Cannot read property '%s' of undefined or null", n)) return } prop := obj.self.getStr(n, v) if prop == nil { prop = memberUnresolved{valueUnresolved{r: vm.r, ref: n}} } vm.push(prop) vm.pc++ } type _getElem struct{} var getElem _getElem func (_getElem) exec(vm *vm) { v := vm.stack[vm.sp-2] obj := v.baseObject(vm.r) if obj == nil { vm.throw(vm.r.NewTypeError("Cannot read property '%s' of undefined", vm.stack[vm.sp-1])) return } propName := toPropertyKey(vm.stack[vm.sp-1]) vm.stack[vm.sp-2] = nilSafe(obj.get(propName, v)) vm.sp-- vm.pc++ } type _getElemRecv struct{} var getElemRecv _getElemRecv func (_getElemRecv) exec(vm *vm) { recv := vm.stack[vm.sp-3] v := vm.stack[vm.sp-1] obj := v.baseObject(vm.r) if obj == nil { vm.throw(vm.r.NewTypeError("Cannot read property '%s' of undefined", vm.stack[vm.sp-2])) return } propName := toPropertyKey(vm.stack[vm.sp-2]) vm.stack[vm.sp-3] = nilSafe(obj.get(propName, recv)) vm.sp -= 2 vm.pc++ } type _getKey struct{} var getKey _getKey func (_getKey) exec(vm *vm) { v := vm.stack[vm.sp-2] obj := v.baseObject(vm.r) propName := vm.stack[vm.sp-1] if obj == nil { vm.throw(vm.r.NewTypeError("Cannot read property '%s' of undefined", propName.String())) return } vm.stack[vm.sp-2] = nilSafe(obj.get(propName, v)) vm.sp-- vm.pc++ } type _getElemCallee struct{} var getElemCallee _getElemCallee func (_getElemCallee) exec(vm *vm) { v := vm.stack[vm.sp-2] obj := v.baseObject(vm.r) if obj == nil { vm.throw(vm.r.NewTypeError("Cannot read property '%s' of undefined", vm.stack[vm.sp-1])) return } propName := toPropertyKey(vm.stack[vm.sp-1]) prop := obj.get(propName, v) if prop == nil { prop = memberUnresolved{valueUnresolved{r: vm.r, ref: propName.string()}} } vm.stack[vm.sp-1] = prop vm.pc++ } type _getElemRecvCallee struct{} var getElemRecvCallee _getElemRecvCallee func (_getElemRecvCallee) exec(vm *vm) { recv := vm.stack[vm.sp-3] v := vm.stack[vm.sp-2] obj := v.baseObject(vm.r) if obj == nil { vm.throw(vm.r.NewTypeError("Cannot read property '%s' of undefined", vm.stack[vm.sp-1])) return } propName := toPropertyKey(vm.stack[vm.sp-1]) prop := obj.get(propName, recv) if prop == nil { prop = memberUnresolved{valueUnresolved{r: vm.r, ref: propName.string()}} } vm.stack[vm.sp-2] = prop vm.sp-- vm.pc++ } type _dup struct{} var dup _dup func (_dup) exec(vm *vm) { vm.push(vm.stack[vm.sp-1]) vm.pc++ } type dupN uint32 func (d dupN) exec(vm *vm) { vm.push(vm.stack[vm.sp-1-int(d)]) vm.pc++ } type rdupN uint32 func (d rdupN) exec(vm *vm) { vm.stack[vm.sp-1-int(d)] = vm.stack[vm.sp-1] vm.pc++ } type dupLast uint32 func (d dupLast) exec(vm *vm) { e := vm.sp + int(d) vm.stack.expand(e) copy(vm.stack[vm.sp:e], vm.stack[vm.sp-int(d):]) vm.sp = e vm.pc++ } type _newObject struct{} var newObject _newObject func (_newObject) exec(vm *vm) { vm.push(vm.r.NewObject()) vm.pc++ } type newArray uint32 func (l newArray) exec(vm *vm) { values := make([]Value, 0, l) vm.push(vm.r.newArrayValues(values)) vm.pc++ } type _pushArrayItem struct{} var pushArrayItem _pushArrayItem func (_pushArrayItem) exec(vm *vm) { arr := vm.stack[vm.sp-2].(*Object).self.(*arrayObject) if arr.length < math.MaxUint32 { arr.length++ } else { vm.throw(vm.r.newError(vm.r.getRangeError(), "Invalid array length")) return } val := vm.stack[vm.sp-1] arr.values = append(arr.values, val) if val != nil { arr.objCount++ } vm.sp-- vm.pc++ } type _pushArraySpread struct{} var pushArraySpread _pushArraySpread func (_pushArraySpread) exec(vm *vm) { arr := vm.stack[vm.sp-2].(*Object).self.(*arrayObject) vm.r.getIterator(vm.stack[vm.sp-1], nil).iterate(func(val Value) { if arr.length < math.MaxUint32 { arr.length++ } else { vm.throw(vm.r.newError(vm.r.getRangeError(), "Invalid array length")) return } arr.values = append(arr.values, val) arr.objCount++ }) vm.sp-- vm.pc++ } type _pushSpread struct{} var pushSpread _pushSpread func (_pushSpread) exec(vm *vm) { vm.sp-- obj := vm.stack[vm.sp] vm.r.getIterator(obj, nil).iterate(func(val Value) { vm.push(val) }) vm.pc++ } type _newArrayFromIter struct{} var newArrayFromIter _newArrayFromIter func (_newArrayFromIter) exec(vm *vm) { var values []Value l := len(vm.iterStack) - 1 iter := vm.iterStack[l].iter vm.iterStack[l] = iterStackItem{} vm.iterStack = vm.iterStack[:l] if iter.iterator != nil { iter.iterate(func(val Value) { values = append(values, val) }) } vm.push(vm.r.newArrayValues(values)) vm.pc++ } type newRegexp struct { pattern *regexpPattern src String } func (n *newRegexp) exec(vm *vm) { vm.push(vm.r.newRegExpp(n.pattern.clone(), n.src, vm.r.getRegExpPrototype()).val) vm.pc++ } func (vm *vm) setLocalLex(s int) { v := vm.stack[vm.sp-1] level := s >> 24 idx := uint32(s & 0x00FFFFFF) stash := vm.stash for i := 0; i < level; i++ { stash = stash.outer } p := &stash.values[idx] if *p == nil { panic(errAccessBeforeInit) } *p = v vm.pc++ } func (vm *vm) initLocal(s int) { v := vm.stack[vm.sp-1] level := s >> 24 idx := uint32(s & 0x00FFFFFF) stash := vm.stash for i := 0; i < level; i++ { stash = stash.outer } stash.initByIdx(idx, v) vm.pc++ } type storeStash uint32 func (s storeStash) exec(vm *vm) { vm.initLocal(int(s)) } type storeStashP uint32 func (s storeStashP) exec(vm *vm) { vm.initLocal(int(s)) vm.sp-- } type storeStashLex uint32 func (s storeStashLex) exec(vm *vm) { vm.setLocalLex(int(s)) } type storeStashLexP uint32 func (s storeStashLexP) exec(vm *vm) { vm.setLocalLex(int(s)) vm.sp-- } type initStash uint32 func (s initStash) exec(vm *vm) { vm.initLocal(int(s)) } type initStashP uint32 func (s initStashP) exec(vm *vm) { vm.initLocal(int(s)) vm.sp-- } type initGlobalP unistring.String func (s initGlobalP) exec(vm *vm) { vm.sp-- vm.r.global.stash.initByName(unistring.String(s), vm.stack[vm.sp]) vm.pc++ } type initGlobal unistring.String func (s initGlobal) exec(vm *vm) { vm.r.global.stash.initByName(unistring.String(s), vm.stack[vm.sp]) vm.pc++ } type resolveVar1 unistring.String func (s resolveVar1) exec(vm *vm) { name := unistring.String(s) var ref ref for stash := vm.stash; stash != nil; stash = stash.outer { ref = stash.getRefByName(name, false) if ref != nil { goto end } } ref = &objStrRef{ base: vm.r.globalObject, name: name, binding: true, } end: vm.refStack = append(vm.refStack, ref) vm.pc++ } type deleteVar unistring.String func (d deleteVar) exec(vm *vm) { name := unistring.String(d) ret := true for stash := vm.stash; stash != nil; stash = stash.outer { if stash.obj != nil { if stashObjHas(stash.obj, name) { ret = stash.obj.self.deleteStr(name, false) goto end } } else { if idx, exists := stash.names[name]; exists { if idx&(maskVar|maskDeletable) == maskVar|maskDeletable { stash.deleteBinding(name) } else { ret = false } goto end } } } if vm.r.globalObject.self.hasPropertyStr(name) { ret = vm.r.globalObject.self.deleteStr(name, false) } end: if ret { vm.push(valueTrue) } else { vm.push(valueFalse) } vm.pc++ } type deleteGlobal unistring.String func (d deleteGlobal) exec(vm *vm) { name := unistring.String(d) var ret bool if vm.r.globalObject.self.hasPropertyStr(name) { ret = vm.r.globalObject.self.deleteStr(name, false) } else { ret = true } if ret { vm.push(valueTrue) } else { vm.push(valueFalse) } vm.pc++ } type resolveVar1Strict unistring.String func (s resolveVar1Strict) exec(vm *vm) { name := unistring.String(s) var ref ref for stash := vm.stash; stash != nil; stash = stash.outer { ref = stash.getRefByName(name, true) if ref != nil { goto end } } if vm.r.globalObject.self.hasPropertyStr(name) { ref = &objStrRef{ base: vm.r.globalObject, name: name, binding: true, strict: true, } goto end } ref = &unresolvedRef{ runtime: vm.r, name: name, } end: vm.refStack = append(vm.refStack, ref) vm.pc++ } type setGlobal unistring.String func (s setGlobal) exec(vm *vm) { vm.r.setGlobal(unistring.String(s), vm.peek(), false) vm.pc++ } type setGlobalStrict unistring.String func (s setGlobalStrict) exec(vm *vm) { vm.r.setGlobal(unistring.String(s), vm.peek(), true) vm.pc++ } // Load a var from stash type loadStash uint32 func (g loadStash) exec(vm *vm) { level := int(g >> 24) idx := uint32(g & 0x00FFFFFF) stash := vm.stash for i := 0; i < level; i++ { stash = stash.outer } vm.push(nilSafe(stash.getByIdx(idx))) vm.pc++ } // Load a lexical binding from stash type loadStashLex uint32 func (g loadStashLex) exec(vm *vm) { level := int(g >> 24) idx := uint32(g & 0x00FFFFFF) stash := vm.stash for i := 0; i < level; i++ { stash = stash.outer } v := stash.getByIdx(idx) if v == nil { vm.throw(errAccessBeforeInit) return } vm.push(v) vm.pc++ } // scan dynamic stashes up to the given level (encoded as 8 most significant bits of idx), if not found // return the indexed var binding value from stash type loadMixed struct { name unistring.String idx uint32 callee bool } func (g *loadMixed) exec(vm *vm) { level := int(g.idx >> 24) idx := g.idx & 0x00FFFFFF stash := vm.stash name := g.name for i := 0; i < level; i++ { if v, found := stash.getByName(name); found { if g.callee { if stash.obj != nil { vm.push(stash.obj) } else { vm.push(_undefined) } } vm.push(v) goto end } stash = stash.outer } if g.callee { vm.push(_undefined) } if stash != nil { vm.push(nilSafe(stash.getByIdx(idx))) } end: vm.pc++ } // scan dynamic stashes up to the given level (encoded as 8 most significant bits of idx), if not found // return the indexed lexical binding value from stash type loadMixedLex loadMixed func (g *loadMixedLex) exec(vm *vm) { level := int(g.idx >> 24) idx := g.idx & 0x00FFFFFF stash := vm.stash name := g.name for i := 0; i < level; i++ { if v, found := stash.getByName(name); found { if g.callee { if stash.obj != nil { vm.push(stash.obj) } else { vm.push(_undefined) } } vm.push(v) goto end } stash = stash.outer } if g.callee { vm.push(_undefined) } if stash != nil { v := stash.getByIdx(idx) if v == nil { vm.throw(errAccessBeforeInit) return } vm.push(v) } end: vm.pc++ } // scan dynamic stashes up to the given level (encoded as 8 most significant bits of idx), if not found // return the indexed var binding value from stack type loadMixedStack struct { name unistring.String idx int level uint8 callee bool } // same as loadMixedStack, but the args have been moved to stash (therefore stack layout is different) type loadMixedStack1 loadMixedStack func (g *loadMixedStack) exec(vm *vm) { stash := vm.stash name := g.name level := int(g.level) for i := 0; i < level; i++ { if v, found := stash.getByName(name); found { if g.callee { if stash.obj != nil { vm.push(stash.obj) } else { vm.push(_undefined) } } vm.push(v) goto end } stash = stash.outer } if g.callee { vm.push(_undefined) } loadStack(g.idx).exec(vm) return end: vm.pc++ } func (g *loadMixedStack1) exec(vm *vm) { stash := vm.stash name := g.name level := int(g.level) for i := 0; i < level; i++ { if v, found := stash.getByName(name); found { if g.callee { if stash.obj != nil { vm.push(stash.obj) } else { vm.push(_undefined) } } vm.push(v) goto end } stash = stash.outer } if g.callee { vm.push(_undefined) } loadStack1(g.idx).exec(vm) return end: vm.pc++ } type loadMixedStackLex loadMixedStack // same as loadMixedStackLex but when the arguments have been moved into stash type loadMixedStack1Lex loadMixedStack func (g *loadMixedStackLex) exec(vm *vm) { stash := vm.stash name := g.name level := int(g.level) for i := 0; i < level; i++ { if v, found := stash.getByName(name); found { if g.callee { if stash.obj != nil { vm.push(stash.obj) } else { vm.push(_undefined) } } vm.push(v) goto end } stash = stash.outer } if g.callee { vm.push(_undefined) } loadStackLex(g.idx).exec(vm) return end: vm.pc++ } func (g *loadMixedStack1Lex) exec(vm *vm) { stash := vm.stash name := g.name level := int(g.level) for i := 0; i < level; i++ { if v, found := stash.getByName(name); found { if g.callee { if stash.obj != nil { vm.push(stash.obj) } else { vm.push(_undefined) } } vm.push(v) goto end } stash = stash.outer } if g.callee { vm.push(_undefined) } loadStack1Lex(g.idx).exec(vm) return end: vm.pc++ } type resolveMixed struct { name unistring.String idx uint32 typ varType strict bool } func newStashRef(typ varType, name unistring.String, v *[]Value, idx int) ref { switch typ { case varTypeVar: return &stashRef{ n: name, v: v, idx: idx, } case varTypeLet: return &stashRefLex{ stashRef: stashRef{ n: name, v: v, idx: idx, }, } case varTypeConst, varTypeStrictConst: return &stashRefConst{ stashRefLex: stashRefLex{ stashRef: stashRef{ n: name, v: v, idx: idx, }, }, strictConst: typ == varTypeStrictConst, } } panic("unsupported var type") } func (r *resolveMixed) exec(vm *vm) { level := int(r.idx >> 24) idx := r.idx & 0x00FFFFFF stash := vm.stash var ref ref for i := 0; i < level; i++ { ref = stash.getRefByName(r.name, r.strict) if ref != nil { goto end } stash = stash.outer } if stash != nil { ref = newStashRef(r.typ, r.name, &stash.values, int(idx)) goto end } ref = &unresolvedRef{ runtime: vm.r, name: r.name, } end: vm.refStack = append(vm.refStack, ref) vm.pc++ } type resolveMixedStack struct { name unistring.String idx int typ varType level uint8 strict bool } type resolveMixedStack1 resolveMixedStack func (r *resolveMixedStack) exec(vm *vm) { level := int(r.level) stash := vm.stash var ref ref var idx int for i := 0; i < level; i++ { ref = stash.getRefByName(r.name, r.strict) if ref != nil { goto end } stash = stash.outer } if r.idx > 0 { idx = vm.sb + vm.args + r.idx } else { idx = vm.sb - r.idx } ref = newStashRef(r.typ, r.name, (*[]Value)(&vm.stack), idx) end: vm.refStack = append(vm.refStack, ref) vm.pc++ } func (r *resolveMixedStack1) exec(vm *vm) { level := int(r.level) stash := vm.stash var ref ref for i := 0; i < level; i++ { ref = stash.getRefByName(r.name, r.strict) if ref != nil { goto end } stash = stash.outer } ref = newStashRef(r.typ, r.name, (*[]Value)(&vm.stack), vm.sb+r.idx) end: vm.refStack = append(vm.refStack, ref) vm.pc++ } type _getValue struct{} var getValue _getValue func (_getValue) exec(vm *vm) { ref := vm.refStack[len(vm.refStack)-1] if v := ref.get(); v != nil { vm.push(v) } else { vm.throw(vm.r.newReferenceError(ref.refname())) return } vm.pc++ } type _putValue struct{} var putValue _putValue func (_putValue) exec(vm *vm) { l := len(vm.refStack) - 1 ref := vm.refStack[l] vm.refStack[l] = nil vm.refStack = vm.refStack[:l] ref.set(vm.stack[vm.sp-1]) vm.pc++ } type _putValueP struct{} var putValueP _putValueP func (_putValueP) exec(vm *vm) { l := len(vm.refStack) - 1 ref := vm.refStack[l] vm.refStack[l] = nil vm.refStack = vm.refStack[:l] ref.set(vm.stack[vm.sp-1]) vm.sp-- vm.pc++ } type _initValueP struct{} var initValueP _initValueP func (_initValueP) exec(vm *vm) { l := len(vm.refStack) - 1 ref := vm.refStack[l] vm.refStack[l] = nil vm.refStack = vm.refStack[:l] ref.init(vm.stack[vm.sp-1]) vm.sp-- vm.pc++ } type loadDynamic unistring.String func (n loadDynamic) exec(vm *vm) { name := unistring.String(n) var val Value for stash := vm.stash; stash != nil; stash = stash.outer { if v, exists := stash.getByName(name); exists { val = v break } } if val == nil { val = vm.r.globalObject.self.getStr(name, nil) if val == nil { vm.throw(vm.r.newReferenceError(name)) return } } vm.push(val) vm.pc++ } type loadDynamicRef unistring.String func (n loadDynamicRef) exec(vm *vm) { name := unistring.String(n) var val Value for stash := vm.stash; stash != nil; stash = stash.outer { if v, exists := stash.getByName(name); exists { val = v break } } if val == nil { val = vm.r.globalObject.self.getStr(name, nil) if val == nil { val = valueUnresolved{r: vm.r, ref: name} } } vm.push(val) vm.pc++ } type loadDynamicCallee unistring.String func (n loadDynamicCallee) exec(vm *vm) { name := unistring.String(n) var val Value var callee *Object for stash := vm.stash; stash != nil; stash = stash.outer { if v, exists := stash.getByName(name); exists { callee = stash.obj val = v break } } if val == nil { val = vm.r.globalObject.self.getStr(name, nil) if val == nil { val = valueUnresolved{r: vm.r, ref: name} } } if callee != nil { vm.push(callee) } else { vm.push(_undefined) } vm.push(val) vm.pc++ } type _pop struct{} var pop _pop func (_pop) exec(vm *vm) { vm.sp-- vm.pc++ } func (vm *vm) callEval(n int, strict bool) { if vm.r.toObject(vm.stack[vm.sp-n-1]) == vm.r.global.Eval { if n > 0 { srcVal := vm.stack[vm.sp-n] if src, ok := srcVal.(String); ok { ret := vm.r.eval(src, true, strict) vm.stack[vm.sp-n-2] = ret } else { vm.stack[vm.sp-n-2] = srcVal } } else { vm.stack[vm.sp-n-2] = _undefined } vm.sp -= n + 1 vm.pc++ } else { call(n).exec(vm) } } type callEval uint32 func (numargs callEval) exec(vm *vm) { vm.callEval(int(numargs), false) } type callEvalStrict uint32 func (numargs callEvalStrict) exec(vm *vm) { vm.callEval(int(numargs), true) } type _callEvalVariadic struct{} var callEvalVariadic _callEvalVariadic func (_callEvalVariadic) exec(vm *vm) { vm.callEval(vm.countVariadicArgs()-2, false) } type _callEvalVariadicStrict struct{} var callEvalVariadicStrict _callEvalVariadicStrict func (_callEvalVariadicStrict) exec(vm *vm) { vm.callEval(vm.countVariadicArgs()-2, true) } type _boxThis struct{} var boxThis _boxThis func (_boxThis) exec(vm *vm) { v := vm.stack[vm.sb] if v == _undefined || v == _null { vm.stack[vm.sb] = vm.r.globalObject } else { vm.stack[vm.sb] = v.ToObject(vm.r) } vm.pc++ } var variadicMarker Value = newSymbol(asciiString("[variadic marker]")) type _startVariadic struct{} var startVariadic _startVariadic func (_startVariadic) exec(vm *vm) { vm.push(variadicMarker) vm.pc++ } type _callVariadic struct{} var callVariadic _callVariadic func (vm *vm) countVariadicArgs() int { count := 0 for i := vm.sp - 1; i >= 0; i-- { if vm.stack[i] == variadicMarker { return count } count++ } panic("Variadic marker was not found. Compiler bug.") } func (_callVariadic) exec(vm *vm) { call(vm.countVariadicArgs() - 2).exec(vm) } type _endVariadic struct{} var endVariadic _endVariadic func (_endVariadic) exec(vm *vm) { vm.sp-- vm.stack[vm.sp-1] = vm.stack[vm.sp] vm.pc++ } type call uint32 func (numargs call) exec(vm *vm) { // this // callee // arg0 // ... // arg n := int(numargs) v := vm.stack[vm.sp-n-1] // callee obj := vm.toCallee(v) obj.self.vmCall(vm, n) } func (vm *vm) clearStack() { sp := vm.sp stackTail := vm.stack[sp:] for i := range stackTail { stackTail[i] = nil } vm.stack = vm.stack[:sp] } type enterBlock struct { names map[unistring.String]uint32 stashSize uint32 stackSize uint32 } func (e *enterBlock) exec(vm *vm) { if e.stashSize > 0 { vm.newStash() vm.stash.values = make([]Value, e.stashSize) if len(e.names) > 0 { vm.stash.names = e.names } } ss := int(e.stackSize) vm.stack.expand(vm.sp + ss - 1) vv := vm.stack[vm.sp : vm.sp+ss] for i := range vv { vv[i] = nil } vm.sp += ss vm.pc++ } type enterCatchBlock struct { names map[unistring.String]uint32 stashSize uint32 stackSize uint32 } func (e *enterCatchBlock) exec(vm *vm) { vm.newStash() vm.stash.values = make([]Value, e.stashSize) if len(e.names) > 0 { vm.stash.names = e.names } vm.sp-- vm.stash.values[0] = vm.stack[vm.sp] ss := int(e.stackSize) vm.stack.expand(vm.sp + ss - 1) vv := vm.stack[vm.sp : vm.sp+ss] for i := range vv { vv[i] = nil } vm.sp += ss vm.pc++ } type leaveBlock struct { stackSize uint32 popStash bool } func (l *leaveBlock) exec(vm *vm) { if l.popStash { vm.stash = vm.stash.outer } if ss := l.stackSize; ss > 0 { vm.sp -= int(ss) } vm.pc++ } type enterFunc struct { names map[unistring.String]uint32 stashSize uint32 stackSize uint32 numArgs uint32 funcType funcType argsToStash bool extensible bool } func (e *enterFunc) exec(vm *vm) { // Input stack: // // callee // this // arg0 // ... // argN // <- sp // Output stack: // // this <- sb // // <- sp sp := vm.sp vm.sb = sp - vm.args - 1 vm.newStash() stash := vm.stash stash.funcType = e.funcType stash.values = make([]Value, e.stashSize) if len(e.names) > 0 { if e.extensible { m := make(map[unistring.String]uint32, len(e.names)) for name, idx := range e.names { m[name] = idx } stash.names = m } else { stash.names = e.names } } ss := int(e.stackSize) ea := 0 if e.argsToStash { offset := vm.args - int(e.numArgs) copy(stash.values, vm.stack[sp-vm.args:sp]) if offset > 0 { vm.stash.extraArgs = make([]Value, offset) copy(stash.extraArgs, vm.stack[sp-offset:]) } else { vv := stash.values[vm.args:e.numArgs] for i := range vv { vv[i] = _undefined } } sp -= vm.args } else { d := int(e.numArgs) - vm.args if d > 0 { ss += d ea = d vm.args = int(e.numArgs) } } vm.stack.expand(sp + ss - 1) if ea > 0 { vv := vm.stack[sp : vm.sp+ea] for i := range vv { vv[i] = _undefined } } vv := vm.stack[sp+ea : sp+ss] for i := range vv { vv[i] = nil } vm.sp = sp + ss vm.pc++ } // Similar to enterFunc, but for when arguments may be accessed before they are initialised, // e.g. by an eval() code or from a closure, or from an earlier initialiser code. // In this case the arguments remain on stack, first argsToCopy of them are copied to the stash. type enterFunc1 struct { names map[unistring.String]uint32 stashSize uint32 numArgs uint32 argsToCopy uint32 funcType funcType extensible bool } func (e *enterFunc1) exec(vm *vm) { sp := vm.sp vm.sb = sp - vm.args - 1 vm.newStash() stash := vm.stash stash.funcType = e.funcType stash.values = make([]Value, e.stashSize) if len(e.names) > 0 { if e.extensible { m := make(map[unistring.String]uint32, len(e.names)) for name, idx := range e.names { m[name] = idx } stash.names = m } else { stash.names = e.names } } offset := vm.args - int(e.argsToCopy) if offset > 0 { copy(stash.values, vm.stack[sp-vm.args:sp-offset]) if offset := vm.args - int(e.numArgs); offset > 0 { vm.stash.extraArgs = make([]Value, offset) copy(stash.extraArgs, vm.stack[sp-offset:]) } } else { copy(stash.values, vm.stack[sp-vm.args:sp]) if int(e.argsToCopy) > vm.args { vv := stash.values[vm.args:e.argsToCopy] for i := range vv { vv[i] = _undefined } } } vm.pc++ } // Finalises the initialisers section and starts the function body which has its own // scope. When used in conjunction with enterFunc1 adjustStack is set to true which // causes the arguments to be removed from the stack. type enterFuncBody struct { enterBlock funcType funcType extensible bool adjustStack bool } func (e *enterFuncBody) exec(vm *vm) { if e.stashSize > 0 || e.extensible { vm.newStash() stash := vm.stash stash.funcType = e.funcType stash.values = make([]Value, e.stashSize) if len(e.names) > 0 { if e.extensible { m := make(map[unistring.String]uint32, len(e.names)) for name, idx := range e.names { m[name] = idx } stash.names = m } else { stash.names = e.names } } } sp := vm.sp if e.adjustStack { sp -= vm.args } nsp := sp + int(e.stackSize) if e.stackSize > 0 { vm.stack.expand(nsp - 1) vv := vm.stack[sp:nsp] for i := range vv { vv[i] = nil } } vm.sp = nsp vm.pc++ } type _ret struct{} var ret _ret func (_ret) exec(vm *vm) { // callee -3 // this -2 <- sb // retval -1 vm.stack[vm.sb-1] = vm.stack[vm.sp-1] vm.sp = vm.sb vm.popCtx() vm.pc++ } type cret uint32 func (c cret) exec(vm *vm) { vm.stack[vm.sb] = *vm.getStashPtr(uint32(c)) ret.exec(vm) } type enterFuncStashless struct { stackSize uint32 args uint32 } func (e *enterFuncStashless) exec(vm *vm) { sp := vm.sp vm.sb = sp - vm.args - 1 d := int(e.args) - vm.args if d > 0 { ss := sp + int(e.stackSize) + d vm.stack.expand(ss) vv := vm.stack[sp : sp+d] for i := range vv { vv[i] = _undefined } vv = vm.stack[sp+d : ss] for i := range vv { vv[i] = nil } vm.args = int(e.args) vm.sp = ss } else { if e.stackSize > 0 { ss := sp + int(e.stackSize) vm.stack.expand(ss) vv := vm.stack[sp:ss] for i := range vv { vv[i] = nil } vm.sp = ss } } vm.pc++ } type newFuncInstruction interface { getPrg() *Program } type newFunc struct { prg *Program name unistring.String source string length int strict bool } func (n *newFunc) exec(vm *vm) { obj := vm.r.newFunc(n.name, n.length, n.strict) obj.prg = n.prg obj.stash = vm.stash obj.privEnv = vm.privEnv obj.src = n.source vm.push(obj.val) vm.pc++ } func (n *newFunc) getPrg() *Program { return n.prg } type newAsyncFunc struct { newFunc } func (n *newAsyncFunc) exec(vm *vm) { obj := vm.r.newAsyncFunc(n.name, n.length, n.strict) obj.prg = n.prg obj.stash = vm.stash obj.privEnv = vm.privEnv obj.src = n.source vm.push(obj.val) vm.pc++ } type newGeneratorFunc struct { newFunc } func (n *newGeneratorFunc) exec(vm *vm) { obj := vm.r.newGeneratorFunc(n.name, n.length, n.strict) obj.prg = n.prg obj.stash = vm.stash obj.privEnv = vm.privEnv obj.src = n.source vm.push(obj.val) vm.pc++ } type newMethod struct { newFunc homeObjOffset uint32 } func (n *newMethod) _exec(vm *vm, obj *methodFuncObject) { obj.prg = n.prg obj.stash = vm.stash obj.privEnv = vm.privEnv obj.src = n.source if n.homeObjOffset > 0 { obj.homeObject = vm.r.toObject(vm.stack[vm.sp-int(n.homeObjOffset)]) } vm.push(obj.val) vm.pc++ } func (n *newMethod) exec(vm *vm) { n._exec(vm, vm.r.newMethod(n.name, n.length, n.strict)) } type newAsyncMethod struct { newMethod } func (n *newAsyncMethod) exec(vm *vm) { obj := vm.r.newAsyncMethod(n.name, n.length, n.strict) n._exec(vm, &obj.methodFuncObject) } type newGeneratorMethod struct { newMethod } func (n *newGeneratorMethod) exec(vm *vm) { obj := vm.r.newGeneratorMethod(n.name, n.length, n.strict) n._exec(vm, &obj.methodFuncObject) } type newArrowFunc struct { newFunc } type newAsyncArrowFunc struct { newArrowFunc } func getFuncObject(v Value) *Object { if o, ok := v.(*Object); ok { if fn, ok := o.self.(*arrowFuncObject); ok { return fn.funcObj } return o } if v == _undefined { return nil } panic(typeError("Value is not an Object")) } func getHomeObject(v Value) *Object { if o, ok := v.(*Object); ok { switch fn := o.self.(type) { case *methodFuncObject: return fn.homeObject case *generatorMethodFuncObject: return fn.homeObject case *asyncMethodFuncObject: return fn.homeObject case *classFuncObject: return o.runtime.toObject(fn.getStr("prototype", nil)) case *arrowFuncObject: return getHomeObject(fn.funcObj) case *asyncArrowFuncObject: return getHomeObject(fn.funcObj) } } panic(newTypeError("Compiler bug: getHomeObject() on the wrong value: %T", v)) } func (n *newArrowFunc) _exec(vm *vm, obj *arrowFuncObject) { obj.prg = n.prg obj.stash = vm.stash obj.privEnv = vm.privEnv obj.src = n.source if vm.sb > 0 { obj.funcObj = getFuncObject(vm.stack[vm.sb-1]) } vm.push(obj.val) vm.pc++ } func (n *newArrowFunc) exec(vm *vm) { n._exec(vm, vm.r.newArrowFunc(n.name, n.length, n.strict)) } func (n *newAsyncArrowFunc) exec(vm *vm) { obj := vm.r.newAsyncArrowFunc(n.name, n.length, n.strict) n._exec(vm, &obj.arrowFuncObject) } func (vm *vm) alreadyDeclared(name unistring.String) Value { return vm.r.newError(vm.r.getSyntaxError(), "Identifier '%s' has already been declared", name) } func (vm *vm) checkBindVarsGlobal(names []unistring.String) { o := vm.r.globalObject.self sn := vm.r.global.stash.names if bo, ok := o.(*baseObject); ok { // shortcut if bo.extensible { for _, name := range names { if _, exists := sn[name]; exists { panic(vm.alreadyDeclared(name)) } } } else { for _, name := range names { if !bo.hasOwnPropertyStr(name) { panic(vm.r.NewTypeError("Cannot define global variable '%s', global object is not extensible", name)) } if _, exists := sn[name]; exists { panic(vm.alreadyDeclared(name)) } } } } else { for _, name := range names { if !o.hasOwnPropertyStr(name) && !o.isExtensible() { panic(vm.r.NewTypeError("Cannot define global variable '%s', global object is not extensible", name)) } if _, exists := sn[name]; exists { panic(vm.alreadyDeclared(name)) } } } } func (vm *vm) createGlobalVarBindings(names []unistring.String, d bool) { o := vm.r.globalObject.self if bo, ok := o.(*templatedObject); ok { for _, name := range names { if !bo.hasOwnPropertyStr(name) && bo.extensible { bo._putProp(name, _undefined, true, true, d) } } } else { var cf Flag if d { cf = FLAG_TRUE } else { cf = FLAG_FALSE } for _, name := range names { if !o.hasOwnPropertyStr(name) && o.isExtensible() { o.defineOwnPropertyStr(name, PropertyDescriptor{ Value: _undefined, Writable: FLAG_TRUE, Enumerable: FLAG_TRUE, Configurable: cf, }, true) o.setOwnStr(name, _undefined, false) } } } } func (vm *vm) createGlobalFuncBindings(names []unistring.String, d bool) { o := vm.r.globalObject.self b := vm.sp - len(names) var shortcutObj *templatedObject if o, ok := o.(*templatedObject); ok { shortcutObj = o } for i, name := range names { var desc PropertyDescriptor prop := o.getOwnPropStr(name) desc.Value = vm.stack[b+i] if shortcutObj != nil && prop == nil && shortcutObj.extensible { shortcutObj._putProp(name, desc.Value, true, true, d) } else { if prop, ok := prop.(*valueProperty); ok && !prop.configurable { // no-op } else { desc.Writable = FLAG_TRUE desc.Enumerable = FLAG_TRUE if d { desc.Configurable = FLAG_TRUE } else { desc.Configurable = FLAG_FALSE } } if shortcutObj != nil { shortcutObj.defineOwnPropertyStr(name, desc, true) } else { o.defineOwnPropertyStr(name, desc, true) o.setOwnStr(name, desc.Value, false) // not a bug, see https://262.ecma-international.org/#sec-createglobalfunctionbinding } } } vm.sp = b } func (vm *vm) checkBindFuncsGlobal(names []unistring.String) { o := vm.r.globalObject.self sn := vm.r.global.stash.names for _, name := range names { if _, exists := sn[name]; exists { panic(vm.alreadyDeclared(name)) } prop := o.getOwnPropStr(name) allowed := true switch prop := prop.(type) { case nil: allowed = o.isExtensible() case *valueProperty: allowed = prop.configurable || prop.getterFunc == nil && prop.setterFunc == nil && prop.writable && prop.enumerable } if !allowed { panic(vm.r.NewTypeError("Cannot redefine global function '%s'", name)) } } } func (vm *vm) checkBindLexGlobal(names []unistring.String) { o := vm.r.globalObject.self s := &vm.r.global.stash for _, name := range names { if _, exists := s.names[name]; exists { goto fail } if prop, ok := o.getOwnPropStr(name).(*valueProperty); ok && !prop.configurable { goto fail } continue fail: panic(vm.alreadyDeclared(name)) } } type bindVars struct { names []unistring.String deletable bool } func (d *bindVars) exec(vm *vm) { var target *stash for _, name := range d.names { for s := vm.stash; s != nil; s = s.outer { if idx, exists := s.names[name]; exists && idx&maskVar == 0 { vm.throw(vm.alreadyDeclared(name)) return } if s.isVariable() { target = s break } } } if target == nil { target = vm.stash } deletable := d.deletable for _, name := range d.names { target.createBinding(name, deletable) } vm.pc++ } type bindGlobal struct { vars, funcs, lets, consts []unistring.String deletable bool } func (b *bindGlobal) exec(vm *vm) { vm.checkBindFuncsGlobal(b.funcs) vm.checkBindLexGlobal(b.lets) vm.checkBindLexGlobal(b.consts) vm.checkBindVarsGlobal(b.vars) s := &vm.r.global.stash for _, name := range b.lets { s.createLexBinding(name, false) } for _, name := range b.consts { s.createLexBinding(name, true) } vm.createGlobalFuncBindings(b.funcs, b.deletable) vm.createGlobalVarBindings(b.vars, b.deletable) vm.pc++ } type jne int32 func (j jne) exec(vm *vm) { vm.sp-- if !vm.stack[vm.sp].ToBoolean() { vm.pc += int(j) } else { vm.pc++ } } type jeq int32 func (j jeq) exec(vm *vm) { vm.sp-- if vm.stack[vm.sp].ToBoolean() { vm.pc += int(j) } else { vm.pc++ } } type jeq1 int32 func (j jeq1) exec(vm *vm) { if vm.stack[vm.sp-1].ToBoolean() { vm.pc += int(j) } else { vm.sp-- vm.pc++ } } type jneq1 int32 func (j jneq1) exec(vm *vm) { if !vm.stack[vm.sp-1].ToBoolean() { vm.pc += int(j) } else { vm.sp-- vm.pc++ } } type jdef int32 func (j jdef) exec(vm *vm) { if vm.stack[vm.sp-1] != _undefined { vm.pc += int(j) } else { vm.sp-- vm.pc++ } } type jdefP int32 func (j jdefP) exec(vm *vm) { if vm.stack[vm.sp-1] != _undefined { vm.pc += int(j) } else { vm.pc++ } vm.sp-- } type jopt int32 func (j jopt) exec(vm *vm) { switch vm.stack[vm.sp-1] { case _null: vm.stack[vm.sp-1] = _undefined fallthrough case _undefined: vm.pc += int(j) default: vm.pc++ } } type joptc int32 func (j joptc) exec(vm *vm) { switch vm.stack[vm.sp-1].(type) { case valueNull, valueUndefined, memberUnresolved: vm.sp-- vm.stack[vm.sp-1] = _undefined vm.pc += int(j) default: vm.pc++ } } type jcoalesc int32 func (j jcoalesc) exec(vm *vm) { switch vm.stack[vm.sp-1] { case _undefined, _null: vm.sp-- vm.pc++ default: vm.pc += int(j) } } type _not struct{} var not _not func (_not) exec(vm *vm) { if vm.stack[vm.sp-1].ToBoolean() { vm.stack[vm.sp-1] = valueFalse } else { vm.stack[vm.sp-1] = valueTrue } vm.pc++ } func toPrimitiveNumber(v Value) Value { if o, ok := v.(*Object); ok { return o.toPrimitiveNumber() } return v } func toPrimitive(v Value) Value { if o, ok := v.(*Object); ok { return o.toPrimitive() } return v } func cmp(px, py Value) Value { var ret bool xs, isPxString := px.(String) ys, isPyString := py.(String) if isPxString && isPyString { ret = xs.CompareTo(ys) < 0 goto end } else { if px, ok := px.(*valueBigInt); ok && isPyString { ny, err := stringToBigInt(ys.toTrimmedUTF8()) if err != nil { return _undefined } ret = (*big.Int)(px).Cmp(ny) < 0 goto end } if py, ok := py.(*valueBigInt); ok && isPxString { nx, err := stringToBigInt(xs.toTrimmedUTF8()) if err != nil { return _undefined } ret = nx.Cmp((*big.Int)(py)) < 0 goto end } } px = toNumeric(px) py = toNumeric(py) switch nx := px.(type) { case valueInt: switch ny := py.(type) { case valueInt: ret = nx < ny goto end case *valueBigInt: ret = big.NewInt(int64(nx)).Cmp((*big.Int)(ny)) < 0 goto end } case valueFloat: switch ny := py.(type) { case *valueBigInt: switch { case math.IsNaN(float64(nx)): return _undefined case nx == _negativeInf: ret = true goto end } if nx := big.NewFloat(float64(nx)); nx.IsInt() { nx, _ := nx.Int(nil) ret = nx.Cmp((*big.Int)(ny)) < 0 } else { ret = nx.Cmp(new(big.Float).SetInt((*big.Int)(ny))) < 0 } goto end } case *valueBigInt: switch ny := py.(type) { case valueInt: ret = (*big.Int)(nx).Cmp(big.NewInt(int64(ny))) < 0 goto end case valueFloat: switch { case math.IsNaN(float64(ny)): return _undefined case ny == _positiveInf: ret = true goto end } if ny := big.NewFloat(float64(ny)); ny.IsInt() { ny, _ := ny.Int(nil) ret = (*big.Int)(nx).Cmp(ny) < 0 } else { ret = new(big.Float).SetInt((*big.Int)(nx)).Cmp(ny) < 0 } goto end case *valueBigInt: ret = (*big.Int)(nx).Cmp((*big.Int)(ny)) < 0 goto end } } if nx, ny := px.ToFloat(), py.ToFloat(); math.IsNaN(nx) || math.IsNaN(ny) { return _undefined } else { ret = nx < ny } end: if ret { return valueTrue } return valueFalse } type _op_lt struct{} var op_lt _op_lt func (_op_lt) exec(vm *vm) { left := toPrimitiveNumber(vm.stack[vm.sp-2]) right := toPrimitiveNumber(vm.stack[vm.sp-1]) r := cmp(left, right) if r == _undefined { vm.stack[vm.sp-2] = valueFalse } else { vm.stack[vm.sp-2] = r } vm.sp-- vm.pc++ } type _op_lte struct{} var op_lte _op_lte func (_op_lte) exec(vm *vm) { left := toPrimitiveNumber(vm.stack[vm.sp-2]) right := toPrimitiveNumber(vm.stack[vm.sp-1]) r := cmp(right, left) if r == _undefined || r == valueTrue { vm.stack[vm.sp-2] = valueFalse } else { vm.stack[vm.sp-2] = valueTrue } vm.sp-- vm.pc++ } type _op_gt struct{} var op_gt _op_gt func (_op_gt) exec(vm *vm) { left := toPrimitiveNumber(vm.stack[vm.sp-2]) right := toPrimitiveNumber(vm.stack[vm.sp-1]) r := cmp(right, left) if r == _undefined { vm.stack[vm.sp-2] = valueFalse } else { vm.stack[vm.sp-2] = r } vm.sp-- vm.pc++ } type _op_gte struct{} var op_gte _op_gte func (_op_gte) exec(vm *vm) { left := toPrimitiveNumber(vm.stack[vm.sp-2]) right := toPrimitiveNumber(vm.stack[vm.sp-1]) r := cmp(left, right) if r == _undefined || r == valueTrue { vm.stack[vm.sp-2] = valueFalse } else { vm.stack[vm.sp-2] = valueTrue } vm.sp-- vm.pc++ } type _op_eq struct{} var op_eq _op_eq func (_op_eq) exec(vm *vm) { if vm.stack[vm.sp-2].Equals(vm.stack[vm.sp-1]) { vm.stack[vm.sp-2] = valueTrue } else { vm.stack[vm.sp-2] = valueFalse } vm.sp-- vm.pc++ } type _op_neq struct{} var op_neq _op_neq func (_op_neq) exec(vm *vm) { if vm.stack[vm.sp-2].Equals(vm.stack[vm.sp-1]) { vm.stack[vm.sp-2] = valueFalse } else { vm.stack[vm.sp-2] = valueTrue } vm.sp-- vm.pc++ } type _op_strict_eq struct{} var op_strict_eq _op_strict_eq func (_op_strict_eq) exec(vm *vm) { if vm.stack[vm.sp-2].StrictEquals(vm.stack[vm.sp-1]) { vm.stack[vm.sp-2] = valueTrue } else { vm.stack[vm.sp-2] = valueFalse } vm.sp-- vm.pc++ } type _op_strict_neq struct{} var op_strict_neq _op_strict_neq func (_op_strict_neq) exec(vm *vm) { if vm.stack[vm.sp-2].StrictEquals(vm.stack[vm.sp-1]) { vm.stack[vm.sp-2] = valueFalse } else { vm.stack[vm.sp-2] = valueTrue } vm.sp-- vm.pc++ } type _op_instanceof struct{} var op_instanceof _op_instanceof func (_op_instanceof) exec(vm *vm) { left := vm.stack[vm.sp-2] right := vm.r.toObject(vm.stack[vm.sp-1]) if instanceOfOperator(left, right) { vm.stack[vm.sp-2] = valueTrue } else { vm.stack[vm.sp-2] = valueFalse } vm.sp-- vm.pc++ } type _op_in struct{} var op_in _op_in func (_op_in) exec(vm *vm) { left := vm.stack[vm.sp-2] right := vm.r.toObject(vm.stack[vm.sp-1]) if right.hasProperty(left) { vm.stack[vm.sp-2] = valueTrue } else { vm.stack[vm.sp-2] = valueFalse } vm.sp-- vm.pc++ } type try struct { catchOffset int32 finallyOffset int32 } func (t try) exec(vm *vm) { var catchPos, finallyPos int32 if t.catchOffset > 0 { catchPos = int32(vm.pc) + t.catchOffset } else { catchPos = -1 } if t.finallyOffset > 0 { finallyPos = int32(vm.pc) + t.finallyOffset } else { finallyPos = -1 } vm.pushTryFrame(catchPos, finallyPos) vm.pc++ } type leaveTry struct{} func (leaveTry) exec(vm *vm) { tf := &vm.tryStack[len(vm.tryStack)-1] if tf.finallyPos >= 0 { tf.finallyRet = int32(vm.pc + 1) vm.pc = int(tf.finallyPos) tf.finallyPos = -1 tf.catchPos = -1 } else { vm.popTryFrame() vm.pc++ } } type enterFinally struct{} func (enterFinally) exec(vm *vm) { tf := &vm.tryStack[len(vm.tryStack)-1] tf.finallyPos = -1 vm.pc++ } type leaveFinally struct{} func (leaveFinally) exec(vm *vm) { tf := &vm.tryStack[len(vm.tryStack)-1] ex, ret := tf.exception, tf.finallyRet tf.exception = nil vm.popTryFrame() if ex != nil { vm.throw(ex) return } else { if ret != -1 { vm.pc = int(ret) } else { vm.pc++ } } } type _throw struct{} var throw _throw func (_throw) exec(vm *vm) { v := vm.stack[vm.sp-1] ex := &Exception{ val: v, } if o, ok := v.(*Object); ok { if e, ok := o.self.(*errorObject); ok { if len(e.stack) > 0 { ex.stack = e.stack } } } if ex.stack == nil { ex.stack = vm.captureStack(make([]StackFrame, 0, len(vm.callStack)+1), 0) } if ex = vm.handleThrow(ex); ex != nil { panic(ex) } } type _newVariadic struct{} var newVariadic _newVariadic func (_newVariadic) exec(vm *vm) { _new(vm.countVariadicArgs() - 1).exec(vm) } type _new uint32 func (n _new) exec(vm *vm) { sp := vm.sp - int(n) obj := vm.stack[sp-1] ctor := vm.r.toConstructor(obj) vm.stack[sp-1] = ctor(vm.stack[sp:vm.sp], nil) vm.sp = sp vm.pc++ } type superCall uint32 func (s superCall) exec(vm *vm) { l := len(vm.refStack) - 1 thisRef := vm.refStack[l] vm.refStack[l] = nil vm.refStack = vm.refStack[:l] obj := vm.r.toObject(vm.stack[vm.sb-1]) var cls *classFuncObject switch fn := obj.self.(type) { case *classFuncObject: cls = fn case *arrowFuncObject: cls, _ = fn.funcObj.self.(*classFuncObject) } if cls == nil { vm.throw(vm.r.NewTypeError("wrong callee type for super()")) return } sp := vm.sp - int(s) newTarget := vm.r.toObject(vm.newTarget) v := cls.createInstance(vm.stack[sp:vm.sp], newTarget) thisRef.set(v) vm.sp = sp cls._initFields(v) vm.push(v) vm.pc++ } type _superCallVariadic struct{} var superCallVariadic _superCallVariadic func (_superCallVariadic) exec(vm *vm) { superCall(vm.countVariadicArgs()).exec(vm) } type _loadNewTarget struct{} var loadNewTarget _loadNewTarget func (_loadNewTarget) exec(vm *vm) { if t := vm.newTarget; t != nil { vm.push(t) } else { vm.push(_undefined) } vm.pc++ } type _typeof struct{} var typeof _typeof func (_typeof) exec(vm *vm) { var r Value switch v := vm.stack[vm.sp-1].(type) { case valueUndefined, valueUnresolved: r = stringUndefined case valueNull: r = stringObjectC case *Object: r = v.self.typeOf() case valueBool: r = stringBoolean case String: r = stringString case valueInt, valueFloat: r = stringNumber case *valueBigInt: r = stringBigInt case *Symbol: r = stringSymbol default: panic(newTypeError("Compiler bug: unknown type: %T", v)) } vm.stack[vm.sp-1] = r vm.pc++ } type createArgsMapped uint32 func (formalArgs createArgsMapped) exec(vm *vm) { v := &Object{runtime: vm.r} args := &argumentsObject{} args.extensible = true args.prototype = vm.r.global.ObjectPrototype args.class = "Arguments" v.self = args args.val = v args.length = vm.args args.init() i := 0 c := int(formalArgs) if vm.args < c { c = vm.args } for ; i < c; i++ { args._put(unistring.String(strconv.Itoa(i)), &mappedProperty{ valueProperty: valueProperty{ writable: true, configurable: true, enumerable: true, }, v: &vm.stash.values[i], }) } for _, v := range vm.stash.extraArgs { args._put(unistring.String(strconv.Itoa(i)), v) i++ } args._putProp("callee", vm.stack[vm.sb-1], true, false, true) args._putSym(SymIterator, valueProp(vm.r.getArrayValues(), true, false, true)) vm.push(v) vm.pc++ } type createArgsUnmapped uint32 func (formalArgs createArgsUnmapped) exec(vm *vm) { args := vm.r.newBaseObject(vm.r.global.ObjectPrototype, "Arguments") i := 0 c := int(formalArgs) if vm.args < c { c = vm.args } for _, v := range vm.stash.values[:c] { args._put(unistring.String(strconv.Itoa(i)), v) i++ } for _, v := range vm.stash.extraArgs { args._put(unistring.String(strconv.Itoa(i)), v) i++ } args._putProp("length", intToValue(int64(vm.args)), true, false, true) args._put("callee", vm.r.newThrowerProperty(false)) args._putSym(SymIterator, valueProp(vm.r.getArrayValues(), true, false, true)) vm.push(args.val) vm.pc++ } type _enterWith struct{} var enterWith _enterWith func (_enterWith) exec(vm *vm) { vm.newStash() vm.stash.obj = vm.stack[vm.sp-1].ToObject(vm.r) vm.sp-- vm.pc++ } type _leaveWith struct{} var leaveWith _leaveWith func (_leaveWith) exec(vm *vm) { vm.stash = vm.stash.outer vm.pc++ } func emptyIter() (propIterItem, iterNextFunc) { return propIterItem{}, nil } type _enumerate struct{} var enumerate _enumerate func (_enumerate) exec(vm *vm) { v := vm.stack[vm.sp-1] if v == _undefined || v == _null { vm.iterStack = append(vm.iterStack, iterStackItem{f: emptyIter}) } else { vm.iterStack = append(vm.iterStack, iterStackItem{f: enumerateRecursive(v.ToObject(vm.r))}) } vm.sp-- vm.pc++ } type enumNext int32 func (jmp enumNext) exec(vm *vm) { l := len(vm.iterStack) - 1 item, n := vm.iterStack[l].f() if n != nil { vm.iterStack[l].val = item.name vm.iterStack[l].f = n vm.pc++ } else { vm.pc += int(jmp) } } type _enumGet struct{} var enumGet _enumGet func (_enumGet) exec(vm *vm) { l := len(vm.iterStack) - 1 vm.push(vm.iterStack[l].val) vm.pc++ } type _enumPop struct{} var enumPop _enumPop func (_enumPop) exec(vm *vm) { l := len(vm.iterStack) - 1 vm.iterStack[l] = iterStackItem{} vm.iterStack = vm.iterStack[:l] vm.pc++ } type _enumPopClose struct{} var enumPopClose _enumPopClose func (_enumPopClose) exec(vm *vm) { l := len(vm.iterStack) - 1 item := vm.iterStack[l] vm.iterStack[l] = iterStackItem{} vm.iterStack = vm.iterStack[:l] if iter := item.iter; iter != nil { iter.returnIter() } vm.pc++ } type _iterateP struct{} var iterateP _iterateP func (_iterateP) exec(vm *vm) { iter := vm.r.getIterator(vm.stack[vm.sp-1], nil) vm.iterStack = append(vm.iterStack, iterStackItem{iter: iter}) vm.sp-- vm.pc++ } type _iterate struct{} var iterate _iterate func (_iterate) exec(vm *vm) { iter := vm.r.getIterator(vm.stack[vm.sp-1], nil) vm.iterStack = append(vm.iterStack, iterStackItem{iter: iter}) vm.pc++ } type iterNext int32 func (jmp iterNext) exec(vm *vm) { l := len(vm.iterStack) - 1 iter := vm.iterStack[l].iter value, ex := iter.step() if ex == nil { if value == nil { vm.pc += int(jmp) } else { vm.iterStack[l].val = value vm.pc++ } } else { l := len(vm.iterStack) - 1 vm.iterStack[l] = iterStackItem{} vm.iterStack = vm.iterStack[:l] vm.throw(ex.val) return } } type iterGetNextOrUndef struct{} func (iterGetNextOrUndef) exec(vm *vm) { l := len(vm.iterStack) - 1 iter := vm.iterStack[l].iter var value Value if iter.iterator != nil { var ex *Exception value, ex = iter.step() if ex != nil { l := len(vm.iterStack) - 1 vm.iterStack[l] = iterStackItem{} vm.iterStack = vm.iterStack[:l] vm.throw(ex.val) return } } vm.push(nilSafe(value)) vm.pc++ } type copyStash struct{} func (copyStash) exec(vm *vm) { oldStash := vm.stash newStash := &stash{ outer: oldStash.outer, } vm.stashAllocs++ newStash.values = append([]Value(nil), oldStash.values...) newStash.names = oldStash.names vm.stash = newStash vm.pc++ } type _throwAssignToConst struct{} var throwAssignToConst _throwAssignToConst func (_throwAssignToConst) exec(vm *vm) { vm.throw(errAssignToConst) } func (r *Runtime) copyDataProperties(target, source Value) { targetObj := r.toObject(target) if source == _null || source == _undefined { return } sourceObj := source.ToObject(r) for item, next := iterateEnumerableProperties(sourceObj)(); next != nil; item, next = next() { createDataPropertyOrThrow(targetObj, item.name, item.value) } } type _copySpread struct{} var copySpread _copySpread func (_copySpread) exec(vm *vm) { vm.r.copyDataProperties(vm.stack[vm.sp-2], vm.stack[vm.sp-1]) vm.sp-- vm.pc++ } type _copyRest struct{} var copyRest _copyRest func (_copyRest) exec(vm *vm) { vm.push(vm.r.NewObject()) vm.r.copyDataProperties(vm.stack[vm.sp-1], vm.stack[vm.sp-2]) vm.pc++ } type _createDestructSrc struct{} var createDestructSrc _createDestructSrc func (_createDestructSrc) exec(vm *vm) { v := vm.stack[vm.sp-1] vm.r.checkObjectCoercible(v) vm.push(vm.r.newDestructKeyedSource(v)) vm.pc++ } type _checkObjectCoercible struct{} var checkObjectCoercible _checkObjectCoercible func (_checkObjectCoercible) exec(vm *vm) { vm.r.checkObjectCoercible(vm.stack[vm.sp-1]) vm.pc++ } type createArgsRestStack int func (n createArgsRestStack) exec(vm *vm) { var values []Value delta := vm.args - int(n) if delta > 0 { values = make([]Value, delta) copy(values, vm.stack[vm.sb+int(n)+1:]) } vm.push(vm.r.newArrayValues(values)) vm.pc++ } type _createArgsRestStash struct{} var createArgsRestStash _createArgsRestStash func (_createArgsRestStash) exec(vm *vm) { vm.push(vm.r.newArrayValues(vm.stash.extraArgs)) vm.stash.extraArgs = nil vm.pc++ } type concatStrings int func (n concatStrings) exec(vm *vm) { strs := vm.stack[vm.sp-int(n) : vm.sp] length := 0 allAscii := true for i, s := range strs { switch s := s.(type) { case asciiString: length += s.Length() case unicodeString: length += s.Length() allAscii = false case *importedString: s.ensureScanned() if s.u != nil { strs[i] = s.u length += s.u.Length() allAscii = false } else { strs[i] = asciiString(s.s) length += len(s.s) } default: panic(unknownStringTypeErr(s)) } } vm.sp -= int(n) - 1 if allAscii { var buf strings.Builder buf.Grow(length) for _, s := range strs { buf.WriteString(string(s.(asciiString))) } vm.stack[vm.sp-1] = asciiString(buf.String()) } else { var buf unicodeStringBuilder buf.ensureStarted(length) for _, s := range strs { buf.writeString(s.(String)) } vm.stack[vm.sp-1] = buf.String() } vm.pc++ } type getTaggedTmplObject struct { raw, cooked []Value } // As tagged template objects are not cached (because it's hard to ensure the cache is cleaned without using // finalizers) this wrapper is needed to override the equality method so that two objects for the same template // literal appeared to be equal from the code's point of view. type taggedTemplateArray struct { *arrayObject idPtr *[]Value } func (a *taggedTemplateArray) equal(other objectImpl) bool { if o, ok := other.(*taggedTemplateArray); ok { return a.idPtr == o.idPtr } return false } func (c *getTaggedTmplObject) exec(vm *vm) { cooked := vm.r.newArrayObject() setArrayValues(cooked, c.cooked) raw := vm.r.newArrayObject() setArrayValues(raw, c.raw) cooked.propValueCount = len(c.cooked) cooked.lengthProp.writable = false raw.propValueCount = len(c.raw) raw.lengthProp.writable = false raw.preventExtensions(true) raw.val.self = &taggedTemplateArray{ arrayObject: raw, idPtr: &c.raw, } cooked._putProp("raw", raw.val, false, false, false) cooked.preventExtensions(true) cooked.val.self = &taggedTemplateArray{ arrayObject: cooked, idPtr: &c.cooked, } vm.push(cooked.val) vm.pc++ } type _loadSuper struct{} var loadSuper _loadSuper func (_loadSuper) exec(vm *vm) { homeObject := getHomeObject(vm.stack[vm.sb-1]) if proto := homeObject.Prototype(); proto != nil { vm.push(proto) } else { vm.push(_undefined) } vm.pc++ } type newClass struct { ctor *Program name unistring.String source string initFields *Program privateFields, privateMethods []unistring.String // only set when dynamic resolution is needed numPrivateFields, numPrivateMethods uint32 length int hasPrivateEnv bool } type newDerivedClass struct { newClass } func (vm *vm) createPrivateType(f *classFuncObject, numFields, numMethods uint32) { typ := &privateEnvType{} typ.numFields = numFields typ.numMethods = numMethods f.privateEnvType = typ f.privateMethods = make([]Value, numMethods) } func (vm *vm) fillPrivateNamesMap(typ *privateEnvType, privateFields, privateMethods []unistring.String) { if len(privateFields) > 0 || len(privateMethods) > 0 { penv := vm.privEnv.names if penv == nil { penv = make(privateNames) vm.privEnv.names = penv } for idx, field := range privateFields { penv[field] = &privateId{ typ: typ, idx: uint32(idx), } } for idx, method := range privateMethods { penv[method] = &privateId{ typ: typ, idx: uint32(idx), isMethod: true, } } } } func (c *newClass) create(protoParent, ctorParent *Object, vm *vm, derived bool) (prototype, cls *Object) { proto := vm.r.newBaseObject(protoParent, classObject) f := vm.r.newClassFunc(c.name, c.length, ctorParent, derived) f._putProp("prototype", proto.val, false, false, false) proto._putProp("constructor", f.val, true, false, true) f.prg = c.ctor f.stash = vm.stash f.src = c.source f.initFields = c.initFields if c.hasPrivateEnv { vm.privEnv = &privateEnv{ outer: vm.privEnv, } vm.createPrivateType(f, c.numPrivateFields, c.numPrivateMethods) vm.fillPrivateNamesMap(f.privateEnvType, c.privateFields, c.privateMethods) vm.privEnv.instanceType = f.privateEnvType } f.privEnv = vm.privEnv return proto.val, f.val } func (c *newClass) exec(vm *vm) { proto, cls := c.create(vm.r.global.ObjectPrototype, vm.r.getFunctionPrototype(), vm, false) sp := vm.sp vm.stack.expand(sp + 1) vm.stack[sp] = proto vm.stack[sp+1] = cls vm.sp = sp + 2 vm.pc++ } func (c *newDerivedClass) exec(vm *vm) { var protoParent *Object var superClass *Object if o := vm.stack[vm.sp-1]; o != _null { if sc, ok := o.(*Object); !ok || sc.self.assertConstructor() == nil { vm.throw(vm.r.NewTypeError("Class extends value is not a constructor or null")) return } else { v := sc.self.getStr("prototype", nil) if v != _null { if o, ok := v.(*Object); ok { protoParent = o } else { vm.throw(vm.r.NewTypeError("Class extends value does not have valid prototype property")) return } } superClass = sc } } else { superClass = vm.r.getFunctionPrototype() } proto, cls := c.create(protoParent, superClass, vm, true) vm.stack[vm.sp-1] = proto vm.push(cls) vm.pc++ } // Creates a special instance of *classFuncObject which is only used during evaluation of a class declaration // to initialise static fields and instance private methods of another class. type newStaticFieldInit struct { initFields *Program numPrivateFields, numPrivateMethods uint32 } func (c *newStaticFieldInit) exec(vm *vm) { f := vm.r.newClassFunc("", 0, vm.r.getFunctionPrototype(), false) if c.numPrivateFields > 0 || c.numPrivateMethods > 0 { vm.createPrivateType(f, c.numPrivateFields, c.numPrivateMethods) } f.initFields = c.initFields f.stash = vm.stash vm.push(f.val) vm.pc++ } func (vm *vm) loadThis(v Value) { if v != nil { vm.push(v) } else { vm.throw(vm.r.newError(vm.r.getReferenceError(), "Must call super constructor in derived class before accessing 'this'")) return } vm.pc++ } type loadThisStash uint32 func (l loadThisStash) exec(vm *vm) { vm.loadThis(*vm.getStashPtr(uint32(l))) } type loadThisStack struct{} func (loadThisStack) exec(vm *vm) { vm.loadThis(vm.stack[vm.sb]) } func (vm *vm) getStashPtr(s uint32) *Value { level := int(s) >> 24 idx := s & 0x00FFFFFF stash := vm.stash for i := 0; i < level; i++ { stash = stash.outer } return &stash.values[idx] } type getThisDynamic struct{} func (getThisDynamic) exec(vm *vm) { for stash := vm.stash; stash != nil; stash = stash.outer { if stash.obj == nil { if v, exists := stash.getByName(thisBindingName); exists { vm.push(v) vm.pc++ return } } } vm.push(vm.r.globalObject) vm.pc++ } type throwConst struct { v interface{} } func (t throwConst) exec(vm *vm) { vm.throw(t.v) } type resolveThisStack struct{} func (r resolveThisStack) exec(vm *vm) { vm.refStack = append(vm.refStack, &thisRef{v: (*[]Value)(&vm.stack), idx: vm.sb}) vm.pc++ } type resolveThisStash uint32 func (r resolveThisStash) exec(vm *vm) { level := int(r) >> 24 idx := r & 0x00FFFFFF stash := vm.stash for i := 0; i < level; i++ { stash = stash.outer } vm.refStack = append(vm.refStack, &thisRef{v: &stash.values, idx: int(idx)}) vm.pc++ } type resolveThisDynamic struct{} func (resolveThisDynamic) exec(vm *vm) { for stash := vm.stash; stash != nil; stash = stash.outer { if stash.obj == nil { if idx, exists := stash.names[thisBindingName]; exists { vm.refStack = append(vm.refStack, &thisRef{v: &stash.values, idx: int(idx &^ maskTyp)}) vm.pc++ return } } } panic(vm.r.newError(vm.r.getReferenceError(), "Compiler bug: 'this' reference is not found in resolveThisDynamic")) } type defineComputedKey int func (offset defineComputedKey) exec(vm *vm) { obj := vm.r.toObject(vm.stack[vm.sp-int(offset)]) if h, ok := obj.self.(*classFuncObject); ok { key := toPropertyKey(vm.stack[vm.sp-1]) h.computedKeys = append(h.computedKeys, key) vm.sp-- vm.pc++ return } panic(vm.r.NewTypeError("Compiler bug: unexpected target for defineComputedKey: %v", obj)) } type loadComputedKey int func (idx loadComputedKey) exec(vm *vm) { obj := vm.r.toObject(vm.stack[vm.sb-1]) if h, ok := obj.self.(*classFuncObject); ok { vm.push(h.computedKeys[idx]) vm.pc++ return } panic(vm.r.NewTypeError("Compiler bug: unexpected target for loadComputedKey: %v", obj)) } type initStaticElements struct { privateFields, privateMethods []unistring.String } func (i *initStaticElements) exec(vm *vm) { cls := vm.stack[vm.sp-1] staticInit := vm.r.toObject(vm.stack[vm.sp-3]) vm.sp -= 2 if h, ok := staticInit.self.(*classFuncObject); ok { h._putProp("prototype", cls, true, true, true) // so that 'super' resolution work h.privEnv = vm.privEnv if h.privateEnvType != nil { vm.privEnv.staticType = h.privateEnvType vm.fillPrivateNamesMap(h.privateEnvType, i.privateFields, i.privateMethods) } h._initFields(vm.r.toObject(cls)) vm.stack[vm.sp-1] = cls vm.pc++ return } panic(vm.r.NewTypeError("Compiler bug: unexpected target for initStaticElements: %v", staticInit)) } type definePrivateMethod struct { idx int targetOffset int } func (d *definePrivateMethod) getPrivateMethods(vm *vm) []Value { obj := vm.r.toObject(vm.stack[vm.sp-d.targetOffset]) if cls, ok := obj.self.(*classFuncObject); ok { return cls.privateMethods } else { panic(vm.r.NewTypeError("Compiler bug: wrong target type for definePrivateMethod: %T", obj.self)) } } func (d *definePrivateMethod) exec(vm *vm) { methods := d.getPrivateMethods(vm) methods[d.idx] = vm.stack[vm.sp-1] vm.sp-- vm.pc++ } type definePrivateGetter struct { definePrivateMethod } func (d *definePrivateGetter) exec(vm *vm) { methods := d.getPrivateMethods(vm) val := vm.stack[vm.sp-1] method := vm.r.toObject(val) p, _ := methods[d.idx].(*valueProperty) if p == nil { p = &valueProperty{ accessor: true, } methods[d.idx] = p } if p.getterFunc != nil { vm.throw(vm.r.NewTypeError("Private getter has already been declared")) return } p.getterFunc = method vm.sp-- vm.pc++ } type definePrivateSetter struct { definePrivateMethod } func (d *definePrivateSetter) exec(vm *vm) { methods := d.getPrivateMethods(vm) val := vm.stack[vm.sp-1] method := vm.r.toObject(val) p, _ := methods[d.idx].(*valueProperty) if p == nil { p = &valueProperty{ accessor: true, } methods[d.idx] = p } if p.setterFunc != nil { vm.throw(vm.r.NewTypeError("Private setter has already been declared")) return } p.setterFunc = method vm.sp-- vm.pc++ } type definePrivateProp struct { idx int } func (d *definePrivateProp) exec(vm *vm) { f := vm.r.toObject(vm.stack[vm.sb-1]).self.(*classFuncObject) obj := vm.r.toObject(vm.stack[vm.sp-2]) penv := obj.self.getPrivateEnv(f.privateEnvType, false) penv.fields[d.idx] = vm.stack[vm.sp-1] vm.sp-- vm.pc++ } type getPrivatePropRes resolvedPrivateName func (vm *vm) getPrivateType(level uint8, isStatic bool) *privateEnvType { e := vm.privEnv for i := uint8(0); i < level; i++ { e = e.outer } if isStatic { return e.staticType } return e.instanceType } func (g *getPrivatePropRes) _get(base Value, vm *vm) Value { return vm.getPrivateProp(base, g.name, vm.getPrivateType(g.level, g.isStatic), g.idx, g.isMethod) } func (g *getPrivatePropRes) exec(vm *vm) { vm.stack[vm.sp-1] = g._get(vm.stack[vm.sp-1], vm) vm.pc++ } type getPrivatePropId privateId func (g *getPrivatePropId) exec(vm *vm) { vm.stack[vm.sp-1] = vm.getPrivateProp(vm.stack[vm.sp-1], g.name, g.typ, g.idx, g.isMethod) vm.pc++ } type getPrivatePropIdCallee privateId func (g *getPrivatePropIdCallee) exec(vm *vm) { prop := vm.getPrivateProp(vm.stack[vm.sp-1], g.name, g.typ, g.idx, g.isMethod) if prop == nil { prop = memberUnresolved{valueUnresolved{r: vm.r, ref: (*privateId)(g).string()}} } vm.push(prop) vm.pc++ } func (vm *vm) getPrivateProp(base Value, name unistring.String, typ *privateEnvType, idx uint32, isMethod bool) Value { obj := vm.r.toObject(base) penv := obj.self.getPrivateEnv(typ, false) var v Value if penv != nil { if isMethod { v = penv.methods[idx] } else { v = penv.fields[idx] if v == nil { panic(vm.r.NewTypeError("Private member #%s is accessed before it is initialized", name)) } } } else { panic(vm.r.NewTypeError("Cannot read private member #%s from an object whose class did not declare it", name)) } if prop, ok := v.(*valueProperty); ok { if prop.getterFunc == nil { panic(vm.r.NewTypeError("'#%s' was defined without a getter", name)) } v = prop.get(obj) } return v } type getPrivatePropResCallee getPrivatePropRes func (g *getPrivatePropResCallee) exec(vm *vm) { prop := (*getPrivatePropRes)(g)._get(vm.stack[vm.sp-1], vm) if prop == nil { prop = memberUnresolved{valueUnresolved{r: vm.r, ref: (*resolvedPrivateName)(g).string()}} } vm.push(prop) vm.pc++ } func (vm *vm) setPrivateProp(base Value, name unistring.String, typ *privateEnvType, idx uint32, isMethod bool, val Value) { obj := vm.r.toObject(base) penv := obj.self.getPrivateEnv(typ, false) if penv != nil { if isMethod { v := penv.methods[idx] if prop, ok := v.(*valueProperty); ok { if prop.setterFunc != nil { prop.set(base, val) } else { panic(vm.r.NewTypeError("Cannot assign to read only property '#%s'", name)) } } else { panic(vm.r.NewTypeError("Private method '#%s' is not writable", name)) } } else { ptr := &penv.fields[idx] if *ptr == nil { panic(vm.r.NewTypeError("Private member #%s is accessed before it is initialized", name)) } *ptr = val } } else { panic(vm.r.NewTypeError("Cannot write private member #%s from an object whose class did not declare it", name)) } } func (vm *vm) exceptionFromValue(x interface{}) *Exception { var ex *Exception switch x1 := x.(type) { case *Object: ex = &Exception{ val: x1, } if er, ok := x1.self.(*errorObject); ok { ex.stack = er.stack } case Value: ex = &Exception{ val: x1, } case *Exception: ex = x1 case typeError: ex = &Exception{ val: vm.r.NewTypeError(string(x1)), } case referenceError: ex = &Exception{ val: vm.r.newError(vm.r.getReferenceError(), string(x1)), } case rangeError: ex = &Exception{ val: vm.r.newError(vm.r.getRangeError(), string(x1)), } case syntaxError: ex = &Exception{ val: vm.r.newError(vm.r.getSyntaxError(), string(x1)), } default: /* if vm.prg != nil { vm.prg.dumpCode(log.Printf) } log.Print("Stack: ", string(debug.Stack())) panic(fmt.Errorf("Panic at %d: %v", vm.pc, x)) */ return nil } if ex.stack == nil { ex.stack = vm.captureStack(make([]StackFrame, 0, len(vm.callStack)+1), 0) } return ex } type setPrivatePropRes resolvedPrivateName func (p *setPrivatePropRes) _set(base Value, val Value, vm *vm) { vm.setPrivateProp(base, p.name, vm.getPrivateType(p.level, p.isStatic), p.idx, p.isMethod, val) } func (p *setPrivatePropRes) exec(vm *vm) { v := vm.stack[vm.sp-1] p._set(vm.stack[vm.sp-2], v, vm) vm.stack[vm.sp-2] = v vm.sp-- vm.pc++ } type setPrivatePropResP setPrivatePropRes func (p *setPrivatePropResP) exec(vm *vm) { v := vm.stack[vm.sp-1] (*setPrivatePropRes)(p)._set(vm.stack[vm.sp-2], v, vm) vm.sp -= 2 vm.pc++ } type setPrivatePropId privateId func (p *setPrivatePropId) exec(vm *vm) { v := vm.stack[vm.sp-1] vm.setPrivateProp(vm.stack[vm.sp-2], p.name, p.typ, p.idx, p.isMethod, v) vm.stack[vm.sp-2] = v vm.sp-- vm.pc++ } type setPrivatePropIdP privateId func (p *setPrivatePropIdP) exec(vm *vm) { v := vm.stack[vm.sp-1] vm.setPrivateProp(vm.stack[vm.sp-2], p.name, p.typ, p.idx, p.isMethod, v) vm.sp -= 2 vm.pc++ } type popPrivateEnv struct{} func (popPrivateEnv) exec(vm *vm) { vm.privEnv = vm.privEnv.outer vm.pc++ } type privateInRes resolvedPrivateName func (i *privateInRes) exec(vm *vm) { obj := vm.r.toObject(vm.stack[vm.sp-1]) pe := obj.self.getPrivateEnv(vm.getPrivateType(i.level, i.isStatic), false) if pe != nil && (i.isMethod && pe.methods[i.idx] != nil || !i.isMethod && pe.fields[i.idx] != nil) { vm.stack[vm.sp-1] = valueTrue } else { vm.stack[vm.sp-1] = valueFalse } vm.pc++ } type privateInId privateId func (i *privateInId) exec(vm *vm) { obj := vm.r.toObject(vm.stack[vm.sp-1]) pe := obj.self.getPrivateEnv(i.typ, false) if pe != nil && (i.isMethod && pe.methods[i.idx] != nil || !i.isMethod && pe.fields[i.idx] != nil) { vm.stack[vm.sp-1] = valueTrue } else { vm.stack[vm.sp-1] = valueFalse } vm.pc++ } type getPrivateRefRes resolvedPrivateName func (r *getPrivateRefRes) exec(vm *vm) { vm.refStack = append(vm.refStack, &privateRefRes{ base: vm.stack[vm.sp-1].ToObject(vm.r), name: (*resolvedPrivateName)(r), }) vm.sp-- vm.pc++ } type getPrivateRefId privateId func (r *getPrivateRefId) exec(vm *vm) { vm.refStack = append(vm.refStack, &privateRefId{ base: vm.stack[vm.sp-1].ToObject(vm.r), id: (*privateId)(r), }) vm.sp-- vm.pc++ } func (y *yieldMarker) exec(vm *vm) { vm.pc = -vm.pc // this will terminate the run loop vm.push(y) // marker so the caller knows it's a yield, not a return } func (y *yieldMarker) String() string { if y == yieldEmpty { return "empty" } switch y.resultType { case resultYield: return "yield" case resultYieldRes: return "yieldRes" case resultYieldDelegate: return "yield*" case resultYieldDelegateRes: return "yield*Res" case resultAwait: return "await" default: return "unknown" } }