package goja import ( "fmt" "github.com/dop251/goja/token" "sort" "github.com/dop251/goja/ast" "github.com/dop251/goja/file" "github.com/dop251/goja/unistring" ) type blockType int const ( blockLoop blockType = iota blockLoopEnum blockTry blockLabel blockSwitch blockWith blockScope blockIterScope blockOptChain ) const ( maskConst = 1 << 31 maskVar = 1 << 30 maskDeletable = 1 << 29 maskStrict = maskDeletable maskTyp = maskConst | maskVar | maskDeletable ) type varType byte const ( varTypeVar varType = iota varTypeLet varTypeStrictConst varTypeConst ) const thisBindingName = " this" // must not be a valid identifier type CompilerError struct { Message string File *file.File Offset int } type CompilerSyntaxError struct { CompilerError } type CompilerReferenceError struct { CompilerError } type srcMapItem struct { pc int srcPos int } // Program is an internal, compiled representation of code which is produced by the Compile function. // This representation is not linked to a runtime in any way and can be used concurrently. // It is always preferable to use a Program over a string when running code as it skips the compilation step. type Program struct { code []instruction funcName unistring.String src *file.File srcMap []srcMapItem } type compiler struct { p *Program scope *scope block *block classScope *classScope enumGetExpr compiledEnumGetExpr evalVM *vm // VM used to evaluate constant expressions ctxVM *vm // VM in which an eval() code is compiled codeScratchpad []instruction stringCache map[unistring.String]Value } type binding struct { scope *scope name unistring.String accessPoints map[*scope]*[]int isConst bool isStrict bool isArg bool isVar bool inStash bool } func (b *binding) getAccessPointsForScope(s *scope) *[]int { m := b.accessPoints[s] if m == nil { a := make([]int, 0, 1) m = &a if b.accessPoints == nil { b.accessPoints = make(map[*scope]*[]int) } b.accessPoints[s] = m } return m } func (b *binding) markAccessPointAt(pos int) { scope := b.scope.c.scope m := b.getAccessPointsForScope(scope) *m = append(*m, pos-scope.base) } func (b *binding) markAccessPointAtScope(scope *scope, pos int) { m := b.getAccessPointsForScope(scope) *m = append(*m, pos-scope.base) } func (b *binding) markAccessPoint() { scope := b.scope.c.scope m := b.getAccessPointsForScope(scope) *m = append(*m, len(scope.prg.code)-scope.base) } func (b *binding) emitGet() { b.markAccessPoint() if b.isVar && !b.isArg { b.scope.c.emit(loadStack(0)) } else { b.scope.c.emit(loadStackLex(0)) } } func (b *binding) emitGetAt(pos int) { b.markAccessPointAt(pos) if b.isVar && !b.isArg { b.scope.c.p.code[pos] = loadStack(0) } else { b.scope.c.p.code[pos] = loadStackLex(0) } } func (b *binding) emitGetP() { if b.isVar && !b.isArg { // no-op } else { // make sure TDZ is checked b.markAccessPoint() b.scope.c.emit(loadStackLex(0), pop) } } func (b *binding) emitSet() { if b.isConst { if b.isStrict || b.scope.c.scope.strict { b.scope.c.emit(throwAssignToConst) } return } b.markAccessPoint() if b.isVar && !b.isArg { b.scope.c.emit(storeStack(0)) } else { b.scope.c.emit(storeStackLex(0)) } } func (b *binding) emitSetP() { if b.isConst { if b.isStrict || b.scope.c.scope.strict { b.scope.c.emit(throwAssignToConst) } return } b.markAccessPoint() if b.isVar && !b.isArg { b.scope.c.emit(storeStackP(0)) } else { b.scope.c.emit(storeStackLexP(0)) } } func (b *binding) emitInitP() { if !b.isVar && b.scope.outer == nil { b.scope.c.emit(initGlobalP(b.name)) } else { b.markAccessPoint() b.scope.c.emit(initStackP(0)) } } func (b *binding) emitInit() { if !b.isVar && b.scope.outer == nil { b.scope.c.emit(initGlobal(b.name)) } else { b.markAccessPoint() b.scope.c.emit(initStack(0)) } } func (b *binding) emitInitAt(pos int) { if !b.isVar && b.scope.outer == nil { b.scope.c.p.code[pos] = initGlobal(b.name) } else { b.markAccessPointAt(pos) b.scope.c.p.code[pos] = initStack(0) } } func (b *binding) emitInitAtScope(scope *scope, pos int) { if !b.isVar && scope.outer == nil { scope.c.p.code[pos] = initGlobal(b.name) } else { b.markAccessPointAtScope(scope, pos) scope.c.p.code[pos] = initStack(0) } } func (b *binding) emitInitPAtScope(scope *scope, pos int) { if !b.isVar && scope.outer == nil { scope.c.p.code[pos] = initGlobalP(b.name) } else { b.markAccessPointAtScope(scope, pos) scope.c.p.code[pos] = initStackP(0) } } func (b *binding) emitGetVar(callee bool) { b.markAccessPoint() if b.isVar && !b.isArg { b.scope.c.emit(&loadMixed{name: b.name, callee: callee}) } else { b.scope.c.emit(&loadMixedLex{name: b.name, callee: callee}) } } func (b *binding) emitResolveVar(strict bool) { b.markAccessPoint() if b.isVar && !b.isArg { b.scope.c.emit(&resolveMixed{name: b.name, strict: strict, typ: varTypeVar}) } else { var typ varType if b.isConst { if b.isStrict { typ = varTypeStrictConst } else { typ = varTypeConst } } else { typ = varTypeLet } b.scope.c.emit(&resolveMixed{name: b.name, strict: strict, typ: typ}) } } func (b *binding) moveToStash() { if b.isArg && !b.scope.argsInStash { b.scope.moveArgsToStash() } else { b.inStash = true b.scope.needStash = true } } func (b *binding) useCount() (count int) { for _, a := range b.accessPoints { count += len(*a) } return } type scope struct { c *compiler prg *Program outer *scope nested []*scope boundNames map[unistring.String]*binding bindings []*binding base int numArgs int // function type. If not funcNone, this is a function or a top-level lexical environment funcType funcType // in strict mode strict bool // eval top-level scope eval bool // at least one inner scope has direct eval() which can lookup names dynamically (by name) dynLookup bool // at least one binding has been marked for placement in stash needStash bool // is a variable environment, i.e. the target for dynamically created var bindings variable bool // a function scope that has at least one direct eval() and non-strict, so the variables can be added dynamically dynamic bool // arguments have been marked for placement in stash (functions only) argsInStash bool // need 'arguments' object (functions only) argsNeeded bool } type block struct { typ blockType label unistring.String cont int breaks []int conts []int outer *block breaking *block // set when the 'finally' block is an empty break statement sequence needResult bool } func (c *compiler) leaveScopeBlock(enter *enterBlock) { c.updateEnterBlock(enter) leave := &leaveBlock{ stackSize: enter.stackSize, popStash: enter.stashSize > 0, } c.emit(leave) for _, pc := range c.block.breaks { c.p.code[pc] = leave } c.block.breaks = nil c.leaveBlock() } func (c *compiler) leaveBlock() { lbl := len(c.p.code) for _, item := range c.block.breaks { c.p.code[item] = jump(lbl - item) } if t := c.block.typ; t == blockLoop || t == blockLoopEnum { for _, item := range c.block.conts { c.p.code[item] = jump(c.block.cont - item) } } c.block = c.block.outer } func (e *CompilerSyntaxError) Error() string { if e.File != nil { return fmt.Sprintf("SyntaxError: %s at %s", e.Message, e.File.Position(e.Offset)) } return fmt.Sprintf("SyntaxError: %s", e.Message) } func (e *CompilerReferenceError) Error() string { return fmt.Sprintf("ReferenceError: %s", e.Message) } func (c *compiler) newScope() { strict := false if c.scope != nil { strict = c.scope.strict } c.scope = &scope{ c: c, prg: c.p, outer: c.scope, strict: strict, } } func (c *compiler) newBlockScope() { c.newScope() if outer := c.scope.outer; outer != nil { outer.nested = append(outer.nested, c.scope) } c.scope.base = len(c.p.code) } func (c *compiler) popScope() { c.scope = c.scope.outer } func (c *compiler) emitLiteralString(s String) { key := s.string() if c.stringCache == nil { c.stringCache = make(map[unistring.String]Value) } internVal := c.stringCache[key] if internVal == nil { c.stringCache[key] = s internVal = s } c.emit(loadVal{internVal}) } func (c *compiler) emitLiteralValue(v Value) { if s, ok := v.(String); ok { c.emitLiteralString(s) return } c.emit(loadVal{v}) } func newCompiler() *compiler { c := &compiler{ p: &Program{}, } c.enumGetExpr.init(c, file.Idx(0)) return c } func (p *Program) dumpCode(logger func(format string, args ...interface{})) { p._dumpCode("", logger) } func (p *Program) _dumpCode(indent string, logger func(format string, args ...interface{})) { dumpInitFields := func(initFields *Program) { i := indent + ">" logger("%s ---- init_fields:", i) initFields._dumpCode(i, logger) logger("%s ----", i) } for pc, ins := range p.code { logger("%s %d: %T(%v)", indent, pc, ins, ins) var prg *Program switch f := ins.(type) { case newFuncInstruction: prg = f.getPrg() case *newDerivedClass: if f.initFields != nil { dumpInitFields(f.initFields) } prg = f.ctor case *newClass: if f.initFields != nil { dumpInitFields(f.initFields) } prg = f.ctor case *newStaticFieldInit: if f.initFields != nil { dumpInitFields(f.initFields) } } if prg != nil { prg._dumpCode(indent+">", logger) } } } func (p *Program) sourceOffset(pc int) int { i := sort.Search(len(p.srcMap), func(idx int) bool { return p.srcMap[idx].pc > pc }) - 1 if i >= 0 { return p.srcMap[i].srcPos } return 0 } func (p *Program) addSrcMap(srcPos int) { if len(p.srcMap) > 0 && p.srcMap[len(p.srcMap)-1].srcPos == srcPos { return } p.srcMap = append(p.srcMap, srcMapItem{pc: len(p.code), srcPos: srcPos}) } func (s *scope) lookupName(name unistring.String) (binding *binding, noDynamics bool) { noDynamics = true toStash := false for curScope := s; ; curScope = curScope.outer { if curScope.outer != nil { if b, exists := curScope.boundNames[name]; exists { if toStash && !b.inStash { b.moveToStash() } binding = b return } } else { noDynamics = false return } if curScope.dynamic { noDynamics = false } if name == "arguments" && curScope.funcType != funcNone && curScope.funcType != funcArrow { if curScope.funcType == funcClsInit { s.c.throwSyntaxError(0, "'arguments' is not allowed in class field initializer or static initialization block") } curScope.argsNeeded = true binding, _ = curScope.bindName(name) return } if curScope.isFunction() { toStash = true } } } func (s *scope) lookupThis() (*binding, bool) { toStash := false for curScope := s; curScope != nil; curScope = curScope.outer { if curScope.outer == nil { if curScope.eval { return nil, true } } if b, exists := curScope.boundNames[thisBindingName]; exists { if toStash && !b.inStash { b.moveToStash() } return b, false } if curScope.isFunction() { toStash = true } } return nil, false } func (s *scope) ensureBoundNamesCreated() { if s.boundNames == nil { s.boundNames = make(map[unistring.String]*binding) } } func (s *scope) addBinding(offset int) *binding { if len(s.bindings) >= (1<<24)-1 { s.c.throwSyntaxError(offset, "Too many variables") } b := &binding{ scope: s, } s.bindings = append(s.bindings, b) return b } func (s *scope) bindNameLexical(name unistring.String, unique bool, offset int) (*binding, bool) { if b := s.boundNames[name]; b != nil { if unique { s.c.throwSyntaxError(offset, "Identifier '%s' has already been declared", name) } return b, false } b := s.addBinding(offset) b.name = name s.ensureBoundNamesCreated() s.boundNames[name] = b return b, true } func (s *scope) createThisBinding() *binding { thisBinding, _ := s.bindNameLexical(thisBindingName, false, 0) thisBinding.isVar = true // don't check on load return thisBinding } func (s *scope) bindName(name unistring.String) (*binding, bool) { if !s.isFunction() && !s.variable && s.outer != nil { return s.outer.bindName(name) } b, created := s.bindNameLexical(name, false, 0) if created { b.isVar = true } return b, created } func (s *scope) bindNameShadow(name unistring.String) (*binding, bool) { if !s.isFunction() && s.outer != nil { return s.outer.bindNameShadow(name) } _, exists := s.boundNames[name] b := &binding{ scope: s, name: name, } s.bindings = append(s.bindings, b) s.ensureBoundNamesCreated() s.boundNames[name] = b return b, !exists } func (s *scope) nearestFunction() *scope { for sc := s; sc != nil; sc = sc.outer { if sc.isFunction() { return sc } } return nil } func (s *scope) nearestThis() *scope { for sc := s; sc != nil; sc = sc.outer { if sc.eval || sc.isFunction() && sc.funcType != funcArrow { return sc } } return nil } func (s *scope) finaliseVarAlloc(stackOffset int) (stashSize, stackSize int) { argsInStash := false if f := s.nearestFunction(); f != nil { argsInStash = f.argsInStash } stackIdx, stashIdx := 0, 0 allInStash := s.isDynamic() var derivedCtor bool if fs := s.nearestThis(); fs != nil && fs.funcType == funcDerivedCtor { derivedCtor = true } for i, b := range s.bindings { var this bool if b.name == thisBindingName { this = true } if allInStash || b.inStash { for scope, aps := range b.accessPoints { var level uint32 for sc := scope; sc != nil && sc != s; sc = sc.outer { if sc.needStash || sc.isDynamic() { level++ } } if level > 255 { s.c.throwSyntaxError(0, "Maximum nesting level (256) exceeded") } idx := (level << 24) | uint32(stashIdx) base := scope.base code := scope.prg.code if this { if derivedCtor { for _, pc := range *aps { ap := &code[base+pc] switch (*ap).(type) { case loadStack: *ap = loadThisStash(idx) case initStack: *ap = initStash(idx) case resolveThisStack: *ap = resolveThisStash(idx) case _ret: *ap = cret(idx) default: s.c.assert(false, s.c.p.sourceOffset(pc), "Unsupported instruction for 'this'") } } } else { for _, pc := range *aps { ap := &code[base+pc] switch (*ap).(type) { case loadStack: *ap = loadStash(idx) case initStack: *ap = initStash(idx) default: s.c.assert(false, s.c.p.sourceOffset(pc), "Unsupported instruction for 'this'") } } } } else { for _, pc := range *aps { ap := &code[base+pc] switch i := (*ap).(type) { case loadStack: *ap = loadStash(idx) case storeStack: *ap = storeStash(idx) case storeStackP: *ap = storeStashP(idx) case loadStackLex: *ap = loadStashLex(idx) case storeStackLex: *ap = storeStashLex(idx) case storeStackLexP: *ap = storeStashLexP(idx) case initStackP: *ap = initStashP(idx) case initStack: *ap = initStash(idx) case *loadMixed: i.idx = idx case *loadMixedLex: i.idx = idx case *resolveMixed: i.idx = idx default: s.c.assert(false, s.c.p.sourceOffset(pc), "Unsupported instruction for binding: %T", i) } } } } stashIdx++ } else { var idx int if !this { if i < s.numArgs { idx = -(i + 1) } else { stackIdx++ idx = stackIdx + stackOffset } } for scope, aps := range b.accessPoints { var level int for sc := scope; sc != nil && sc != s; sc = sc.outer { if sc.needStash || sc.isDynamic() { level++ } } if level > 255 { s.c.throwSyntaxError(0, "Maximum nesting level (256) exceeded") } code := scope.prg.code base := scope.base if this { if derivedCtor { for _, pc := range *aps { ap := &code[base+pc] switch (*ap).(type) { case loadStack: *ap = loadThisStack{} case initStack: // no-op case resolveThisStack: // no-op case _ret: // no-op, already in the right place default: s.c.assert(false, s.c.p.sourceOffset(pc), "Unsupported instruction for 'this'") } } } /*else { no-op }*/ } else if argsInStash { for _, pc := range *aps { ap := &code[base+pc] switch i := (*ap).(type) { case loadStack: *ap = loadStack1(idx) case storeStack: *ap = storeStack1(idx) case storeStackP: *ap = storeStack1P(idx) case loadStackLex: *ap = loadStack1Lex(idx) case storeStackLex: *ap = storeStack1Lex(idx) case storeStackLexP: *ap = storeStack1LexP(idx) case initStackP: *ap = initStack1P(idx) case initStack: *ap = initStack1(idx) case *loadMixed: *ap = &loadMixedStack1{name: i.name, idx: idx, level: uint8(level), callee: i.callee} case *loadMixedLex: *ap = &loadMixedStack1Lex{name: i.name, idx: idx, level: uint8(level), callee: i.callee} case *resolveMixed: *ap = &resolveMixedStack1{typ: i.typ, name: i.name, idx: idx, level: uint8(level), strict: i.strict} default: s.c.assert(false, s.c.p.sourceOffset(pc), "Unsupported instruction for binding: %T", i) } } } else { for _, pc := range *aps { ap := &code[base+pc] switch i := (*ap).(type) { case loadStack: *ap = loadStack(idx) case storeStack: *ap = storeStack(idx) case storeStackP: *ap = storeStackP(idx) case loadStackLex: *ap = loadStackLex(idx) case storeStackLex: *ap = storeStackLex(idx) case storeStackLexP: *ap = storeStackLexP(idx) case initStack: *ap = initStack(idx) case initStackP: *ap = initStackP(idx) case *loadMixed: *ap = &loadMixedStack{name: i.name, idx: idx, level: uint8(level), callee: i.callee} case *loadMixedLex: *ap = &loadMixedStackLex{name: i.name, idx: idx, level: uint8(level), callee: i.callee} case *resolveMixed: *ap = &resolveMixedStack{typ: i.typ, name: i.name, idx: idx, level: uint8(level), strict: i.strict} default: s.c.assert(false, s.c.p.sourceOffset(pc), "Unsupported instruction for binding: %T", i) } } } } } } for _, nested := range s.nested { nested.finaliseVarAlloc(stackIdx + stackOffset) } return stashIdx, stackIdx } func (s *scope) moveArgsToStash() { for _, b := range s.bindings { if !b.isArg { break } b.inStash = true } s.argsInStash = true s.needStash = true } func (c *compiler) trimCode(delta int) { src := c.p.code[delta:] newCode := make([]instruction, len(src)) copy(newCode, src) if cap(c.codeScratchpad) < cap(c.p.code) { c.codeScratchpad = c.p.code[:0] } c.p.code = newCode } func (s *scope) trimCode(delta int) { s.c.trimCode(delta) if delta != 0 { srcMap := s.c.p.srcMap for i := range srcMap { srcMap[i].pc -= delta } s.adjustBase(-delta) } } func (s *scope) adjustBase(delta int) { s.base += delta for _, nested := range s.nested { nested.adjustBase(delta) } } func (s *scope) makeNamesMap() map[unistring.String]uint32 { l := len(s.bindings) if l == 0 { return nil } names := make(map[unistring.String]uint32, l) for i, b := range s.bindings { idx := uint32(i) if b.isConst { idx |= maskConst if b.isStrict { idx |= maskStrict } } if b.isVar { idx |= maskVar } names[b.name] = idx } return names } func (s *scope) isDynamic() bool { return s.dynLookup || s.dynamic } func (s *scope) isFunction() bool { return s.funcType != funcNone && !s.eval } func (s *scope) deleteBinding(b *binding) { idx := 0 for i, bb := range s.bindings { if bb == b { idx = i goto found } } return found: delete(s.boundNames, b.name) copy(s.bindings[idx:], s.bindings[idx+1:]) l := len(s.bindings) - 1 s.bindings[l] = nil s.bindings = s.bindings[:l] } func (c *compiler) compile(in *ast.Program, strict, inGlobal bool, evalVm *vm) { c.ctxVM = evalVm eval := evalVm != nil c.p.src = in.File c.newScope() scope := c.scope scope.dynamic = true scope.eval = eval if !strict && len(in.Body) > 0 { strict = c.isStrict(in.Body) != nil } scope.strict = strict ownVarScope := eval && strict ownLexScope := !inGlobal || eval if ownVarScope { c.newBlockScope() scope = c.scope scope.variable = true } if eval && !inGlobal { for s := evalVm.stash; s != nil; s = s.outer { if ft := s.funcType; ft != funcNone && ft != funcArrow { scope.funcType = ft break } } } funcs := c.extractFunctions(in.Body) c.createFunctionBindings(funcs) numFuncs := len(scope.bindings) if inGlobal && !ownVarScope { if numFuncs == len(funcs) { c.compileFunctionsGlobalAllUnique(funcs) } else { c.compileFunctionsGlobal(funcs) } } c.compileDeclList(in.DeclarationList, false) numVars := len(scope.bindings) - numFuncs vars := make([]unistring.String, len(scope.bindings)) for i, b := range scope.bindings { vars[i] = b.name } if len(vars) > 0 && !ownVarScope && ownLexScope { if inGlobal { c.emit(&bindGlobal{ vars: vars[numFuncs:], funcs: vars[:numFuncs], deletable: eval, }) } else { c.emit(&bindVars{names: vars, deletable: eval}) } } var enter *enterBlock if c.compileLexicalDeclarations(in.Body, ownVarScope || !ownLexScope) { if ownLexScope { c.block = &block{ outer: c.block, typ: blockScope, needResult: true, } enter = &enterBlock{} c.emit(enter) } } if len(scope.bindings) > 0 && !ownLexScope { var lets, consts []unistring.String for _, b := range c.scope.bindings[numFuncs+numVars:] { if b.isConst { consts = append(consts, b.name) } else { lets = append(lets, b.name) } } c.emit(&bindGlobal{ vars: vars[numFuncs:], funcs: vars[:numFuncs], lets: lets, consts: consts, }) } if !inGlobal || ownVarScope { c.compileFunctions(funcs) } c.compileStatements(in.Body, true) if enter != nil { c.leaveScopeBlock(enter) c.popScope() } scope.finaliseVarAlloc(0) c.stringCache = nil } func (c *compiler) compileDeclList(v []*ast.VariableDeclaration, inFunc bool) { for _, value := range v { c.createVarBindings(value, inFunc) } } func (c *compiler) extractLabelled(st ast.Statement) ast.Statement { if st, ok := st.(*ast.LabelledStatement); ok { return c.extractLabelled(st.Statement) } return st } func (c *compiler) extractFunctions(list []ast.Statement) (funcs []*ast.FunctionDeclaration) { for _, st := range list { var decl *ast.FunctionDeclaration switch st := c.extractLabelled(st).(type) { case *ast.FunctionDeclaration: decl = st case *ast.LabelledStatement: if st1, ok := st.Statement.(*ast.FunctionDeclaration); ok { decl = st1 } else { continue } default: continue } funcs = append(funcs, decl) } return } func (c *compiler) createFunctionBindings(funcs []*ast.FunctionDeclaration) { s := c.scope if s.outer != nil { unique := !s.isFunction() && !s.variable && s.strict if !unique { hasNonStandard := false for _, decl := range funcs { if !decl.Function.Async && !decl.Function.Generator { s.bindNameLexical(decl.Function.Name.Name, false, int(decl.Function.Name.Idx1())-1) } else { hasNonStandard = true } } if hasNonStandard { for _, decl := range funcs { if decl.Function.Async || decl.Function.Generator { s.bindNameLexical(decl.Function.Name.Name, true, int(decl.Function.Name.Idx1())-1) } } } } else { for _, decl := range funcs { s.bindNameLexical(decl.Function.Name.Name, true, int(decl.Function.Name.Idx1())-1) } } } else { for _, decl := range funcs { s.bindName(decl.Function.Name.Name) } } } func (c *compiler) compileFunctions(list []*ast.FunctionDeclaration) { for _, decl := range list { c.compileFunction(decl) } } func (c *compiler) compileFunctionsGlobalAllUnique(list []*ast.FunctionDeclaration) { for _, decl := range list { c.compileFunctionLiteral(decl.Function, false).emitGetter(true) } } func (c *compiler) compileFunctionsGlobal(list []*ast.FunctionDeclaration) { m := make(map[unistring.String]int, len(list)) for i := len(list) - 1; i >= 0; i-- { name := list[i].Function.Name.Name if _, exists := m[name]; !exists { m[name] = i } } idx := 0 for i, decl := range list { name := decl.Function.Name.Name if m[name] == i { c.compileFunctionLiteral(decl.Function, false).emitGetter(true) c.scope.bindings[idx] = c.scope.boundNames[name] idx++ } else { leave := c.enterDummyMode() c.compileFunctionLiteral(decl.Function, false).emitGetter(false) leave() } } } func (c *compiler) createVarIdBinding(name unistring.String, offset int, inFunc bool) { if c.scope.strict { c.checkIdentifierLName(name, offset) c.checkIdentifierName(name, offset) } if !inFunc || name != "arguments" { c.scope.bindName(name) } } func (c *compiler) createBindings(target ast.Expression, createIdBinding func(name unistring.String, offset int)) { switch target := target.(type) { case *ast.Identifier: createIdBinding(target.Name, int(target.Idx)-1) case *ast.ObjectPattern: for _, prop := range target.Properties { switch prop := prop.(type) { case *ast.PropertyShort: createIdBinding(prop.Name.Name, int(prop.Name.Idx)-1) case *ast.PropertyKeyed: c.createBindings(prop.Value, createIdBinding) default: c.throwSyntaxError(int(target.Idx0()-1), "unsupported property type in ObjectPattern: %T", prop) } } if target.Rest != nil { c.createBindings(target.Rest, createIdBinding) } case *ast.ArrayPattern: for _, elt := range target.Elements { if elt != nil { c.createBindings(elt, createIdBinding) } } if target.Rest != nil { c.createBindings(target.Rest, createIdBinding) } case *ast.AssignExpression: c.createBindings(target.Left, createIdBinding) default: c.throwSyntaxError(int(target.Idx0()-1), "unsupported binding target: %T", target) } } func (c *compiler) createVarBinding(target ast.Expression, inFunc bool) { c.createBindings(target, func(name unistring.String, offset int) { c.createVarIdBinding(name, offset, inFunc) }) } func (c *compiler) createVarBindings(v *ast.VariableDeclaration, inFunc bool) { for _, item := range v.List { c.createVarBinding(item.Target, inFunc) } } func (c *compiler) createLexicalIdBinding(name unistring.String, isConst bool, offset int) *binding { if name == "let" { c.throwSyntaxError(offset, "let is disallowed as a lexically bound name") } if c.scope.strict { c.checkIdentifierLName(name, offset) c.checkIdentifierName(name, offset) } b, _ := c.scope.bindNameLexical(name, true, offset) if isConst { b.isConst, b.isStrict = true, true } return b } func (c *compiler) createLexicalIdBindingFuncBody(name unistring.String, isConst bool, offset int, calleeBinding *binding) *binding { if name == "let" { c.throwSyntaxError(offset, "let is disallowed as a lexically bound name") } if c.scope.strict { c.checkIdentifierLName(name, offset) c.checkIdentifierName(name, offset) } paramScope := c.scope.outer parentBinding := paramScope.boundNames[name] if parentBinding != nil { if parentBinding != calleeBinding && (name != "arguments" || !paramScope.argsNeeded) { c.throwSyntaxError(offset, "Identifier '%s' has already been declared", name) } } b, _ := c.scope.bindNameLexical(name, true, offset) if isConst { b.isConst, b.isStrict = true, true } return b } func (c *compiler) createLexicalBinding(target ast.Expression, isConst bool) { c.createBindings(target, func(name unistring.String, offset int) { c.createLexicalIdBinding(name, isConst, offset) }) } func (c *compiler) createLexicalBindings(lex *ast.LexicalDeclaration) { for _, d := range lex.List { c.createLexicalBinding(d.Target, lex.Token == token.CONST) } } func (c *compiler) compileLexicalDeclarations(list []ast.Statement, scopeDeclared bool) bool { for _, st := range list { if lex, ok := st.(*ast.LexicalDeclaration); ok { if !scopeDeclared { c.newBlockScope() scopeDeclared = true } c.createLexicalBindings(lex) } else if cls, ok := st.(*ast.ClassDeclaration); ok { if !scopeDeclared { c.newBlockScope() scopeDeclared = true } c.createLexicalIdBinding(cls.Class.Name.Name, false, int(cls.Class.Name.Idx)-1) } } return scopeDeclared } func (c *compiler) compileLexicalDeclarationsFuncBody(list []ast.Statement, calleeBinding *binding) { for _, st := range list { if lex, ok := st.(*ast.LexicalDeclaration); ok { isConst := lex.Token == token.CONST for _, d := range lex.List { c.createBindings(d.Target, func(name unistring.String, offset int) { c.createLexicalIdBindingFuncBody(name, isConst, offset, calleeBinding) }) } } else if cls, ok := st.(*ast.ClassDeclaration); ok { c.createLexicalIdBindingFuncBody(cls.Class.Name.Name, false, int(cls.Class.Name.Idx)-1, calleeBinding) } } } func (c *compiler) compileFunction(v *ast.FunctionDeclaration) { name := v.Function.Name.Name b := c.scope.boundNames[name] if b == nil || b.isVar { e := &compiledIdentifierExpr{ name: v.Function.Name.Name, } e.init(c, v.Function.Idx0()) e.emitSetter(c.compileFunctionLiteral(v.Function, false), false) } else { c.compileFunctionLiteral(v.Function, false).emitGetter(true) b.emitInitP() } } func (c *compiler) compileStandaloneFunctionDecl(v *ast.FunctionDeclaration) { if v.Function.Async { c.throwSyntaxError(int(v.Idx0())-1, "Async functions can only be declared at top level or inside a block.") } if v.Function.Generator { c.throwSyntaxError(int(v.Idx0())-1, "Generators can only be declared at top level or inside a block.") } if c.scope.strict { c.throwSyntaxError(int(v.Idx0())-1, "In strict mode code, functions can only be declared at top level or inside a block.") } c.throwSyntaxError(int(v.Idx0())-1, "In non-strict mode code, functions can only be declared at top level, inside a block, or as the body of an if statement.") } func (c *compiler) emit(instructions ...instruction) { c.p.code = append(c.p.code, instructions...) } func (c *compiler) throwSyntaxError(offset int, format string, args ...interface{}) { panic(&CompilerSyntaxError{ CompilerError: CompilerError{ File: c.p.src, Offset: offset, Message: fmt.Sprintf(format, args...), }, }) } func (c *compiler) isStrict(list []ast.Statement) *ast.StringLiteral { for _, st := range list { if st, ok := st.(*ast.ExpressionStatement); ok { if e, ok := st.Expression.(*ast.StringLiteral); ok { if e.Literal == `"use strict"` || e.Literal == `'use strict'` { return e } } else { break } } else { break } } return nil } func (c *compiler) isStrictStatement(s ast.Statement) *ast.StringLiteral { if s, ok := s.(*ast.BlockStatement); ok { return c.isStrict(s.List) } return nil } func (c *compiler) checkIdentifierName(name unistring.String, offset int) { switch name { case "implements", "interface", "let", "package", "private", "protected", "public", "static", "yield": c.throwSyntaxError(offset, "Unexpected strict mode reserved word") } } func (c *compiler) checkIdentifierLName(name unistring.String, offset int) { switch name { case "eval", "arguments": c.throwSyntaxError(offset, "Assignment to eval or arguments is not allowed in strict mode") } } // Enter a 'dummy' compilation mode. Any code produced after this method is called will be discarded after // leaveFunc is called with no additional side effects. This is useful for compiling code inside a // constant falsy condition 'if' branch or a loop (i.e 'if (false) { ... } or while (false) { ... }). // Such code should not be included in the final compilation result as it's never called, but it must // still produce compilation errors if there are any. // TODO: make sure variable lookups do not de-optimise parent scopes func (c *compiler) enterDummyMode() (leaveFunc func()) { savedBlock, savedProgram := c.block, c.p if savedBlock != nil { c.block = &block{ typ: savedBlock.typ, label: savedBlock.label, outer: savedBlock.outer, breaking: savedBlock.breaking, } } c.p = &Program{ src: c.p.src, } c.newScope() return func() { c.block, c.p = savedBlock, savedProgram c.popScope() } } func (c *compiler) compileStatementDummy(statement ast.Statement) { leave := c.enterDummyMode() c.compileStatement(statement, false) leave() } func (c *compiler) assert(cond bool, offset int, msg string, args ...interface{}) { if !cond { c.throwSyntaxError(offset, "Compiler bug: "+msg, args...) } } func privateIdString(desc unistring.String) unistring.String { return asciiString("#").Concat(stringValueFromRaw(desc)).string() } type privateName struct { idx int isStatic bool isMethod bool hasGetter, hasSetter bool } type resolvedPrivateName struct { name unistring.String idx uint32 level uint8 isStatic bool isMethod bool } func (r *resolvedPrivateName) string() unistring.String { return privateIdString(r.name) } type privateEnvRegistry struct { fields, methods []unistring.String } type classScope struct { c *compiler privateNames map[unistring.String]*privateName instanceEnv, staticEnv privateEnvRegistry outer *classScope } func (r *privateEnvRegistry) createPrivateMethodId(name unistring.String) int { r.methods = append(r.methods, name) return len(r.methods) - 1 } func (r *privateEnvRegistry) createPrivateFieldId(name unistring.String) int { r.fields = append(r.fields, name) return len(r.fields) - 1 } func (s *classScope) declarePrivateId(name unistring.String, kind ast.PropertyKind, isStatic bool, offset int) { pn := s.privateNames[name] if pn != nil { if pn.isStatic == isStatic { switch kind { case ast.PropertyKindGet: if pn.hasSetter && !pn.hasGetter { pn.hasGetter = true return } case ast.PropertyKindSet: if pn.hasGetter && !pn.hasSetter { pn.hasSetter = true return } } } s.c.throwSyntaxError(offset, "Identifier '#%s' has already been declared", name) panic("unreachable") } var env *privateEnvRegistry if isStatic { env = &s.staticEnv } else { env = &s.instanceEnv } pn = &privateName{ isStatic: isStatic, hasGetter: kind == ast.PropertyKindGet, hasSetter: kind == ast.PropertyKindSet, } if kind != ast.PropertyKindValue { pn.idx = env.createPrivateMethodId(name) pn.isMethod = true } else { pn.idx = env.createPrivateFieldId(name) } if s.privateNames == nil { s.privateNames = make(map[unistring.String]*privateName) } s.privateNames[name] = pn } func (s *classScope) getDeclaredPrivateId(name unistring.String) *privateName { if n := s.privateNames[name]; n != nil { return n } s.c.assert(false, 0, "getDeclaredPrivateId() for undeclared id") panic("unreachable") } func (c *compiler) resolvePrivateName(name unistring.String, offset int) (*resolvedPrivateName, *privateId) { level := 0 for s := c.classScope; s != nil; s = s.outer { if len(s.privateNames) > 0 { if pn := s.privateNames[name]; pn != nil { return &resolvedPrivateName{ name: name, idx: uint32(pn.idx), level: uint8(level), isStatic: pn.isStatic, isMethod: pn.isMethod, }, nil } level++ } } if c.ctxVM != nil { for s := c.ctxVM.privEnv; s != nil; s = s.outer { if id := s.names[name]; id != nil { return nil, id } } } c.throwSyntaxError(offset, "Private field '#%s' must be declared in an enclosing class", name) panic("unreachable") }