|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
|
|
package sun.tools.tree; |
|
|
|
import sun.tools.java.*; |
|
import sun.tools.asm.*; |
|
import java.io.PrintStream; |
|
import java.util.Hashtable; |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public |
|
class FieldExpression extends UnaryExpression { |
|
Identifier id; |
|
MemberDefinition field; |
|
Expression implementation; |
|
|
|
|
|
ClassDefinition clazz; |
|
|
|
// For an expression of the form '<class>.super', then |
|
|
|
private ClassDefinition superBase; |
|
|
|
|
|
|
|
*/ |
|
public FieldExpression(long where, Expression right, Identifier id) { |
|
super(FIELD, where, Type.tError, right); |
|
this.id = id; |
|
} |
|
public FieldExpression(long where, Expression right, MemberDefinition field) { |
|
super(FIELD, where, field.getType(), right); |
|
this.id = field.getName(); |
|
this.field = field; |
|
} |
|
|
|
public Expression getImplementation() { |
|
if (implementation != null) |
|
return implementation; |
|
return this; |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
private boolean isQualSuper() { |
|
return superBase != null; |
|
} |
|
|
|
|
|
|
|
*/ |
|
static public Identifier toIdentifier(Expression e) { |
|
StringBuffer buf = new StringBuffer(); |
|
while (e.op == FIELD) { |
|
FieldExpression fe = (FieldExpression)e; |
|
if (fe.id == idThis || fe.id == idClass) { |
|
return null; |
|
} |
|
buf.insert(0, fe.id); |
|
buf.insert(0, '.'); |
|
e = fe.right; |
|
} |
|
if (e.op != IDENT) { |
|
return null; |
|
} |
|
buf.insert(0, ((IdentifierExpression)e).id); |
|
return Identifier.lookup(buf.toString()); |
|
} |
|
|
|
/** |
|
* Convert a qualified name into a type. |
|
* Performs a careful check of each inner-class component, |
|
* including the JLS 6.6.1 access checks that were omitted |
|
* in 'FieldExpression.toType'. |
|
* <p> |
|
* This code is similar to 'checkCommon', which could be cleaned |
|
* up a bit long the lines we have done here. |
|
*/ |
|
/*-------------------------------------------------------* |
|
Type toQualifiedType(Environment env, Context ctx) { |
|
ClassDefinition ctxClass = ctx.field.getClassDefinition(); |
|
Type rty = right.toQualifiedType(env, ctx); |
|
if (rty == Type.tPackage) { |
|
// Is this field expression a non-inner type? |
|
Identifier nm = toIdentifier(this); |
|
if ((nm != null) && env.classExists(nm)) { |
|
Type t = Type.tClass(nm); |
|
if (env.resolve(where, ctxClass, t)) { |
|
return t; |
|
} else { |
|
return null; |
|
} |
|
} |
|
// Not a type. Must be a package prefix. |
|
return Type.tPackage; |
|
} |
|
if (rty == null) { |
|
// An error was already reported, so quit. |
|
return null; |
|
} |
|
|
|
// Check inner-class qualification while unwinding from recursion. |
|
try { |
|
ClassDefinition rightClass = env.getClassDefinition(rty); |
|
|
|
// Local variables, which cannot be inner classes, |
|
// are ignored here, and thus will not hide inner |
|
// classes. Is this correct? |
|
MemberDefinition field = rightClass.getInnerClass(env, id); |
|
if (field == null) { |
|
env.error(where, "inner.class.expected", id, rightClass); |
|
return Type.tError; |
|
} |
|
|
|
ClassDefinition innerClass = field.getInnerClass(); |
|
Type t = innerClass.getType(); |
|
|
|
if (!ctxClass.canAccess(env, field)) { |
|
env.error(where, "no.type.access", id, rightClass, ctxClass); |
|
return t; |
|
} |
|
if (field.isProtected() |
|
&& !ctxClass.protectedAccess(env, field, rty)) { |
|
env.error(where, "invalid.protected.type.use", id, ctxClass, rty); |
|
return t; |
|
} |
|
|
|
// These were omitted earlier in calls to 'toType', but I can't |
|
// see any reason for that. I think it was an oversight. See |
|
// 'checkCommon' and 'checkInnerClass'. |
|
innerClass.noteUsedBy(ctxClass, where, env); |
|
ctxClass.addDependency(field.getClassDeclaration()); |
|
|
|
return t; |
|
|
|
} catch (ClassNotFound e) { |
|
env.error(where, "class.not.found", e.name, ctx.field); |
|
} |
|
|
|
// Class not found. |
|
return null; |
|
} |
|
*-------------------------------------------------------*/ |
|
|
|
/** |
|
* Convert an '.' expression to a type |
|
*/ |
|
|
|
// This is a rewrite to treat qualified names in a |
|
// context in which a type name is expected in the |
|
// same way that they are handled for an ambiguous |
|
// or expression-expected context in 'checkCommon' |
|
// below. The new code is cleaner and allows better |
|
// localization of errors. Unfortunately, most |
|
// qualified names appearing in types are actually |
|
// handled by 'Environment.resolve'. There isn't |
|
// much point, then, in breaking out 'toType' as a |
|
// special case until the other cases can be cleaned |
|
// up as well. For the time being, we will leave this |
|
// code disabled, thus reducing the testing requirements. |
|
/*-------------------------------------------------------* |
|
Type toType(Environment env, Context ctx) { |
|
Type t = toQualifiedType(env, ctx); |
|
if (t == null) { |
|
return Type.tError; |
|
} |
|
if (t == Type.tPackage) { |
|
FieldExpression.reportFailedPackagePrefix(env, right, true); |
|
return Type.tError; |
|
} |
|
return t; |
|
} |
|
*-------------------------------------------------------*/ |
|
|
|
Type toType(Environment env, Context ctx) { |
|
Identifier id = toIdentifier(this); |
|
if (id == null) { |
|
env.error(where, "invalid.type.expr"); |
|
return Type.tError; |
|
} |
|
Type t = Type.tClass(ctx.resolveName(env, id)); |
|
if (env.resolve(where, ctx.field.getClassDefinition(), t)) { |
|
return t; |
|
} |
|
return Type.tError; |
|
} |
|
|
|
/** |
|
* Check if the present name is part of a scoping prefix. |
|
*/ |
|
|
|
public Vset checkAmbigName(Environment env, Context ctx, |
|
Vset vset, Hashtable exp, |
|
UnaryExpression loc) { |
|
if (id == idThis || id == idClass) { |
|
loc = null; |
|
} |
|
return checkCommon(env, ctx, vset, exp, loc, false); |
|
} |
|
|
|
/** |
|
* Check the expression |
|
*/ |
|
|
|
public Vset checkValue(Environment env, Context ctx, |
|
Vset vset, Hashtable exp) { |
|
vset = checkCommon(env, ctx, vset, exp, null, false); |
|
if (id == idSuper && type != Type.tError) { |
|
// "super" is not allowed in this context. |
|
|
|
env.error(where, "undef.var.super", idSuper); |
|
} |
|
return vset; |
|
} |
|
|
|
/** |
|
* If 'checkAmbiguousName' returns 'Package.tPackage', then it was |
|
* unable to resolve any prefix of the qualified name. This method |
|
* attempts to diagnose the problem. |
|
*/ |
|
|
|
static void reportFailedPackagePrefix(Environment env, Expression right) { |
|
reportFailedPackagePrefix(env, right, false); |
|
} |
|
|
|
static void reportFailedPackagePrefix(Environment env, |
|
Expression right, |
|
boolean mustBeType) { |
|
|
|
Expression idp = right; |
|
while (idp instanceof UnaryExpression) |
|
idp = ((UnaryExpression)idp).right; |
|
IdentifierExpression ie = (IdentifierExpression)idp; |
|
|
|
// It may be that 'ie' refers to an ambiguous class. Check this |
|
|
|
try { |
|
env.resolve(ie.id); |
|
} catch (AmbiguousClass e) { |
|
env.error(right.where, "ambig.class", e.name1, e.name2); |
|
return; |
|
} catch (ClassNotFound e) { |
|
} |
|
|
|
if (idp == right) { |
|
if (mustBeType) { |
|
env.error(ie.where, "undef.class", ie.id); |
|
} else { |
|
env.error(ie.where, "undef.var.or.class", ie.id); |
|
} |
|
} else { |
|
if (mustBeType) { |
|
env.error(ie.where, "undef.class.or.package", ie.id); |
|
} else { |
|
env.error(ie.where, "undef.var.class.or.package", ie.id); |
|
} |
|
} |
|
} |
|
|
|
/** |
|
* Rewrite accesses to private fields of another class. |
|
*/ |
|
|
|
private Expression |
|
implementFieldAccess(Environment env, Context ctx, Expression base, boolean isLHS) { |
|
ClassDefinition abase = accessBase(env, ctx); |
|
if (abase != null) { |
|
|
|
// If the field is final and its initializer is a constant expression, |
|
// then just rewrite to the constant expression. This is not just an |
|
// optimization, but is required for correctness. If an expression is |
|
// rewritten to use an access method, then its status as a constant |
|
// expression is lost. This was the cause of bug 4098737. Note that |
|
// a call to 'getValue(env)' below would not be correct, as it attempts |
|
// to simplify the initial value expression, which must not occur until |
|
|
|
if (field.isFinal()) { |
|
Expression e = (Expression)field.getValue(); |
|
// Must not be LHS here. Test as a precaution, |
|
// as we may not be careful to avoid this when |
|
|
|
if ((e != null) && e.isConstant() && !isLHS) { |
|
return e.copyInline(ctx); |
|
} |
|
} |
|
|
|
|
|
MemberDefinition af = abase.getAccessMember(env, ctx, field, isQualSuper()); |
|
//System.out.println("Using access method " + af); |
|
|
|
if (!isLHS) { |
|
//System.out.println("Reading " + field + |
|
// " via access method " + af); |
|
// If referencing the value of the field, then replace |
|
// with a call to the access method. If assigning to |
|
// the field, a call to the update method will be |
|
// generated later. It is important that |
|
// 'implementation' not be set to non-null if the |
|
// expression is a valid assignment target. |
|
|
|
if (field.isStatic()) { |
|
Expression args[] = { }; |
|
Expression call = |
|
new MethodExpression(where, null, af, args); |
|
return new CommaExpression(where, base, call); |
|
} else { |
|
Expression args[] = { base }; |
|
return new MethodExpression(where, null, af, args); |
|
} |
|
} |
|
} |
|
|
|
return null; |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
private ClassDefinition accessBase(Environment env, Context ctx) { |
|
if (field.isPrivate()) { |
|
ClassDefinition cdef = field.getClassDefinition(); |
|
ClassDefinition ctxClass = ctx.field.getClassDefinition(); |
|
if (cdef == ctxClass){ |
|
// If access from same class as field, then no access |
|
|
|
return null; |
|
} |
|
|
|
return cdef; |
|
} else if (field.isProtected()) { |
|
if (superBase == null) { |
|
// If access is not via qualified super, then it is either |
|
// OK without an access method, or it is an illegal access |
|
// for which an error message should have been issued. |
|
|
|
return null; |
|
} |
|
ClassDefinition cdef = field.getClassDefinition(); |
|
ClassDefinition ctxClass = ctx.field.getClassDefinition(); |
|
if (cdef.inSamePackage(ctxClass)) { |
|
|
|
return null; |
|
} |
|
// Access via qualified super. |
|
// An access method is needed in the qualifying class, an |
|
// immediate subclass of the class containing the selected |
|
// field. NOTE: The fact that the returned class is 'superBase' |
|
// carries the additional bit of information (that a special |
|
// superclass access method is being created) which is provided |
|
|
|
return superBase; |
|
} else { |
|
|
|
return null; |
|
} |
|
} |
|
|
|
|
|
|
|
*/ |
|
static boolean isTypeAccessible(long where, |
|
Environment env, |
|
Type t, |
|
ClassDefinition c) { |
|
switch (t.getTypeCode()) { |
|
case TC_CLASS: |
|
try { |
|
Identifier nm = t.getClassName(); |
|
// Why not just use 'Environment.getClassDeclaration' here? |
|
// But 'Environment.getClassDeclation' has special treatment |
|
// for local classes that is probably necessary. This code |
|
|
|
ClassDefinition def = env.getClassDefinition(t); |
|
return c.canAccess(env, def.getClassDeclaration()); |
|
} catch (ClassNotFound e) {} |
|
return true; |
|
case TC_ARRAY: |
|
return isTypeAccessible(where, env, t.getElementType(), c); |
|
default: |
|
return true; |
|
} |
|
} |
|
|
|
/** |
|
* Common code for checkValue and checkAmbigName |
|
*/ |
|
|
|
private Vset checkCommon(Environment env, Context ctx, |
|
Vset vset, Hashtable exp, |
|
UnaryExpression loc, boolean isLHS) { |
|
|
|
|
|
if (id == idClass) { |
|
|
|
|
|
Type t = right.toType(env, ctx); |
|
|
|
if (!t.isType(TC_CLASS) && !t.isType(TC_ARRAY)) { |
|
if (t.isType(TC_ERROR)) { |
|
type = Type.tClassDesc; |
|
return vset; |
|
} |
|
String wrc = null; |
|
switch (t.getTypeCode()) { |
|
case TC_VOID: wrc = "Void"; break; |
|
case TC_BOOLEAN: wrc = "Boolean"; break; |
|
case TC_BYTE: wrc = "Byte"; break; |
|
case TC_CHAR: wrc = "Character"; break; |
|
case TC_SHORT: wrc = "Short"; break; |
|
case TC_INT: wrc = "Integer"; break; |
|
case TC_FLOAT: wrc = "Float"; break; |
|
case TC_LONG: wrc = "Long"; break; |
|
case TC_DOUBLE: wrc = "Double"; break; |
|
default: |
|
env.error(right.where, "invalid.type.expr"); |
|
return vset; |
|
} |
|
Identifier wid = Identifier.lookup(idJavaLang+"."+wrc); |
|
Expression wcls = new TypeExpression(where, Type.tClass(wid)); |
|
implementation = new FieldExpression(where, wcls, idTYPE); |
|
vset = implementation.checkValue(env, ctx, vset, exp); |
|
type = implementation.type; |
|
return vset; |
|
} |
|
|
|
|
|
if (t.isVoidArray()) { |
|
type = Type.tClassDesc; |
|
env.error(right.where, "void.array"); |
|
return vset; |
|
} |
|
|
|
|
|
long fwhere = ctx.field.getWhere(); |
|
ClassDefinition fcls = ctx.field.getClassDefinition(); |
|
MemberDefinition lookup = fcls.getClassLiteralLookup(fwhere); |
|
|
|
String sig = t.getTypeSignature(); |
|
String className; |
|
if (t.isType(TC_CLASS)) { |
|
// sig is like "Lfoo/bar;", name is like "foo.bar". |
|
|
|
className = sig.substring(1, sig.length()-1) |
|
.replace(SIGC_PACKAGE, '.'); |
|
} else { |
|
// sig is like "[Lfoo/bar;" or "[I"; |
|
|
|
className = sig.replace(SIGC_PACKAGE, '.'); |
|
} |
|
|
|
if (fcls.isInterface()) { |
|
// The immediately-enclosing type is an interface. |
|
// The class literal can only appear in an initialization |
|
// expression, so don't bother caching it. (This could |
|
// lose if many initializations use the same class literal, |
|
|
|
implementation = |
|
makeClassLiteralInlineRef(env, ctx, lookup, className); |
|
} else { |
|
// Cache the call to the helper, as it may be executed |
|
|
|
ClassDefinition inClass = lookup.getClassDefinition(); |
|
MemberDefinition cfld = |
|
getClassLiteralCache(env, ctx, className, inClass); |
|
implementation = |
|
makeClassLiteralCacheRef(env, ctx, lookup, cfld, className); |
|
} |
|
|
|
vset = implementation.checkValue(env, ctx, vset, exp); |
|
type = implementation.type; |
|
return vset; |
|
} |
|
|
|
// Arrive here if not a class literal. |
|
|
|
if (field != null) { |
|
|
|
// The field as been pre-set, e.g., as the result of transforming |
|
// an 'IdentifierExpression'. Most error-checking has already been |
|
// performed at this point. |
|
// QUERY: Why don't we further unify checking of identifier |
|
// expressions and field expressions that denote instance and |
|
// class variables? |
|
|
|
implementation = implementFieldAccess(env, ctx, right, isLHS); |
|
return (right == null) ? |
|
vset : right.checkAmbigName(env, ctx, vset, exp, this); |
|
} |
|
|
|
|
|
vset = right.checkAmbigName(env, ctx, vset, exp, this); |
|
if (right.type == Type.tPackage) { |
|
|
|
if (loc == null) { |
|
FieldExpression.reportFailedPackagePrefix(env, right); |
|
return vset; |
|
} |
|
|
|
// ASSERT(loc.right == this) |
|
|
|
|
|
Identifier nm = toIdentifier(this); |
|
if ((nm != null) && env.classExists(nm)) { |
|
loc.right = new TypeExpression(where, Type.tClass(nm)); |
|
|
|
ClassDefinition ctxClass = ctx.field.getClassDefinition(); |
|
env.resolve(where, ctxClass, loc.right.type); |
|
return vset; |
|
} |
|
|
|
|
|
type = Type.tPackage; |
|
return vset; |
|
} |
|
|
|
// Good; we have a well-defined qualifier type. |
|
|
|
ClassDefinition ctxClass = ctx.field.getClassDefinition(); |
|
boolean staticRef = (right instanceof TypeExpression); |
|
|
|
try { |
|
|
|
// Handle array 'length' field, e.g., 'x.length'. |
|
|
|
if (!right.type.isType(TC_CLASS)) { |
|
if (right.type.isType(TC_ARRAY) && id.equals(idLength)) { |
|
// Verify that the type of the base expression is accessible. |
|
|
|
if (!FieldExpression.isTypeAccessible(where, env, right.type, ctxClass)) { |
|
ClassDeclaration cdecl = ctxClass.getClassDeclaration(); |
|
if (staticRef) { |
|
env.error(where, "no.type.access", |
|
id, right.type.toString(), cdecl); |
|
} else { |
|
env.error(where, "cant.access.member.type", |
|
id, right.type.toString(), cdecl); |
|
} |
|
} |
|
type = Type.tInt; |
|
implementation = new LengthExpression(where, right); |
|
return vset; |
|
} |
|
if (!right.type.isType(TC_ERROR)) { |
|
env.error(where, "invalid.field.reference", id, right.type); |
|
} |
|
return vset; |
|
} |
|
|
|
// At this point, we know that 'right.type' is a class type. |
|
|
|
// Note that '<expr>.super(...)' and '<expr>.this(...)' cases never |
|
// reach here. Instead, '<expr>' is stored as the 'outerArg' field |
|
// of a 'SuperExpression' or 'ThisExpression' node. |
|
|
|
// If our prefix is of the form '<class>.super', then we are |
|
// about to do a field selection '<class>.super.<field>'. |
|
// Save the qualifying class in 'superBase', which is non-null |
|
// only if the current FieldExpression is a qualified 'super' form. |
|
// Also, set 'sourceClass' to the "effective accessing class" relative |
|
// to which access checks will be performed. Normally, this is the |
|
// immediately enclosing class. For '<class>.this' and '<class>.super', |
|
// however, we use <class>. |
|
|
|
ClassDefinition sourceClass = ctxClass; |
|
if (right instanceof FieldExpression) { |
|
Identifier id = ((FieldExpression)right).id; |
|
if (id == idThis) { |
|
sourceClass = ((FieldExpression)right).clazz; |
|
} else if (id == idSuper) { |
|
sourceClass = ((FieldExpression)right).clazz; |
|
superBase = sourceClass; |
|
} |
|
} |
|
|
|
// Handle 'class.this' and 'class.super'. |
|
// |
|
// Suppose 'super.name' appears within a class C with immediate |
|
// superclass S. According to JLS 15.10.2, 'super.name' in this |
|
// case is equivalent to '((S)this).name'. Analogously, we interpret |
|
// 'class.super.name' as '((S)(class.this)).name', where S is the |
|
// immediate superclass of (enclosing) class 'class'. |
|
// Note that 'super' may not stand alone as an expression, but must |
|
// occur as the qualifying expression of a field access or a method |
|
// invocation. This is enforced in 'SuperExpression.checkValue' and |
|
// 'FieldExpression.checkValue', and need not concern us here. |
|
|
|
|
|
clazz = env.getClassDefinition(right.type); |
|
if (id == idThis || id == idSuper) { |
|
if (!staticRef) { |
|
env.error(right.where, "invalid.type.expr"); |
|
} |
|
|
|
// We used to check that 'right.type' is accessible here, |
|
// per JLS 6.6.1. As a result of the fix for 4102393, however, |
|
// the qualifying class name must exactly match an enclosing |
|
// outer class, which is necessarily accessible. |
|
|
|
|
|
if (ctx.field.isSynthetic()) |
|
throw new CompilerError("synthetic qualified this"); |
|
/*********************************/ |
|
|
|
// A.this means we're inside an A and we want its self ptr. |
|
// C.this is always the same as this when C is innermost. |
|
// Another A.this means we skip out to get a "hidden" this, |
|
// just as ASuper.foo skips out to get a hidden variable. |
|
// Last argument 'true' means we want an exact class match, |
|
|
|
implementation = ctx.findOuterLink(env, where, clazz, null, true); |
|
vset = implementation.checkValue(env, ctx, vset, exp); |
|
if (id == idSuper) { |
|
type = clazz.getSuperClass().getType(); |
|
} else { |
|
type = clazz.getType(); |
|
} |
|
return vset; |
|
} |
|
|
|
|
|
field = clazz.getVariable(env, id, sourceClass); |
|
|
|
if (field == null && staticRef && loc != null) { |
|
// Is this field expression an inner type? |
|
// Search the class and its supers (but not its outers). |
|
// QUERY: We may need to get the inner class from a |
|
// superclass of 'clazz'. 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. |
|
|
|
field = clazz.getInnerClass(env, id); |
|
if (field != null) { |
|
return checkInnerClass(env, ctx, vset, exp, loc); |
|
} |
|
} |
|
|
|
// If not a variable reference, diagnose error if name is |
|
// that of a method. |
|
|
|
if (field == null) { |
|
if ((field = clazz.findAnyMethod(env, id)) != null) { |
|
env.error(where, "invalid.field", |
|
id, field.getClassDeclaration()); |
|
} else { |
|
env.error(where, "no.such.field", id, clazz); |
|
} |
|
return vset; |
|
} |
|
|
|
// At this point, we have identified a valid field. |
|
|
|
|
|
if (!FieldExpression.isTypeAccessible(where, env, right.type, sourceClass)) { |
|
ClassDeclaration cdecl = sourceClass.getClassDeclaration(); |
|
if (staticRef) { |
|
env.error(where, "no.type.access", |
|
id, right.type.toString(), cdecl); |
|
} else { |
|
env.error(where, "cant.access.member.type", |
|
id, right.type.toString(), cdecl); |
|
} |
|
} |
|
|
|
type = field.getType(); |
|
|
|
if (!sourceClass.canAccess(env, field)) { |
|
env.error(where, "no.field.access", |
|
id, clazz, sourceClass.getClassDeclaration()); |
|
return vset; |
|
} |
|
|
|
if (staticRef && !field.isStatic()) { |
|
// 'Class.field' is not legal when field is not static; |
|
// see JLS 15.13.1. This case was permitted by javac |
|
// prior to 1.2; static refs were silently changed to |
|
|
|
env.error(where, "no.static.field.access", id, clazz); |
|
return vset; |
|
} else { |
|
|
|
implementation = implementFieldAccess(env, ctx, right, isLHS); |
|
} |
|
|
|
|
|
if (field.isProtected() |
|
&& !(right instanceof SuperExpression |
|
|
|
|| (right instanceof FieldExpression && |
|
((FieldExpression)right).id == idSuper)) |
|
&& !sourceClass.protectedAccess(env, field, right.type)) { |
|
env.error(where, "invalid.protected.field.use", |
|
field.getName(), field.getClassDeclaration(), |
|
right.type); |
|
return vset; |
|
} |
|
|
|
if ((!field.isStatic()) && |
|
(right.op == THIS) && !vset.testVar(ctx.getThisNumber())) { |
|
env.error(where, "access.inst.before.super", id); |
|
} |
|
|
|
if (field.reportDeprecated(env)) { |
|
env.error(where, "warn."+"field.is.deprecated", |
|
id, field.getClassDefinition()); |
|
} |
|
|
|
// When a package-private class defines public or protected |
|
// members, those members may sometimes be accessed from |
|
// outside of the package in public subclasses. In these |
|
// cases, we need to massage the getField to refer to |
|
// to an accessible subclass rather than the package-private |
|
// parent class. Part of fix for 4135692. |
|
|
|
// Find out if the class which contains this field |
|
// reference has access to the class which declares the |
|
|
|
if (sourceClass == ctxClass) { |
|
ClassDefinition declarer = field.getClassDefinition(); |
|
if (declarer.isPackagePrivate() && |
|
!declarer.getName().getQualifier() |
|
.equals(sourceClass.getName().getQualifier())) { |
|
|
|
//System.out.println("The access of member " + |
|
// field + " declared in class " + |
|
// declarer + |
|
// " is not allowed by the VM from class " + |
|
// ctxClass + |
|
// ". Replacing with an access of class " + |
|
// clazz); |
|
|
|
// We cannot make this access at the VM level. |
|
// Construct a member which will stand for this |
|
|
|
field = |
|
MemberDefinition.makeProxyMember(field, clazz, env); |
|
} |
|
} |
|
|
|
sourceClass.addDependency(field.getClassDeclaration()); |
|
|
|
} catch (ClassNotFound e) { |
|
env.error(where, "class.not.found", e.name, ctx.field); |
|
|
|
} catch (AmbiguousMember e) { |
|
env.error(where, "ambig.field", |
|
id, e.field1.getClassDeclaration(), e.field2.getClassDeclaration()); |
|
} |
|
return vset; |
|
} |
|
|
|
/** |
|
* Return a <code>FieldUpdater</code> object to be used in updating the |
|
* value of the location denoted by <code>this</code>, which must be an |
|
* expression suitable for the left-hand side of an assignment. |
|
* This is used for implementing assignments to private fields for which |
|
* an access method is required. Returns null if no access method is |
|
* needed, in which case the assignment is handled in the usual way, by |
|
* direct access. Only simple assignment expressions are handled here |
|
* Assignment operators and pre/post increment/decrement operators are |
|
* are handled by 'getUpdater' below. |
|
* <p> |
|
* Must be called after 'checkValue', else 'right' will be invalid. |
|
*/ |
|
|
|
|
|
public FieldUpdater getAssigner(Environment env, Context ctx) { |
|
if (field == null) { |
|
// Field can legitimately be null if the field name was |
|
// undefined, in which case an error was reported, but |
|
// no value for 'field' is available. |
|
|
|
return null; |
|
} |
|
ClassDefinition abase = accessBase(env, ctx); |
|
if (abase != null) { |
|
MemberDefinition setter = abase.getUpdateMember(env, ctx, field, isQualSuper()); |
|
|
|
Expression base = (right == null) ? null : right.copyInline(ctx); |
|
|
|
return new FieldUpdater(where, field, base, null, setter); |
|
} |
|
return null; |
|
} |
|
|
|
/** |
|
* Return a <code>FieldUpdater</code> object to be used in updating the |
|
* value of the location denoted by <code>this</code>, which must be an |
|
* expression suitable for the left-hand side of an assignment. This is |
|
* used for implementing the assignment operators and the increment and |
|
* decrement operators on private fields that are accessed from another |
|
* class, e.g, uplevel from an inner class. Returns null if no access |
|
* method is needed. |
|
* <p> |
|
* Must be called after 'checkValue', else 'right' will be invalid. |
|
*/ |
|
|
|
public FieldUpdater getUpdater(Environment env, Context ctx) { |
|
if (field == null) { |
|
// Field can legitimately be null if the field name was |
|
// undefined, in which case an error was reported, but |
|
// no value for 'field' is available. |
|
|
|
return null; |
|
} |
|
ClassDefinition abase = accessBase(env, ctx); |
|
if (abase != null) { |
|
MemberDefinition getter = abase.getAccessMember(env, ctx, field, isQualSuper()); |
|
MemberDefinition setter = abase.getUpdateMember(env, ctx, field, isQualSuper()); |
|
|
|
Expression base = (right == null) ? null : right.copyInline(ctx); |
|
return new FieldUpdater(where, field, base, getter, setter); |
|
} |
|
return null; |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
private Vset checkInnerClass(Environment env, Context ctx, |
|
Vset vset, Hashtable exp, |
|
UnaryExpression loc) { |
|
ClassDefinition inner = field.getInnerClass(); |
|
type = inner.getType(); |
|
|
|
if (!inner.isTopLevel()) { |
|
env.error(where, "inner.static.ref", inner.getName()); |
|
} |
|
|
|
Expression te = new TypeExpression(where, type); |
|
|
|
|
|
ClassDefinition ctxClass = ctx.field.getClassDefinition(); |
|
try { |
|
if (!ctxClass.canAccess(env, field)) { |
|
ClassDefinition clazz = env.getClassDefinition(right.type); |
|
//env.error(where, "no.type.access", |
|
|
|
env.error(where, "no.type.access", |
|
id, clazz, ctxClass.getClassDeclaration()); |
|
return vset; |
|
} |
|
|
|
if (field.isProtected() |
|
&& !(right instanceof SuperExpression |
|
|
|
|| (right instanceof FieldExpression && |
|
((FieldExpression)right).id == idSuper)) |
|
&& !ctxClass.protectedAccess(env, field, right.type)){ |
|
env.error(where, "invalid.protected.field.use", |
|
field.getName(), field.getClassDeclaration(), |
|
right.type); |
|
return vset; |
|
} |
|
|
|
inner.noteUsedBy(ctxClass, where, env); |
|
|
|
} catch (ClassNotFound e) { |
|
env.error(where, "class.not.found", e.name, ctx.field); |
|
} |
|
|
|
ctxClass.addDependency(field.getClassDeclaration()); |
|
if (loc == null) |
|
|
|
return te.checkValue(env, ctx, vset, exp); |
|
loc.right = te; |
|
return vset; |
|
} |
|
|
|
|
|
|
|
*/ |
|
public Vset checkLHS(Environment env, Context ctx, |
|
Vset vset, Hashtable exp) { |
|
boolean hadField = (field != null); |
|
|
|
|
|
checkCommon(env, ctx, vset, exp, null, true); |
|
|
|
// If 'implementation' is set to a non-null value, then the |
|
// field expression does not denote an assignable location, |
|
|
|
if (implementation != null) { |
|
|
|
return super.checkLHS(env, ctx, vset, exp); |
|
} |
|
|
|
if (field != null && field.isFinal() && !hadField) { |
|
if (field.isBlankFinal()) { |
|
if (field.isStatic()) { |
|
if (right != null) { |
|
env.error(where, "qualified.static.final.assign"); |
|
} |
|
// Continue with checking anyhow. |
|
// In fact, it would be easy to allow this case. |
|
} else { |
|
if ((right != null) && (right.op != THIS)) { |
|
env.error(where, "bad.qualified.final.assign", field.getName()); |
|
// The actual instance could be anywhere, so don't |
|
|
|
return vset; |
|
} |
|
} |
|
vset = checkFinalAssign(env, ctx, vset, where, field); |
|
} else { |
|
env.error(where, "assign.to.final", id); |
|
} |
|
} |
|
return vset; |
|
} |
|
|
|
|
|
|
|
*/ |
|
public Vset checkAssignOp(Environment env, Context ctx, |
|
Vset vset, Hashtable exp, Expression outside) { |
|
|
|
|
|
checkCommon(env, ctx, vset, exp, null, true); |
|
|
|
// If 'implementation' is set to a non-null value, then the |
|
// field expression does not denote an assignable location, |
|
|
|
if (implementation != null) { |
|
return super.checkLHS(env, ctx, vset, exp); |
|
} |
|
if (field != null && field.isFinal()) { |
|
env.error(where, "assign.to.final", id); |
|
} |
|
return vset; |
|
} |
|
|
|
/** |
|
* There is a simple assignment being made to the given final field. |
|
* The field was named either by a simple name or by an almost-simple |
|
* expression of the form "this.v". |
|
* Check if this is a legal assignment. |
|
* <p> |
|
* Blank final variables can be set in initializers or constructor |
|
* bodies. In all cases there must be definite single assignment. |
|
* (All instance and instance variable initializers and each |
|
* constructor body are treated as if concatenated for the purposes |
|
* of this check. Assignment to "this.x" is treated as a definite |
|
* assignment to the simple name "x" which names the instance variable.) |
|
*/ |
|
|
|
public static Vset checkFinalAssign(Environment env, Context ctx, |
|
Vset vset, long where, |
|
MemberDefinition field) { |
|
if (field.isBlankFinal() |
|
&& field.getClassDefinition() == ctx.field.getClassDefinition()) { |
|
int number = ctx.getFieldNumber(field); |
|
if (number >= 0 && vset.testVarUnassigned(number)) { |
|
|
|
vset = vset.addVar(number); |
|
} else { |
|
|
|
Identifier id = field.getName(); |
|
env.error(where, "assign.to.blank.final", id); |
|
} |
|
} else { |
|
|
|
Identifier id = field.getName(); |
|
env.error(where, "assign.to.final", id); |
|
} |
|
return vset; |
|
} |
|
|
|
private static MemberDefinition getClassLiteralCache(Environment env, |
|
Context ctx, |
|
String className, |
|
ClassDefinition c) { |
|
// Given a class name, look for a static field to cache it. |
|
// className lname |
|
// pkg.Foo class$pkg$Foo |
|
// [Lpkg.Foo; array$Lpkg$Foo |
|
// [[Lpkg.Foo; array$$Lpkg$Foo |
|
// [I array$I |
|
|
|
String lname; |
|
if (!className.startsWith(SIG_ARRAY)) { |
|
lname = prefixClass + className.replace('.', '$'); |
|
} else { |
|
lname = prefixArray + className.substring(1); |
|
lname = lname.replace(SIGC_ARRAY, '$'); |
|
if (className.endsWith(SIG_ENDCLASS)) { |
|
|
|
lname = lname.substring(0, lname.length() - 1); |
|
lname = lname.replace('.', '$'); |
|
} |
|
// else [I => array$I or some such; lname is already OK |
|
} |
|
Identifier fname = Identifier.lookup(lname); |
|
|
|
// The class to put the cache in is now given as an argument. |
|
// |
|
// ClassDefinition c = ctx.field.getClassDefinition(); |
|
// while (c.isInnerClass()) { |
|
// c = c.getOuterClass(); |
|
|
|
MemberDefinition cfld; |
|
try { |
|
cfld = c.getVariable(env, fname, c); |
|
} catch (ClassNotFound ee) { |
|
return null; |
|
} catch (AmbiguousMember ee) { |
|
return null; |
|
} |
|
|
|
// Ignore inherited field. Each top-level class |
|
// containing a given class literal must have its own copy, |
|
// both for reasons of binary compatibility and to prevent |
|
// access violations should the superclass be in another |
|
|
|
if (cfld != null && cfld.getClassDefinition() == c) { |
|
return cfld; |
|
} |
|
|
|
// Since each class now has its own copy, we might as well |
|
// tighten up the access to private (previously default). |
|
// Part of fix for 4106051. |
|
|
|
return env.makeMemberDefinition(env, c.getWhere(), |
|
c, null, |
|
M_STATIC | M_SYNTHETIC, |
|
Type.tClassDesc, fname, |
|
null, null, null); |
|
} |
|
|
|
private Expression makeClassLiteralCacheRef(Environment env, Context ctx, |
|
MemberDefinition lookup, |
|
MemberDefinition cfld, |
|
String className) { |
|
Expression ccls = new TypeExpression(where, |
|
cfld.getClassDefinition() |
|
.getType()); |
|
Expression cache = new FieldExpression(where, ccls, cfld); |
|
Expression cacheOK = |
|
new NotEqualExpression(where, cache.copyInline(ctx), |
|
new NullExpression(where)); |
|
Expression lcls = |
|
new TypeExpression(where, lookup.getClassDefinition() .getType()); |
|
Expression name = new StringExpression(where, className); |
|
Expression namearg[] = { name }; |
|
Expression setCache = new MethodExpression(where, lcls, |
|
lookup, namearg); |
|
setCache = new AssignExpression(where, cache.copyInline(ctx), |
|
setCache); |
|
return new ConditionalExpression(where, cacheOK, cache, setCache); |
|
} |
|
|
|
private Expression makeClassLiteralInlineRef(Environment env, Context ctx, |
|
MemberDefinition lookup, |
|
String className) { |
|
Expression lcls = |
|
new TypeExpression(where, lookup.getClassDefinition().getType()); |
|
Expression name = new StringExpression(where, className); |
|
Expression namearg[] = { name }; |
|
Expression getClass = new MethodExpression(where, lcls, |
|
lookup, namearg); |
|
return getClass; |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
public boolean isConstant() { |
|
if (implementation != null) |
|
return implementation.isConstant(); |
|
if ((field != null) |
|
&& (right == null || right instanceof TypeExpression |
|
|| (right.op == THIS && right.where == where))) { |
|
return field.isConstant(); |
|
} |
|
return false; |
|
} |
|
|
|
|
|
|
|
*/ |
|
public Expression inline(Environment env, Context ctx) { |
|
if (implementation != null) |
|
return implementation.inline(env, ctx); |
|
// A field expression may have the side effect of causing |
|
// a NullPointerException, so evaluate it even though |
|
// the value is not needed. Similarly, static field dereferences |
|
// may cause class initialization, so they mustn't be omitted |
|
// either. |
|
// |
|
// However, NullPointerException can't happen and initialization must |
|
// already have occurred if you are dotting into 'this'. So |
|
|
|
Expression e = inlineValue(env, ctx); |
|
if (e instanceof FieldExpression) { |
|
FieldExpression fe = (FieldExpression) e; |
|
if ((fe.right != null) && (fe.right.op==THIS)) |
|
return null; |
|
// It should be possible to split this into two checks: one using |
|
// isNonNull() for non-statics and a different check for statics. |
|
// That would make the inlining slightly less conservative by |
|
// allowing, for example, dotting into String constants. |
|
} |
|
return e; |
|
} |
|
public Expression inlineValue(Environment env, Context ctx) { |
|
if (implementation != null) |
|
return implementation.inlineValue(env, ctx); |
|
try { |
|
if (field == null) { |
|
return this; |
|
} |
|
|
|
if (field.isFinal()) { |
|
Expression e = (Expression)field.getValue(env); |
|
if ((e != null) && e.isConstant()) { |
|
|
|
e = e.copyInline(ctx); |
|
e.where = where; |
|
return new CommaExpression(where, right, e).inlineValue(env, ctx); |
|
} |
|
} |
|
|
|
if (right != null) { |
|
if (field.isStatic()) { |
|
Expression e = right.inline(env, ctx); |
|
right = null; |
|
if (e != null) { |
|
return new CommaExpression(where, e, this); |
|
} |
|
} else { |
|
right = right.inlineValue(env, ctx); |
|
} |
|
} |
|
return this; |
|
|
|
} catch (ClassNotFound e) { |
|
throw new CompilerError(e); |
|
} |
|
} |
|
public Expression inlineLHS(Environment env, Context ctx) { |
|
if (implementation != null) |
|
return implementation.inlineLHS(env, ctx); |
|
if (right != null) { |
|
if (field.isStatic()) { |
|
Expression e = right.inline(env, ctx); |
|
right = null; |
|
if (e != null) { |
|
return new CommaExpression(where, e, this); |
|
} |
|
} else { |
|
right = right.inlineValue(env, ctx); |
|
} |
|
} |
|
return this; |
|
} |
|
|
|
public Expression copyInline(Context ctx) { |
|
if (implementation != null) |
|
return implementation.copyInline(ctx); |
|
return super.copyInline(ctx); |
|
} |
|
|
|
|
|
|
|
*/ |
|
public int costInline(int thresh, Environment env, Context ctx) { |
|
if (implementation != null) |
|
return implementation.costInline(thresh, env, ctx); |
|
if (ctx == null) { |
|
return 3 + ((right == null) ? 0 |
|
: right.costInline(thresh, env, ctx)); |
|
} |
|
|
|
ClassDefinition ctxClass = ctx.field.getClassDefinition(); |
|
try { |
|
// We only allow the inlining if the current class can access |
|
|
|
if ( ctxClass.permitInlinedAccess(env, field.getClassDeclaration()) |
|
&& ctxClass.permitInlinedAccess(env, field)) { |
|
if (right == null) { |
|
return 3; |
|
} else { |
|
ClassDeclaration rt = env.getClassDeclaration(right.type); |
|
if (ctxClass.permitInlinedAccess(env, rt)) { |
|
return 3 + right.costInline(thresh, env, ctx); |
|
} |
|
} |
|
} |
|
} catch (ClassNotFound e) { |
|
} |
|
return thresh; |
|
} |
|
|
|
|
|
|
|
*/ |
|
int codeLValue(Environment env, Context ctx, Assembler asm) { |
|
if (implementation != null) |
|
throw new CompilerError("codeLValue"); |
|
if (field.isStatic()) { |
|
if (right != null) { |
|
right.code(env, ctx, asm); |
|
return 1; |
|
} |
|
return 0; |
|
} |
|
right.codeValue(env, ctx, asm); |
|
return 1; |
|
} |
|
void codeLoad(Environment env, Context ctx, Assembler asm) { |
|
if (field == null) { |
|
throw new CompilerError("should not be null"); |
|
} |
|
if (field.isStatic()) { |
|
asm.add(where, opc_getstatic, field); |
|
} else { |
|
asm.add(where, opc_getfield, field); |
|
} |
|
} |
|
void codeStore(Environment env, Context ctx, Assembler asm) { |
|
if (field.isStatic()) { |
|
asm.add(where, opc_putstatic, field); |
|
} else { |
|
asm.add(where, opc_putfield, field); |
|
} |
|
} |
|
|
|
public void codeValue(Environment env, Context ctx, Assembler asm) { |
|
codeLValue(env, ctx, asm); |
|
codeLoad(env, ctx, asm); |
|
} |
|
|
|
|
|
|
|
*/ |
|
public void print(PrintStream out) { |
|
out.print("("); |
|
if (right != null) { |
|
right.print(out); |
|
} else { |
|
out.print("<empty>"); |
|
} |
|
out.print("." + id + ")"); |
|
if (implementation != null) { |
|
out.print("/IMPL="); |
|
implementation.print(out); |
|
} |
|
} |
|
} |