/*
* 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);
}
}