blob: 3a55ab7a80fdb3faffb0d5775808e80a55200cab [file] [log] [blame]
/*
* 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.*;
import java.io.PrintStream;
import java.util.Hashtable;
/**
* 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 FieldExpression extends UnaryExpression {
Identifier id;
MemberDefinition field;
Expression implementation;
// The class from which the field is select ed.
ClassDefinition clazz;
// For an expression of the form '<class>.super', then
// this is <class>, else null.
private ClassDefinition superBase;
/**
* constructor
*/
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;
}
/**
* Return true if the field is being selected from
* a qualified 'super'.
*/
private boolean isQualSuper() {
return superBase != null;
}
/**
* Convert an '.' expression to a qualified identifier
*/
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 ommitted 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; // this cannot be a type or package
}
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.
// It must always qualify another name.
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) {
// Find the leftmost component, and put the blame on it.
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
// with a call to env.resolve(). Part of solution for 4059855.
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
// after the checking phase, for example, after definite assignment checks.
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
// compiling an erroneous program.
if ((e != null) && e.isConstant() && !isLHS) {
return e.copyInline(ctx);
}
}
//System.out.println("Finding access method for " + field);
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.
// (See 'checkLHS'.)
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;
}
/**
* Determine if an access method is required, and, if so, return
* the class in which it should appear, else 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
// method is needed.
return null;
}
// An access method is needed in the class containing the field.
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.
// Legal accesses include unqualified 'super.foo'.
return null;
}
ClassDefinition cdef = field.getClassDefinition();
ClassDefinition ctxClass = ctx.field.getClassDefinition();
if (cdef.inSamePackage(ctxClass)) {
// Access to protected member in same package always allowed.
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
// to 'getAccessMember' via its 'isSuper' argument.
return superBase;
} else {
// No access method needed.
return null;
}
}
/**
* Determine if a type is accessible from a given class.
*/
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
// was adapted from 'Environment.resolve'.
ClassDefinition def = env.getClassDefinition(t);
return c.canAccess(env, def.getClassDeclaration());
} catch (ClassNotFound e) {} // Ignore -- reported elsewhere.
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) {
// Handle class literal, e.g., 'x.class'.
if (id == idClass) {
// In 'x.class', 'x' must be a type name, possibly qualified.
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; // java.lang.Class
return vset;
}
// Check for the bogus type `array of void'
if (t.isVoidArray()) {
type = Type.tClassDesc;
env.error(right.where, "void.array");
return vset;
}
// it is a class or array
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".
// We assume SIG_CLASS and SIG_ENDCLASS are 1 char each.
className = sig.substring(1, sig.length()-1)
.replace(SIGC_PACKAGE, '.');
} else {
// sig is like "[Lfoo/bar;" or "[I";
// name is like "[Lfoo.bar" or (again) "[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,
// but saves time and code space otherwise.)
implementation =
makeClassLiteralInlineRef(env, ctx, lookup, className);
} else {
// Cache the call to the helper, as it may be executed
// many times (e.g., if the class literal is inside a loop).
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; // java.lang.Class
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);
}
// Does the qualifier have a meaning of its own?
vset = right.checkAmbigName(env, ctx, vset, exp, this);
if (right.type == Type.tPackage) {
// Are we out of options?
if (loc == null) {
FieldExpression.reportFailedPackagePrefix(env, right);
return vset;
}
// ASSERT(loc.right == this)
// Nope. Is this field expression a type?
Identifier nm = toIdentifier(this);
if ((nm != null) && env.classExists(nm)) {
loc.right = new TypeExpression(where, Type.tClass(nm));
// Check access. (Cf. IdentifierExpression.toResolvedType.)
ClassDefinition ctxClass = ctx.field.getClassDefinition();
env.resolve(where, ctxClass, loc.right.type);
return vset;
}
// Let the caller make sense of it, then.
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.
// Required by JLS 6.6.1. Fixes 4094658.
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.
//ClassDefinition clazz = env.getClassDefinition(right.type);
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.
/*** Temporary assertion check ***/
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,
// not a subclass of the specified class ('clazz').
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 should be an instance variable or class variable.
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.
// See 'ClassDefinition.getInnerClass(env, nm)'.
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.
// Required by JLS 6.6.1. Fixes 4094658.
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
// be dynamic access of the form 'this.field'.
env.error(where, "no.static.field.access", id, clazz);
return vset;
} else {
// Rewrite access to use an access method if necessary.
implementation = implementFieldAccess(env, ctx, right, isLHS);
}
// Check for invalid access to protected field.
if (field.isProtected()
&& !(right instanceof SuperExpression
// Extension of JLS 6.6.2 for qualified 'super'.
|| (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
// public or protected field.
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 in ctxClass and set `field' to refer to it.
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.
// throw new CompilerError("getAssigner");
return null;
}
ClassDefinition abase = accessBase(env, ctx);
if (abase != null) {
MemberDefinition setter = abase.getUpdateMember(env, ctx, field, isQualSuper());
// It may not be necessary to copy 'right' here.
Expression base = (right == null) ? null : right.copyInline(ctx);
// Created 'FieldUpdater' has no getter method.
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.
// throw new CompilerError("getUpdater");
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());
// It may not be necessary to copy 'right' here.
Expression base = (right == null) ? null : right.copyInline(ctx);
return new FieldUpdater(where, field, base, getter, setter);
}
return null;
}
/**
* This field expression is an inner class reference.
* Finish checking it.
*/
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);
// check access
ClassDefinition ctxClass = ctx.field.getClassDefinition();
try {
if (!ctxClass.canAccess(env, field)) {
ClassDefinition clazz = env.getClassDefinition(right.type);
//env.error(where, "no.type.access",
// id, clazz, ctx.field.getClassDeclaration());
env.error(where, "no.type.access",
id, clazz, ctxClass.getClassDeclaration());
return vset;
}
if (field.isProtected()
&& !(right instanceof SuperExpression
// Extension of JLS 6.6.2 for qualified 'super'.
|| (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)
// Complain about a free-floating type name.
return te.checkValue(env, ctx, vset, exp);
loc.right = te;
return vset;
}
/**
* Check the expression if it appears on the LHS of an assignment
*/
public Vset checkLHS(Environment env, Context ctx,
Vset vset, Hashtable exp) {
boolean hadField = (field != null);
//checkValue(env, ctx, vset, exp);
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,
// e.g., the 'length' field of an array.
if (implementation != null) {
// This just reports an error and recovers.
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
// continue with checking the definite assignment status.
return vset;
}
}
vset = checkFinalAssign(env, ctx, vset, where, field);
} else {
env.error(where, "assign.to.final", id);
}
}
return vset;
}
/**
* Check the expression if it appears on the LHS of an op= expression
*/
public Vset checkAssignOp(Environment env, Context ctx,
Vset vset, Hashtable exp, Expression outside) {
//checkValue(env, ctx, vset, exp);
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,
// e.g., the 'length' field of an array.
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)) {
// definite single assignment
vset = vset.addVar(number);
} else {
// it is a blank final in this class, but not assignable
Identifier id = field.getName();
env.error(where, "assign.to.blank.final", id);
}
} else {
// give the generic error message
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
// [[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, '$'); // [[[I => array$$$I
if (className.endsWith(SIG_ENDCLASS)) {
// [Lpkg.Foo; => array$Lpkg$Foo
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
// package. Part of fix 4106051.
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.
// ** Temporarily retract this, as it tickles 4098316.
return env.makeMemberDefinition(env, c.getWhere(),
c, null,
M_STATIC | M_SYNTHETIC, // M_PRIVATE,
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;
}
/**
* Check if constant: Will it inline away?
*/
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;
}
/**
* Inline
*/
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 occured if you are dotting into 'this'. So
// allow fields of 'this' to be eliminated as a special case.
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()) {
// remove bogus line-number info
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);
}
/**
* The cost of inlining this expression
*/
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));
}
// ctxClass is the current class trying to inline this method
ClassDefinition ctxClass = ctx.field.getClassDefinition();
try {
// We only allow the inlining if the current class can access
// the field, the field's class, and right's declared type.
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;
}
/**
* Code
*/
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);
}
/**
* Print
*/
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);
}
}
}