/* * Copyright (c) 1994, 2006, 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.java; import sun.tools.tree.Node; import sun.tools.tree.Vset; import sun.tools.tree.Expression; import sun.tools.tree.Statement; import sun.tools.tree.Context; import sun.tools.asm.Assembler; import java.io.PrintStream; import java.util.Vector; import java.util.Map; import java.util.HashMap; /** * This class defines a member of a Java class: * a variable, a method, or an inner class. * * 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 MemberDefinition implements Constants { protected long where; protected int modifiers; protected Type type; protected String documentation; protected IdentifierToken expIds[]; protected ClassDeclaration exp[]; protected Node value; protected ClassDefinition clazz; protected Identifier name; protected ClassDefinition innerClass; protected MemberDefinition nextMember; protected MemberDefinition nextMatch; protected MemberDefinition accessPeer; protected boolean superAccessMethod; /** * Constructor */ public MemberDefinition(long where, ClassDefinition clazz, int modifiers, Type type, Identifier name, IdentifierToken expIds[], Node value) { if (expIds == null) { expIds = new IdentifierToken[0]; } this.where = where; this.clazz = clazz; this.modifiers = modifiers; this.type = type; this.name = name; this.expIds = expIds; this.value = value; } /** * Constructor for an inner class. * Inner classes are represented as fields right along with * variables and methods for simplicity of data structure, * and to reflect properly the textual declaration order. * <p> * This constructor calls the generic constructor for this * class, extracting all necessary values from the innerClass. */ public MemberDefinition(ClassDefinition innerClass) { this(innerClass.getWhere(), innerClass.getOuterClass(), innerClass.getModifiers(), innerClass.getType(), innerClass.getName().getFlatName().getName(), null, null); this.innerClass = innerClass; } /** * A cache of previously created proxy members. Used to ensure * uniqueness of proxy objects. See the makeProxyMember method * defined below. */ static private Map proxyCache; /** * Create a member which is externally the same as `field' but * is defined in class `classDef'. This is used by code * in sun.tools.tree.(MethodExpression,FieldExpression) as * part of the fix for bug 4135692. * * Proxy members should not be added, ala addMember(), to classes. * They are merely "stand-ins" to produce modified MethodRef * constant pool entries during code generation. * * We keep a cache of previously created proxy members not to * save time or space, but to ensure uniqueness of the proxy * member for any (field,classDef) pair. If these are not made * unique then we can end up generating duplicate MethodRef * constant pool entries during code generation. */ public static MemberDefinition makeProxyMember(MemberDefinition field, ClassDefinition classDef, Environment env) { if (proxyCache == null) { proxyCache = new HashMap(); } String key = field.toString() + "@" + classDef.toString(); // System.out.println("Key is : " + key); MemberDefinition proxy = (MemberDefinition)proxyCache.get(key); if (proxy != null) return proxy; proxy = new MemberDefinition(field.getWhere(), classDef, field.getModifiers(), field.getType(), field.getName(), field.getExceptionIds(), null); proxy.exp = field.getExceptions(env); proxyCache.put(key, proxy); return proxy; } /** * Get the position in the input */ public final long getWhere() { return where; } /** * Get the class declaration */ public final ClassDeclaration getClassDeclaration() { return clazz.getClassDeclaration(); } /** * A stub. Subclasses can do more checking. */ public void resolveTypeStructure(Environment env) { } /** * Get the class declaration in which the field is actually defined */ public ClassDeclaration getDefiningClassDeclaration() { return getClassDeclaration(); } /** * Get the class definition */ public final ClassDefinition getClassDefinition() { return clazz; } /** * Get the field's top-level enclosing class */ public final ClassDefinition getTopClass() { return clazz.getTopClass(); } /** * Get the field's modifiers */ public final int getModifiers() { return modifiers; } public final void subModifiers(int mod) { modifiers &= ~mod; } public final void addModifiers(int mod) { modifiers |= mod; } /** * Get the field's type */ public final Type getType() { return type; } /** * Get the field's name */ public final Identifier getName() { return name; } /** * Get arguments (a vector of LocalMember) */ public Vector getArguments() { return isMethod() ? new Vector() : null; } /** * Get the exceptions that are thrown by this method. */ public ClassDeclaration[] getExceptions(Environment env) { if (expIds != null && exp == null) { if (expIds.length == 0) exp = new ClassDeclaration[0]; else // we should have translated this already! throw new CompilerError("getExceptions "+this); } return exp; } public final IdentifierToken[] getExceptionIds() { return expIds; } /** * Get an inner class. */ public ClassDefinition getInnerClass() { return innerClass; } /** * Is this a synthetic field which holds a copy of, * or reference to, a local variable or enclosing instance? */ public boolean isUplevelValue() { if (!isSynthetic() || !isVariable() || isStatic()) { return false; } String name = this.name.toString(); return name.startsWith(prefixVal) || name.startsWith(prefixLoc) || name.startsWith(prefixThis); } public boolean isAccessMethod() { // This no longer works, because access methods // for constructors do not use the standard naming // scheme. // return isSynthetic() && isMethod() // && name.toString().startsWith(prefixAccess); // Assume that a method is an access method if it has // an access peer. NOTE: An access method will not be // recognized as such until 'setAccessMethodTarget' has // been called on it. return isSynthetic() && isMethod() && (accessPeer != null); } /** * Is this a synthetic method which provides access to a * visible private member? */ public MemberDefinition getAccessMethodTarget() { if (isAccessMethod()) { for (MemberDefinition f = accessPeer; f != null; f = f.accessPeer) { // perhaps skip over another access for the same field if (!f.isAccessMethod()) { return f; } } } return null; } public void setAccessMethodTarget(MemberDefinition target) { if (getAccessMethodTarget() != target) { /*-------------------* if (!isAccessMethod() || accessPeer != null || target.accessPeer != null) { throw new CompilerError("accessPeer"); } *-------------------*/ if (accessPeer != null || target.accessPeer != null) { throw new CompilerError("accessPeer"); } accessPeer = target; } } /** * If this method is a getter for a private field, return the setter. */ public MemberDefinition getAccessUpdateMember() { if (isAccessMethod()) { for (MemberDefinition f = accessPeer; f != null; f = f.accessPeer) { if (f.isAccessMethod()) { return f; } } } return null; } public void setAccessUpdateMember(MemberDefinition updater) { if (getAccessUpdateMember() != updater) { if (!isAccessMethod() || updater.getAccessMethodTarget() != getAccessMethodTarget()) { throw new CompilerError("accessPeer"); } updater.accessPeer = accessPeer; accessPeer = updater; } } /** * Is this an access method for a field selection or method call * of the form '...super.foo' or '...super.foo()'? */ public final boolean isSuperAccessMethod() { return superAccessMethod; } /** * Mark this member as an access method for a field selection * or method call via the 'super' keyword. */ public final void setIsSuperAccessMethod(boolean b) { superAccessMethod = b; } /** * Tell if this is a final variable without an initializer. * Such variables are subject to definite single assignment. */ public final boolean isBlankFinal() { return isFinal() && !isSynthetic() && getValue() == null; } public boolean isNeverNull() { if (isUplevelValue()) { // loc$x and this$C are never null return !name.toString().startsWith(prefixVal); } return false; } /** * Get the field's final value (may return null) */ public Node getValue(Environment env) throws ClassNotFound { return value; } public final Node getValue() { return value; } public final void setValue(Node value) { this.value = value; } public Object getInitialValue() { return null; } /** * Get the next field or the next match */ public final MemberDefinition getNextMember() { return nextMember; } public final MemberDefinition getNextMatch() { return nextMatch; } /** * Get the field's documentation */ public String getDocumentation() { return documentation; } /** * Request a check of the field definition. */ public void check(Environment env) throws ClassNotFound { } /** * Really check the field definition. */ public Vset check(Environment env, Context ctx, Vset vset) throws ClassNotFound { return vset; } /** * Generate code */ public void code(Environment env, Assembler asm) throws ClassNotFound { throw new CompilerError("code"); } public void codeInit(Environment env, Context ctx, Assembler asm) throws ClassNotFound { throw new CompilerError("codeInit"); } /** * Tells whether to report a deprecation error for this field. */ public boolean reportDeprecated(Environment env) { return (isDeprecated() || clazz.reportDeprecated(env)); } /** * Check if a field can reach another field (only considers * forward references, not the access modifiers). */ public final boolean canReach(Environment env, MemberDefinition f) { if (f.isLocal() || !f.isVariable() || !(isVariable() || isInitializer())) return true; if ((getClassDeclaration().equals(f.getClassDeclaration())) && (isStatic() == f.isStatic())) { // They are located in the same class, and are either both // static or both non-static. Check the initialization order. while (((f = f.getNextMember()) != null) && (f != this)); return f != null; } return true; } //----------------------------------------------------------------- // The code in this section is intended to test certain kinds of // compatibility between methods. There are two kinds of compatibility // that the compiler may need to test. The first is whether one // method can legally override another. The second is whether two // method definitions can legally coexist. We use the word `meet' // to mean the intersection of two legally coexisting methods. // For more information on these kinds of compatibility, see the // comments/code for checkOverride() and checkMeet() below. /** * Constants used by getAccessLevel() to represent the access * modifiers as numbers. */ static final int PUBLIC_ACCESS = 1; static final int PROTECTED_ACCESS = 2; static final int PACKAGE_ACCESS = 3; static final int PRIVATE_ACCESS = 4; /** * Return the access modifier of this member as a number. The idea * is that this number may be used to check properties like "the * access modifier of x is more restrictive than the access * modifier of y" with a simple inequality test: * "x.getAccessLevel() > y.getAccessLevel. * * This is an internal utility method. */ private int getAccessLevel() { // Could just compute this once instead of recomputing. // Check to see if this is worth it. if (isPublic()) { return PUBLIC_ACCESS; } else if (isProtected()) { return PROTECTED_ACCESS; } else if (isPackagePrivate()) { return PACKAGE_ACCESS; } else if (isPrivate()) { return PRIVATE_ACCESS; } else { throw new CompilerError("getAccessLevel()"); } } /** * Munge our error message to report whether the override conflict * came from an inherited method or a declared method. */ private void reportError(Environment env, String errorString, ClassDeclaration clazz, MemberDefinition method) { if (clazz == null) { // For example: // "Instance method BLAH inherited from CLASSBLAH1 cannot be // overridden by the static method declared in CLASSBLAH2." env.error(getWhere(), errorString, this, getClassDeclaration(), method.getClassDeclaration()); } else { // For example: // "In CLASSBLAH1, instance method BLAH inherited from CLASSBLAH2 // cannot be overridden by the static method inherited from // CLASSBLAH3." env.error(clazz.getClassDefinition().getWhere(), //"inherit." + errorString, errorString, //clazz, this, getClassDeclaration(), method.getClassDeclaration()); } } /** * Convenience method to see if two methods return the same type */ public boolean sameReturnType(MemberDefinition method) { // Make sure both are methods. if (!isMethod() || !method.isMethod()) { throw new CompilerError("sameReturnType: not method"); } Type myReturnType = getType().getReturnType(); Type yourReturnType = method.getType().getReturnType(); return (myReturnType == yourReturnType); } /** * Check to see if `this' can override/hide `method'. Caller is * responsible for verifying that `method' has the same signature * as `this'. Caller is also responsible for verifying that * `method' is visible to the class where this override is occurring. * This method is called for the case when class B extends A and both * A and B define some method. * <pre> * A - void foo() throws e1 * | * | * B - void foo() throws e2 * </pre> */ public boolean checkOverride(Environment env, MemberDefinition method) { return checkOverride(env, method, null); } /** * Checks whether `this' can override `method'. It `clazz' is * null, it reports the errors in the class where `this' is * declared. If `clazz' is not null, it reports the error in `clazz'. */ private boolean checkOverride(Environment env, MemberDefinition method, ClassDeclaration clazz) { // This section of code is largely based on section 8.4.6.3 // of the JLS. boolean success = true; // Sanity if (!isMethod()) { throw new CompilerError("checkOverride(), expected method"); } // Suppress checks for synthetic methods, as the compiler presumably // knows what it is doing, e.g., access methods. if (isSynthetic()) { // Sanity check: We generally do not intend for one synthetic // method to override another, though hiding of static members // is expected. This check may need to be changed if new uses // of synthetic methods are devised. // // Query: this code was copied from elsewhere. What // exactly is the role of the !isStatic() in the test? if (method.isFinal() || (!method.isConstructor() && !method.isStatic() && !isStatic())) { //////////////////////////////////////////////////////////// // NMG 2003-01-28 removed the following test because it is // invalidated by bridge methods inserted by the "generic" // (1.5) Java compiler. In 1.5, this code is used, // indirectly, by rmic //////////////////////////////////////////////////////////// // throw new CompilerError("checkOverride() synthetic"); //////////////////////////////////////////////////////////// } // We trust the compiler. (Ha!) We're done checking. return true; } // Our caller should have verified that the method had the // same signature. if (getName() != method.getName() || !getType().equalArguments(method.getType())) { throw new CompilerError("checkOverride(), signature mismatch"); } // It is forbidden to `override' a static method with an instance // method. if (method.isStatic() && !isStatic()) { reportError(env, "override.static.with.instance", clazz, method); success = false; } // It is forbidden to `hide' an instance method with a static // method. if (!method.isStatic() && isStatic()) { reportError(env, "hide.instance.with.static", clazz, method); success = false; } // We cannot override a final method. if (method.isFinal()) { reportError(env, "override.final.method", clazz, method); success = false; } // Give a warning when we override a deprecated method with // a non-deprecated one. // // We bend over backwards to suppress this warning if // the `method' has not been already compiled or // `this' has been already compiled. if (method.reportDeprecated(env) && !isDeprecated() && this instanceof sun.tools.javac.SourceMember) { reportError(env, "warn.override.is.deprecated", clazz, method); } // Visibility may not be more restrictive if (getAccessLevel() > method.getAccessLevel()) { reportError(env, "override.more.restrictive", clazz, method); success = false; } // Return type equality if (!sameReturnType(method)) { //////////////////////////////////////////////////////////// // PCJ 2003-07-30 removed the following error because it is // invalidated by the covariant return type feature of the // 1.5 compiler. The resulting check is now much looser // than the actual 1.5 language spec, but that should be OK // because this code is only still used by rmic. See 4892308. //////////////////////////////////////////////////////////// // reportError(env, "override.different.return", clazz, method); // success = false; //////////////////////////////////////////////////////////// } // Exception agreeement if (!exceptionsFit(env, method)) { reportError(env, "override.incompatible.exceptions", clazz, method); success = false; } return success; } /** * Check to see if two method definitions are compatible, that is * do they have a `meet'. The meet of two methods is essentially * and `intersection' of * two methods. This method is called when some class C inherits * declarations for some method foo from two parents (superclass, * interfaces) but it does not, itself, have a declaration of foo. * Caller is responsible for making sure that both methods are * indeed visible in clazz. * <pre> * A - void foo() throws e1 * \ * \ B void foo() throws e2 * \ / * \ / * C * </pre> */ public boolean checkMeet(Environment env, MemberDefinition method, ClassDeclaration clazz) { // This section of code is largely based on Section 8.4.6 // and 9.4.1 of the JLS. // Sanity if (!isMethod()) { throw new CompilerError("checkMeet(), expected method"); } // Check for both non-abstract. if (!isAbstract() && !method.isAbstract()) { throw new CompilerError("checkMeet(), no abstract method"); } // If either method is non-abstract, then we need to check that // the abstract method can be properly overridden. We call // the checkOverride method to check this and generate any errors. // This test must follow the previous test. else if (!isAbstract()) { return checkOverride(env, method, clazz); } else if (!method.isAbstract()) { return method.checkOverride(env, this, clazz); } // Both methods are abstract. // Our caller should have verified that the method has the // same signature. if (getName() != method.getName() || !getType().equalArguments(method.getType())) { throw new CompilerError("checkMeet(), signature mismatch"); } // Check for return type equality if (!sameReturnType(method)) { // More args? env.error(clazz.getClassDefinition().getWhere(), "meet.different.return", this, this.getClassDeclaration(), method.getClassDeclaration()); return false; } // We don't have to check visibility -- there always // potentially exists a meet. Similarly with exceptions. // There does exist a meet. return true; } /** * This method is meant to be used to determine if one of two inherited * methods could override the other. Unlike checkOverride(), failure * is not an error. This method is only meant to be called after * checkMeet() has succeeded on the two methods. * * If you call couldOverride() without doing a checkMeet() first, then * you are on your own. */ public boolean couldOverride(Environment env, MemberDefinition method) { // Sanity if (!isMethod()) { throw new CompilerError("coulcOverride(), expected method"); } // couldOverride() is only called with `this' and `method' both // being inherited methods. Neither of them is defined in the // class which we are currently working on. Even though an // abstract method defined *in* a class can override a non-abstract // method defined in a superclass, an abstract method inherited // from an interface *never* can override a non-abstract method. // This comment may sound odd, but that's the way inheritance is. // The following check makes sure we aren't trying to override // an inherited non-abstract definition with an abstract definition // from an interface. if (!method.isAbstract()) { return false; } // Visibility should be less restrictive if (getAccessLevel() > method.getAccessLevel()) { return false; } // Exceptions if (!exceptionsFit(env, method)) { return false; } // Potentially some deprecation warnings could be given here // when we merge two abstract methods, one of which is deprecated. // This is not currently reported. return true; } /** * Check to see if the exceptions of `this' fit within the * exceptions of `method'. */ private boolean exceptionsFit(Environment env, MemberDefinition method) { ClassDeclaration e1[] = getExceptions(env); // my exceptions ClassDeclaration e2[] = method.getExceptions(env); // parent's // This code is taken nearly verbatim from the old implementation // of checkOverride() in SourceClass. outer: for (int i = 0 ; i < e1.length ; i++) { try { ClassDefinition c1 = e1[i].getClassDefinition(env); for (int j = 0 ; j < e2.length ; j++) { if (c1.subClassOf(env, e2[j])) { continue outer; } } if (c1.subClassOf(env, env.getClassDeclaration(idJavaLangError))) continue outer; if (c1.subClassOf(env, env.getClassDeclaration(idJavaLangRuntimeException))) continue outer; // the throws was neither something declared by a parent, // nor one of the ignorables. return false; } catch (ClassNotFound ee) { // We were unable to find one of the exceptions. env.error(getWhere(), "class.not.found", ee.name, method.getClassDeclaration()); } } // All of the exceptions `fit'. return true; } //----------------------------------------------------------------- /** * Checks */ public final boolean isPublic() { return (modifiers & M_PUBLIC) != 0; } public final boolean isPrivate() { return (modifiers & M_PRIVATE) != 0; } public final boolean isProtected() { return (modifiers & M_PROTECTED) != 0; } public final boolean isPackagePrivate() { return (modifiers & (M_PUBLIC | M_PRIVATE | M_PROTECTED)) == 0; } public final boolean isFinal() { return (modifiers & M_FINAL) != 0; } public final boolean isStatic() { return (modifiers & M_STATIC) != 0; } public final boolean isSynchronized() { return (modifiers & M_SYNCHRONIZED) != 0; } public final boolean isAbstract() { return (modifiers & M_ABSTRACT) != 0; } public final boolean isNative() { return (modifiers & M_NATIVE) != 0; } public final boolean isVolatile() { return (modifiers & M_VOLATILE) != 0; } public final boolean isTransient() { return (modifiers & M_TRANSIENT) != 0; } public final boolean isMethod() { return type.isType(TC_METHOD); } public final boolean isVariable() { return !type.isType(TC_METHOD) && innerClass == null; } public final boolean isSynthetic() { return (modifiers & M_SYNTHETIC) != 0; } public final boolean isDeprecated() { return (modifiers & M_DEPRECATED) != 0; } public final boolean isStrict() { return (modifiers & M_STRICTFP) != 0; } public final boolean isInnerClass() { return innerClass != null; } public final boolean isInitializer() { return getName().equals(idClassInit); } public final boolean isConstructor() { return getName().equals(idInit); } public boolean isLocal() { return false; } public boolean isInlineable(Environment env, boolean fromFinal) throws ClassNotFound { return (isStatic() || isPrivate() || isFinal() || isConstructor() || fromFinal) && !(isSynchronized() || isNative()); } /** * Check if constant: Will it inline away to a constant? */ public boolean isConstant() { if (isFinal() && isVariable() && value != null) { try { // If an infinite regress requeries this name, // deny that it is a constant. modifiers &= ~M_FINAL; return ((Expression)value).isConstant(); } finally { modifiers |= M_FINAL; } } return false; } /** * toString */ public String toString() { Identifier name = getClassDefinition().getName(); if (isInitializer()) { return isStatic() ? "static {}" : "instance {}"; } else if (isConstructor()) { StringBuffer buf = new StringBuffer(); buf.append(name); buf.append('('); Type argTypes[] = getType().getArgumentTypes(); for (int i = 0 ; i < argTypes.length ; i++) { if (i > 0) { buf.append(','); } buf.append(argTypes[i].toString()); } buf.append(')'); return buf.toString(); } else if (isInnerClass()) { return getInnerClass().toString(); } return type.typeString(getName().toString()); } /** * Print for debugging */ public void print(PrintStream out) { if (isPublic()) { out.print("public "); } if (isPrivate()) { out.print("private "); } if (isProtected()) { out.print("protected "); } if (isFinal()) { out.print("final "); } if (isStatic()) { out.print("static "); } if (isSynchronized()) { out.print("synchronized "); } if (isAbstract()) { out.print("abstract "); } if (isNative()) { out.print("native "); } if (isVolatile()) { out.print("volatile "); } if (isTransient()) { out.print("transient "); } out.println(toString() + ";"); } public void cleanup(Environment env) { documentation = null; if (isMethod() && value != null) { int cost = 0; if (isPrivate() || isInitializer()) { value = Statement.empty; } else if ((cost = ((Statement)value) .costInline(Statement.MAXINLINECOST, null, null)) >= Statement.MAXINLINECOST) { // will never be inlined value = Statement.empty; } else { try { if (!isInlineable(null, true)) { value = Statement.empty; } } catch (ClassNotFound ee) { } } if (value != Statement.empty && env.dump()) { env.output("[after cleanup of " + getName() + ", " + cost + " expression cost units remain]"); } } else if (isVariable()) { if (isPrivate() || !isFinal() || type.isType(TC_ARRAY)) { value = null; } } } }