/* * Copyright (c) 1994, 2003, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package sun.tools.tree; import sun.tools.java.*; import sun.tools.asm.Assembler; /** * WARNING: The contents of this source file are not part of any * supported API. Code that depends on them does so at its own risk: * they are subject to change or removal without notice. */ public class Context implements Constants { Context prev; Node node; int varNumber; LocalMember locals; LocalMember classes; MemberDefinition field; int scopeNumber; int frameNumber; /** * Create the initial context for a method * The incoming context is inherited from */ public Context(Context ctx, MemberDefinition field) { this.field = field; if (ctx == null) { this.frameNumber = 1; this.scopeNumber = 2; this.varNumber = 0; } else { this.prev = ctx; this.locals = ctx.locals; this.classes = ctx.classes; if (field != null && (field.isVariable() || field.isInitializer())) { // Variables and initializers are inlined into a constructor. // Model this by inheriting the frame number of the parent, // which will contain a "this" parameter. this.frameNumber = ctx.frameNumber; this.scopeNumber = ctx.scopeNumber + 1; } else { this.frameNumber = ctx.scopeNumber + 1; this.scopeNumber = this.frameNumber + 1; } this.varNumber = ctx.varNumber; } } /** * Create a new context, for initializing a class. */ public Context(Context ctx, ClassDefinition c) { this(ctx, (MemberDefinition)null); } /** * Create a new nested context, for a block statement */ Context(Context ctx, Node node) { if (ctx == null) { this.frameNumber = 1; this.scopeNumber = 2; this.varNumber = 0; } else { this.prev = ctx; this.locals = ctx.locals; // Inherit local classes from surrounding block, // just as for local variables. Fixes 4074421. this.classes = ctx.classes; this.varNumber = ctx.varNumber; this.field = ctx.field; this.frameNumber = ctx.frameNumber; this.scopeNumber = ctx.scopeNumber + 1; this.node = node; } } public Context(Context ctx) { this(ctx, (Node)null); } /** * Declare local */ public int declare(Environment env, LocalMember local) { //System.out.println( "DECLARE= " + local.getName() + "=" + varNumber + ", read=" + local.readcount + ", write=" + local.writecount + ", hash=" + local.hashCode()); local.scopeNumber = scopeNumber; if (this.field == null && idThis.equals(local.getName())) { local.scopeNumber += 1; // Anticipate variable or initializer. } if (local.isInnerClass()) { local.prev = classes; classes = local; return 0; } // Originally the statement: // // local.subModifiers(M_INLINEABLE); // // was here with the comment: // // // prevent inlining across call sites // // This statement prevented constant local variables from // inlining. It didn't seem to do anything useful. // // The statement has been removed and an assertion has been // added which mandates that the only members which are marked // with M_INLINEABLE are the ones for which isConstant() is true. // (Fix for 4106244.) // // Addition to the above comment: they might also be // final variables initialized with 'this', 'super', or other // final identifiers. See VarDeclarationStatement.inline(). // So I've removed the assertion. The original subModifiers // call appears to have been there to fix nested class translation // breakage, which has been fixed in VarDeclarationStatement // now instead. (Fix for 4073244.) local.prev = locals; locals = local; local.number = varNumber; varNumber += local.getType().stackSize(); return local.number; } /** * Get a local variable by name */ public LocalMember getLocalField(Identifier name) { for (LocalMember f = locals ; f != null ; f = f.prev) { if (name.equals(f.getName())) { return f; } } return null; } /** * Get the scope number for a reference to a member of this class * (Larger scope numbers are more deeply nested.) * @see LocalMember#scopeNumber */ public int getScopeNumber(ClassDefinition c) { for (Context ctx = this; ctx != null; ctx = ctx.prev) { if (ctx.field == null) continue; if (ctx.field.getClassDefinition() == c) { return ctx.frameNumber; } } return -1; } private MemberDefinition getFieldCommon(Environment env, Identifier name, boolean apparentOnly) throws AmbiguousMember, ClassNotFound { // Note: This is structured as a pair of parallel lookups. // If we were to redesign Context, we might prefer to walk // along a single chain of scopes. LocalMember lf = getLocalField(name); int ls = (lf == null) ? -2 : lf.scopeNumber; ClassDefinition thisClass = field.getClassDefinition(); // Also look for a class member in a shallower scope. for (ClassDefinition c = thisClass; c != null; c = c.getOuterClass()) { MemberDefinition f = c.getVariable(env, name, thisClass); if (f != null && getScopeNumber(c) > ls) { if (apparentOnly && f.getClassDefinition() != c) { continue; } return f; } } return lf; } /** * Assign a number to a class field. * (This is used to track definite assignment of some blank finals.) */ public int declareFieldNumber(MemberDefinition field) { return declare(null, new LocalMember(field)); } /** * Retrieve a number previously assigned by declareMember(). * Return -1 if there was no such assignment in this context. */ public int getFieldNumber(MemberDefinition field) { for (LocalMember f = locals ; f != null ; f = f.prev) { if (f.getMember() == field) { return f.number; } } return -1; } /** * Return the local field or member field corresponding to a number. * Return null if there is no such field. */ public MemberDefinition getElement(int number) { for (LocalMember f = locals ; f != null ; f = f.prev) { if (f.number == number) { MemberDefinition field = f.getMember(); return (field != null) ? field : f; } } return null; } /** * Get a local class by name */ public LocalMember getLocalClass(Identifier name) { for (LocalMember f = classes ; f != null ; f = f.prev) { if (name.equals(f.getName())) { return f; } } return null; } private MemberDefinition getClassCommon(Environment env, Identifier name, boolean apparentOnly) throws ClassNotFound { LocalMember lf = getLocalClass(name); int ls = (lf == null) ? -2 : lf.scopeNumber; // Also look for a class member in a shallower scope. for (ClassDefinition c = field.getClassDefinition(); c != null; c = c.getOuterClass()) { // QUERY: We may need to get the inner class from a // superclass of 'c'. This call is prepared to // resolve the superclass if necessary. Can we arrange // to assure that it is always previously resolved? // This is one of a small number of problematic calls that // requires 'getSuperClass' to resolve superclasses on demand. // See 'ClassDefinition.getInnerClass(env, nm)'. MemberDefinition f = c.getInnerClass(env, name); if (f != null && getScopeNumber(c) > ls) { if (apparentOnly && f.getClassDefinition() != c) { continue; } return f; } } return lf; } /** * Get either a local variable, or a field in a current class */ public final MemberDefinition getField(Environment env, Identifier name) throws AmbiguousMember, ClassNotFound { return getFieldCommon(env, name, false); } /** * Like getField, except that it skips over inherited fields. * Used for error checking. */ public final MemberDefinition getApparentField(Environment env, Identifier name) throws AmbiguousMember, ClassNotFound { return getFieldCommon(env, name, true); } /** * Check if the given field is active in this context. */ public boolean isInScope(LocalMember field) { for (LocalMember f = locals ; f != null ; f = f.prev) { if (field == f) { return true; } } return false; } /** * Notice a reference (usually an uplevel one). * Update the references list of every enclosing class * which is enclosed by the scope of the target. * Update decisions about which uplevels to make into fields. * Return the uplevel reference descriptor, or null if it's local. * <p> * The target must be in scope in this context. * So, call this method only from the check phase. * (In other phases, the context may be less complete.) * <p> * This can and should be called both before and after classes are frozen. * It should be a no-op, and will raise a compiler error if not. */ public UplevelReference noteReference(Environment env, LocalMember target) { int targetScopeNumber = !isInScope(target) ? -1 : target.scopeNumber; // Walk outward visiting each scope. // Note each distinct frame (i.e., enclosing method). // For each frame in which the variable is uplevel, // record the event in the references list of the enclosing class. UplevelReference res = null; int currentFrameNumber = -1; for (Context refctx = this; refctx != null; refctx = refctx.prev) { if (currentFrameNumber == refctx.frameNumber) { continue; // we're processing frames, not contexts } currentFrameNumber = refctx.frameNumber; if (targetScopeNumber >= currentFrameNumber) { break; // the target is native to this frame } // process a frame which is using this variable as an uplevel ClassDefinition refc = refctx.field.getClassDefinition(); UplevelReference r = refc.getReference(target); r.noteReference(env, refctx); // remember the reference pertaining to the innermost frame if (res == null) { res = r; } } return res; } /** * Implement a reference (usually an uplevel one). * Call noteReference() first, to make sure the reference * lists are up to date. * <p> * The resulting expression tree does not need checking; * it can be code-generated right away. * If the reference is not uplevel, the result is an IDENT or THIS. */ public Expression makeReference(Environment env, LocalMember target) { UplevelReference r = noteReference(env, target); // Now create a referencing expression. if (r != null) { return r.makeLocalReference(env, this); } else if (idThis.equals(target.getName())) { return new ThisExpression(0, target); } else { return new IdentifierExpression(0, target); } } /** * Return a local expression which can serve as the base reference * for the given field. If the field is a constructor, return an * expression for the implicit enclosing instance argument. * <p> * Return null if there is no need for such an argument, * or if there was an error. */ public Expression findOuterLink(Environment env, long where, MemberDefinition f) { // reqc is the base pointer type required to use f ClassDefinition fc = f.getClassDefinition(); ClassDefinition reqc = f.isStatic() ? null : !f.isConstructor() ? fc : fc.isTopLevel() ? null : fc.getOuterClass(); if (reqc == null) { return null; } return findOuterLink(env, where, reqc, f, false); } private static boolean match(Environment env, ClassDefinition thisc, ClassDefinition reqc) { try { return thisc == reqc || reqc.implementedBy(env, thisc.getClassDeclaration()); } catch (ClassNotFound ee) { return false; } } public Expression findOuterLink(Environment env, long where, ClassDefinition reqc, MemberDefinition f, boolean needExactMatch) { if (field.isStatic()) { if (f == null) { // say something like: undefined variable A.this Identifier nm = reqc.getName().getFlatName().getName(); env.error(where, "undef.var", Identifier.lookup(nm,idThis)); } else if (f.isConstructor()) { env.error(where, "no.outer.arg", reqc, f.getClassDeclaration()); } else if (f.isMethod()) { env.error(where, "no.static.meth.access", f, f.getClassDeclaration()); } else { env.error(where, "no.static.field.access", f.getName(), f.getClassDeclaration()); } // This is an attempt at error recovery. // Unfortunately, the constructor may throw // a null pointer exception after failing to resolve // 'idThis'. Since an error message has already been // issued previously, this exception is caught and // silently ignored. Ideally, we should avoid throwing // the exception. Expression e = new ThisExpression(where, this); e.type = reqc.getType(); return e; } // use lp to scan for current instances (locals named "this") LocalMember lp = locals; // thise is a link expression being built up Expression thise = null; // root is the local variable (idThis) at the far left of thise LocalMember root = null; // thisc is the class of the link expression thise ClassDefinition thisc = null; // conCls is the class of the "this", in a constructor ClassDefinition conCls = null; if (field.isConstructor()) { conCls = field.getClassDefinition(); } if (!field.isMethod()) { thisc = field.getClassDefinition(); thise = new ThisExpression(where, this); } while (true) { if (thise == null) { // start fresh from lp while (lp != null && !idThis.equals(lp.getName())) { lp = lp.prev; } if (lp == null) { break; } thise = new ThisExpression(where, lp); thisc = lp.getClassDefinition(); root = lp; lp = lp.prev; } // Require exact class identity when called with // 'needExactMatch' true. This is done when checking // the '<class>.this' syntax. Fixes 4102393 and 4133457. if (thisc == reqc || (!needExactMatch && match(env, thisc, reqc))) { break; } // move out one step, if the current instance has an outer link MemberDefinition outerMember = thisc.findOuterMember(); if (outerMember == null) { thise = null; continue; // try to find more help in lp } ClassDefinition prevc = thisc; thisc = prevc.getOuterClass(); if (prevc == conCls) { // Must pick up "this$C" from the constructor argument, // not from "this.this$C", since the latter may not be // initialized properly. (This way is cheaper too.) Identifier nm = outerMember.getName(); IdentifierExpression arg = new IdentifierExpression(where, nm); arg.bind(env, this); thise = arg; } else { thise = new FieldExpression(where, thise, outerMember); } } if (thise != null) { // mark crossed scopes // ????? //ensureAvailable(root); return thise; } if (f == null) { // say something like: undefined variable A.this Identifier nm = reqc.getName().getFlatName().getName(); env.error(where, "undef.var", Identifier.lookup(nm,idThis)); } else if (f.isConstructor()) { env.error(where, "no.outer.arg", reqc, f.getClassDefinition()); } else { env.error(where, "no.static.field.access", f, field); } // avoid floodgating: Expression e = new ThisExpression(where, this); e.type = reqc.getType(); return e; } /** * Is there a "this" of type reqc in scope? */ public static boolean outerLinkExists(Environment env, ClassDefinition reqc, ClassDefinition thisc) { while (!match(env, thisc, reqc)) { if (thisc.isTopLevel()) { return false; } thisc = thisc.getOuterClass(); } return true; } /** * From which enclosing class do members of this type come? */ public ClassDefinition findScope(Environment env, ClassDefinition reqc) { ClassDefinition thisc = field.getClassDefinition(); while (thisc != null && !match(env, thisc, reqc)) { thisc = thisc.getOuterClass(); } return thisc; } /** * Resolve a type name from within a local scope. * @see Environment#resolveName */ Identifier resolveName(Environment env, Identifier name) { // This logic is pretty much exactly parallel to that of // Environment.resolveName(). if (name.isQualified()) { // Try to resolve the first identifier component, // because inner class names take precedence over // package prefixes. (Cf. Environment.resolveName.) Identifier rhead = resolveName(env, name.getHead()); if (rhead.hasAmbigPrefix()) { // The first identifier component refers to an // ambiguous class. Limp on. We throw away the // rest of the classname as it is irrelevant. // (part of solution for 4059855). return rhead; } if (!env.classExists(rhead)) { return env.resolvePackageQualifiedName(name); } try { return env.getClassDefinition(rhead). resolveInnerClass(env, name.getTail()); } catch (ClassNotFound ee) { // return partially-resolved name someone else can fail on return Identifier.lookupInner(rhead, name.getTail()); } } // Look for an unqualified name in enclosing scopes. try { MemberDefinition f = getClassCommon(env, name, false); if (f != null) { return f.getInnerClass().getName(); } } catch (ClassNotFound ee) { // a missing superclass, or something catastrophic } // look in imports, etc. return env.resolveName(name); } /** * Return the name of a lexically apparent type, * skipping inherited members, and ignoring * the current pacakge and imports. * This is used for error checking. */ public Identifier getApparentClassName(Environment env, Identifier name) { if (name.isQualified()) { // Try to resolve the first identifier component, // because inner class names take precedence over // package prefixes. (Cf. Environment.resolveName.) Identifier rhead = getApparentClassName(env, name.getHead()); return (rhead == null) ? idNull : Identifier.lookup(rhead, name.getTail()); } // Look for an unqualified name in enclosing scopes. try { MemberDefinition f = getClassCommon(env, name, true); if (f != null) { return f.getInnerClass().getName(); } } catch (ClassNotFound ee) { // a missing superclass, or something catastrophic } // the enclosing class name is the only apparent package member: Identifier topnm = field.getClassDefinition().getTopClass().getName(); if (topnm.getName().equals(name)) { return topnm; } return idNull; } /** * Raise an error if a blank final was definitely unassigned * on entry to a loop, but has possibly been assigned on the * back-branch. If this is the case, the loop may be assigning * it multiple times. */ public void checkBackBranch(Environment env, Statement loop, Vset vsEntry, Vset vsBack) { for (LocalMember f = locals ; f != null ; f = f.prev) { if (f.isBlankFinal() && vsEntry.testVarUnassigned(f.number) && !vsBack.testVarUnassigned(f.number)) { env.error(loop.where, "assign.to.blank.final.in.loop", f.getName()); } } } /** * Check if a field can reach another field (only considers * forward references, not the access modifiers). */ public boolean canReach(Environment env, MemberDefinition f) { return field.canReach(env, f); } /** * Get the context that corresponds to a label, return null if * not found. */ public Context getLabelContext(Identifier lbl) { for (Context ctx = this ; ctx != null ; ctx = ctx.prev) { if ((ctx.node != null) && (ctx.node instanceof Statement)) { if (((Statement)(ctx.node)).hasLabel(lbl)) return ctx; } } return null; } /** * Get the destination context of a break */ public Context getBreakContext(Identifier lbl) { if (lbl != null) { return getLabelContext(lbl); } for (Context ctx = this ; ctx != null ; ctx = ctx.prev) { if (ctx.node != null) { switch (ctx.node.op) { case SWITCH: case FOR: case DO: case WHILE: return ctx; } } } return null; } /** * Get the destination context of a continue */ public Context getContinueContext(Identifier lbl) { if (lbl != null) { return getLabelContext(lbl); } for (Context ctx = this ; ctx != null ; ctx = ctx.prev) { if (ctx.node != null) { switch (ctx.node.op) { case FOR: case DO: case WHILE: return ctx; } } } return null; } /** * Get the destination context of a return (the method body) */ public CheckContext getReturnContext() { for (Context ctx = this ; ctx != null ; ctx = ctx.prev) { // The METHOD node is set up by Statement.checkMethod(). if (ctx.node != null && ctx.node.op == METHOD) { return (CheckContext)ctx; } } return null; } /** * Get the context of the innermost surrounding try-block. * Consider only try-blocks contained within the same method. * (There could be others when searching from within a method * of a local class, but they are irrelevant to our purpose.) * This is used for recording DA/DU information preceding * all abnormal transfers of control: break, continue, return, * and throw. */ public CheckContext getTryExitContext() { for (Context ctx = this; ctx != null && ctx.node != null && ctx.node.op != METHOD; ctx = ctx.prev) { if (ctx.node.op == TRY) { return (CheckContext)ctx; } } return null; } /** * Get the nearest inlined context */ Context getInlineContext() { for (Context ctx = this ; ctx != null ; ctx = ctx.prev) { if (ctx.node != null) { switch (ctx.node.op) { case INLINEMETHOD: case INLINENEWINSTANCE: return ctx; } } } return null; } /** * Get the context of a field that is being inlined */ Context getInlineMemberContext(MemberDefinition field) { for (Context ctx = this ; ctx != null ; ctx = ctx.prev) { if (ctx.node != null) { switch (ctx.node.op) { case INLINEMETHOD: if (((InlineMethodExpression)ctx.node).field.equals(field)) { return ctx; } break; case INLINENEWINSTANCE: if (((InlineNewInstanceExpression)ctx.node).field.equals(field)) { return ctx; } } } } return null; } /** * Remove variables from the vset set that are no longer part of * this context. */ public final Vset removeAdditionalVars(Vset vset) { return vset.removeAdditionalVars(varNumber); } public final int getVarNumber() { return varNumber; } /** * Return the number of the innermost current instance reference. */ public int getThisNumber() { LocalMember thisf = getLocalField(idThis); if (thisf != null && thisf.getClassDefinition() == field.getClassDefinition()) { return thisf.number; } // this is a variable; there is no "this" (should not happen) return varNumber; } /** * Return the field containing the present context. */ public final MemberDefinition getField() { return field; } /** * Extend an environment with the given context. * The resulting environment behaves the same as * the given one, except that resolveName() takes * into account local class names in this context. */ public static Environment newEnvironment(Environment env, Context ctx) { return new ContextEnvironment(env, ctx); } } final class ContextEnvironment extends Environment { Context ctx; Environment innerEnv; ContextEnvironment(Environment env, Context ctx) { super(env, env.getSource()); this.ctx = ctx; this.innerEnv = env; } public Identifier resolveName(Identifier name) { return ctx.resolveName(innerEnv, name); } }