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