X-Git-Url: http://git.megacz.com/?a=blobdiff_plain;f=src%2Forg%2Feclipse%2Fjdt%2Finternal%2Fcompiler%2Flookup%2FMethodVerifier.java;fp=src%2Forg%2Feclipse%2Fjdt%2Finternal%2Fcompiler%2Flookup%2FMethodVerifier.java;h=115864cd58f5dae0e7192eec2d56def87410af55;hb=040fa5af2cd00017cf3575950cdaade34a6d7f6c;hp=0000000000000000000000000000000000000000;hpb=a580fb8376d315d05e4d6bfdff9ff1101a151cd6;p=org.ibex.tool.git diff --git a/src/org/eclipse/jdt/internal/compiler/lookup/MethodVerifier.java b/src/org/eclipse/jdt/internal/compiler/lookup/MethodVerifier.java new file mode 100644 index 0000000..115864c --- /dev/null +++ b/src/org/eclipse/jdt/internal/compiler/lookup/MethodVerifier.java @@ -0,0 +1,533 @@ +/******************************************************************************* + * Copyright (c) 2000, 2004 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Common Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/cpl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.jdt.internal.compiler.lookup; + +import org.eclipse.jdt.core.compiler.CharOperation; +import org.eclipse.jdt.internal.compiler.ast.MethodDeclaration; +import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration; +import org.eclipse.jdt.internal.compiler.problem.ProblemReporter; +import org.eclipse.jdt.internal.compiler.util.HashtableOfObject; + +public final class MethodVerifier implements TagBits, TypeConstants { + SourceTypeBinding type; + HashtableOfObject inheritedMethods; + HashtableOfObject currentMethods; + ReferenceBinding runtimeException; + ReferenceBinding errorException; + LookupEnvironment environment; +/* +Binding creation is responsible for reporting all problems with types: + - all modifier problems (duplicates & multiple visibility modifiers + incompatible combinations - abstract/final) + - plus invalid modifiers given the context (the verifier did not do this before) + - qualified name collisions between a type and a package (types in default packages are excluded) + - all type hierarchy problems: + - cycles in the superclass or superinterface hierarchy + - an ambiguous, invisible or missing superclass or superinterface + - extending a final class + - extending an interface instead of a class + - implementing a class instead of an interface + - implementing the same interface more than once (ie. duplicate interfaces) + - with nested types: + - shadowing an enclosing type's source name + - defining a static class or interface inside a non-static nested class + - defining an interface as a local type (local types can only be classes) +*/ +public MethodVerifier(LookupEnvironment environment) { + this.type = null; // Initialized with the public method verify(SourceTypeBinding) + this.inheritedMethods = null; + this.currentMethods = null; + this.runtimeException = null; + this.errorException = null; + this.environment = environment; +} +private boolean areParametersEqual(MethodBinding one, MethodBinding two) { + TypeBinding[] oneArgs = one.parameters; + TypeBinding[] twoArgs = two.parameters; + if (oneArgs == twoArgs) return true; + + int length = oneArgs.length; + if (length != twoArgs.length) return false; + + for (int i = 0; i < length; i++) + if (!areTypesEqual(oneArgs[i], twoArgs[i])) return false; + return true; +} +private boolean areReturnTypesEqual(MethodBinding one, MethodBinding two) { + return areTypesEqual(one.returnType, two.returnType); +} +private boolean areTypesEqual(TypeBinding one, TypeBinding two) { + if (one == two) return true; + if (one instanceof ReferenceBinding && two instanceof ReferenceBinding) + // can compare unresolved to resolved reference bindings + return CharOperation.equals(((ReferenceBinding) one).compoundName, ((ReferenceBinding) two).compoundName); + return false; // all other type bindings are identical +} +private void checkAbstractMethod(MethodBinding abstractMethod) { + if (mustImplementAbstractMethod(abstractMethod)) { + TypeDeclaration typeDeclaration = this.type.scope.referenceContext; + if (typeDeclaration != null) { + MethodDeclaration missingAbstractMethod = typeDeclaration.addMissingAbstractMethodFor(abstractMethod); + missingAbstractMethod.scope.problemReporter().abstractMethodMustBeImplemented(this.type, abstractMethod); + } else { + this.problemReporter().abstractMethodMustBeImplemented(this.type, abstractMethod); + } + } +} +private void checkAgainstInheritedMethods(MethodBinding currentMethod, MethodBinding[] methods, int length) { + nextMethod : for (int i = length; --i >= 0;) { + MethodBinding inheritedMethod = methods[i]; + if (currentMethod.isStatic() != inheritedMethod.isStatic()) { // Cannot override a static method or hide an instance method + this.problemReporter(currentMethod).staticAndInstanceConflict(currentMethod, inheritedMethod); + continue nextMethod; + } + + if (!currentMethod.isAbstract() && inheritedMethod.isAbstract()) { + if ((currentMethod.modifiers & CompilerModifiers.AccOverriding) == 0) + currentMethod.modifiers |= CompilerModifiers.AccImplementing; + } else { + currentMethod.modifiers |= CompilerModifiers.AccOverriding; + } + + if (!areReturnTypesEqual(currentMethod, inheritedMethod)) { + this.problemReporter(currentMethod).incompatibleReturnType(currentMethod, inheritedMethod); + } else { + if (currentMethod.thrownExceptions != NoExceptions) + this.checkExceptions(currentMethod, inheritedMethod); + if (inheritedMethod.isFinal()) + this.problemReporter(currentMethod).finalMethodCannotBeOverridden(currentMethod, inheritedMethod); + if (!this.isAsVisible(currentMethod, inheritedMethod)) + this.problemReporter(currentMethod).visibilityConflict(currentMethod, inheritedMethod); + if (environment.options.reportDeprecationWhenOverridingDeprecatedMethod && inheritedMethod.isViewedAsDeprecated()) { + if (!currentMethod.isViewedAsDeprecated() || environment.options.reportDeprecationInsideDeprecatedCode) { + // check against the other inherited methods to see if they hide this inheritedMethod + ReferenceBinding declaringClass = inheritedMethod.declaringClass; + if (declaringClass.isInterface()) + for (int j = length; --j >= 0;) + if (i != j && methods[j].declaringClass.implementsInterface(declaringClass, false)) + continue nextMethod; + + this.problemReporter(currentMethod).overridesDeprecatedMethod(currentMethod, inheritedMethod); + } + } + } + } +} +/* +"8.4.4" +Verify that newExceptions are all included in inheritedExceptions. +Assumes all exceptions are valid and throwable. +Unchecked exceptions (compatible with runtime & error) are ignored (see the spec on pg. 203). +*/ +private void checkExceptions(MethodBinding newMethod, MethodBinding inheritedMethod) { + ReferenceBinding[] newExceptions = resolvedExceptionTypesFor(newMethod); + ReferenceBinding[] inheritedExceptions = resolvedExceptionTypesFor(inheritedMethod); + for (int i = newExceptions.length; --i >= 0;) { + ReferenceBinding newException = newExceptions[i]; + int j = inheritedExceptions.length; + while (--j > -1 && !this.isSameClassOrSubclassOf(newException, inheritedExceptions[j])){/*empty*/} + if (j == -1) + if (!(newException.isCompatibleWith(this.runtimeException()) || newException.isCompatibleWith(this.errorException()))) + this.problemReporter(newMethod).incompatibleExceptionInThrowsClause(this.type, newMethod, inheritedMethod, newException); + } +} +private void checkInheritedMethods(MethodBinding[] methods, int length) { + MethodBinding first = methods[0]; + int index = length; + while (--index > 0 && areReturnTypesEqual(first, methods[index])){/*empty*/} + if (index > 0) { // All inherited methods do NOT have the same vmSignature + this.problemReporter().inheritedMethodsHaveIncompatibleReturnTypes(this.type, methods, length); + return; + } + + MethodBinding concreteMethod = null; + if (!type.isInterface()) { // ignore concrete methods for interfaces + for (int i = length; --i >= 0;) { // Remember that only one of the methods can be non-abstract + if (!methods[i].isAbstract()) { + concreteMethod = methods[i]; + break; + } + } + } + if (concreteMethod == null) { + if (this.type.isClass() && !this.type.isAbstract()) { + for (int i = length; --i >= 0;) { + if (mustImplementAbstractMethod(methods[i])) { + TypeDeclaration typeDeclaration = this.type.scope.referenceContext; + if (typeDeclaration != null) { + MethodDeclaration missingAbstractMethod = typeDeclaration.addMissingAbstractMethodFor(methods[0]); + missingAbstractMethod.scope.problemReporter().abstractMethodMustBeImplemented(this.type, methods[0]); + } else { + this.problemReporter().abstractMethodMustBeImplemented(this.type, methods[0]); + } + return; + } + } + } + return; + } + + MethodBinding[] abstractMethods = new MethodBinding[length - 1]; + index = 0; + for (int i = length; --i >= 0;) + if (methods[i] != concreteMethod) + abstractMethods[index++] = methods[i]; + + // Remember that interfaces can only define public instance methods + if (concreteMethod.isStatic()) + // Cannot inherit a static method which is specified as an instance method by an interface + this.problemReporter().staticInheritedMethodConflicts(type, concreteMethod, abstractMethods); + if (!concreteMethod.isPublic()) + // Cannot reduce visibility of a public method specified by an interface + this.problemReporter().inheritedMethodReducesVisibility(type, concreteMethod, abstractMethods); + if (concreteMethod.thrownExceptions != NoExceptions) + for (int i = abstractMethods.length; --i >= 0;) + this.checkExceptions(concreteMethod, abstractMethods[i]); +} +/* +For each inherited method identifier (message pattern - vm signature minus the return type) + if current method exists + if current's vm signature does not match an inherited signature then complain + else compare current's exceptions & visibility against each inherited method + else + if inherited methods = 1 + if inherited is abstract && type is NOT an interface or abstract, complain + else + if vm signatures do not match complain + else + find the concrete implementation amongst the abstract methods (can only be 1) + if one exists then + it must be a public instance method + compare concrete's exceptions against each abstract method + else + complain about missing implementation only if type is NOT an interface or abstract +*/ +private void checkMethods() { + boolean mustImplementAbstractMethods = this.type.isClass() && !this.type.isAbstract(); + boolean skipInheritedMethods = mustImplementAbstractMethods && this.type.superInterfaces() == NoSuperInterfaces + && this.type.superclass() != null && !this.type.superclass().isAbstract(); // have a single concrete superclass so only check overridden methods + char[][] methodSelectors = this.inheritedMethods.keyTable; + nextSelector : for (int s = methodSelectors.length; --s >= 0;) { + if (methodSelectors[s] == null) continue nextSelector; + + MethodBinding[] current = (MethodBinding[]) this.currentMethods.get(methodSelectors[s]); + if (current == null && skipInheritedMethods) + continue nextSelector; + + MethodBinding[] inherited = (MethodBinding[]) this.inheritedMethods.valueTable[s]; + if (inherited.length == 1 && current == null) { // handle the common case + if (mustImplementAbstractMethods && inherited[0].isAbstract()) + checkAbstractMethod(inherited[0]); + continue nextSelector; + } + + int index = -1; + MethodBinding[] matchingInherited = new MethodBinding[inherited.length]; + if (current != null) { + for (int i = 0, length1 = current.length; i < length1; i++) { + while (index >= 0) matchingInherited[index--] = null; // clear the previous contents of the matching methods + MethodBinding currentMethod = current[i]; + for (int j = 0, length2 = inherited.length; j < length2; j++) { + MethodBinding inheritedMethod = inherited[j]; + if (inheritedMethod != null && areParametersEqual(currentMethod, inheritedMethod)) { + matchingInherited[++index] = inheritedMethod; + inherited[j] = null; // do not want to find it again + } + } + if (index >= 0) + this.checkAgainstInheritedMethods(currentMethod, matchingInherited, index + 1); // pass in the length of matching + } + } + + for (int i = 0, length = inherited.length; i < length; i++) { + while (index >= 0) matchingInherited[index--] = null; // clear the previous contents of the matching methods + MethodBinding inheritedMethod = inherited[i]; + if (inheritedMethod != null) { + matchingInherited[++index] = inheritedMethod; + for (int j = i + 1; j < length; j++) { + if (inherited[j] != null && areParametersEqual(inheritedMethod, inherited[j])) { + matchingInherited[++index] = inherited[j]; + inherited[j] = null; // do not want to find it again + } + } + } + if (index > 0) + this.checkInheritedMethods(matchingInherited, index + 1); // pass in the length of matching + else if (mustImplementAbstractMethods && index == 0 && matchingInherited[0].isAbstract()) + checkAbstractMethod(matchingInherited[0]); + } + } +} +private void checkPackagePrivateAbstractMethod(MethodBinding abstractMethod) { + ReferenceBinding superType = this.type.superclass(); + char[] selector = abstractMethod.selector; + do { + if (!superType.isValidBinding()) return; + if (!superType.isAbstract()) return; // closer non abstract super type will be flagged instead + + MethodBinding[] methods = superType.getMethods(selector); + nextMethod : for (int m = methods.length; --m >= 0;) { + MethodBinding method = methods[m]; + if (!areReturnTypesEqual(method, abstractMethod) || !areParametersEqual(method, abstractMethod)) + continue nextMethod; + if (method.isPrivate() || method.isConstructor() || method.isDefaultAbstract()) + continue nextMethod; + if (superType.fPackage == abstractMethod.declaringClass.fPackage) return; // found concrete implementation of abstract method in same package + } + } while ((superType = superType.superclass()) != abstractMethod.declaringClass); + + // non visible abstract methods cannot be overridden so the type must be defined abstract + this.problemReporter().abstractMethodCannotBeOverridden(this.type, abstractMethod); +} +/* +Binding creation is responsible for reporting: + - all modifier problems (duplicates & multiple visibility modifiers + incompatible combinations) + - plus invalid modifiers given the context... examples: + - interface methods can only be public + - abstract methods can only be defined by abstract classes + - collisions... 2 methods with identical vmSelectors + - multiple methods with the same message pattern but different return types + - ambiguous, invisible or missing return/argument/exception types + - check the type of any array is not void + - check that each exception type is Throwable or a subclass of it +*/ +private void computeInheritedMethods() { + this.inheritedMethods = new HashtableOfObject(51); // maps method selectors to an array of methods... must search to match paramaters & return type + ReferenceBinding[][] interfacesToVisit = new ReferenceBinding[3][]; + int lastPosition = -1; + ReferenceBinding[] itsInterfaces = type.superInterfaces(); + if (itsInterfaces != NoSuperInterfaces) + interfacesToVisit[++lastPosition] = itsInterfaces; + + ReferenceBinding superType = this.type.isClass() + ? this.type.superclass() + : this.type.scope.getJavaLangObject(); // check interface methods against Object + HashtableOfObject nonVisibleDefaultMethods = new HashtableOfObject(3); // maps method selectors to an array of methods + boolean allSuperclassesAreAbstract = true; + + while (superType != null) { + if (superType.isValidBinding()) { + if (allSuperclassesAreAbstract) { + if (superType.isAbstract()) { + // only need to include superinterfaces if immediate superclasses are abstract + if ((itsInterfaces = superType.superInterfaces()) != NoSuperInterfaces) { + if (++lastPosition == interfacesToVisit.length) + System.arraycopy(interfacesToVisit, 0, interfacesToVisit = new ReferenceBinding[lastPosition * 2][], 0, lastPosition); + interfacesToVisit[lastPosition] = itsInterfaces; + } + } else { + allSuperclassesAreAbstract = false; + } + } + + MethodBinding[] methods = superType.unResolvedMethods(); + nextMethod : for (int m = methods.length; --m >= 0;) { + MethodBinding method = methods[m]; + if (method.isPrivate() || method.isConstructor() || method.isDefaultAbstract()) + continue nextMethod; + MethodBinding[] existingMethods = (MethodBinding[]) this.inheritedMethods.get(method.selector); + if (existingMethods != null) { + for (int i = 0, length = existingMethods.length; i < length; i++) { + if (areReturnTypesEqual(method, existingMethods[i]) && areParametersEqual(method, existingMethods[i])) { + if (method.isDefault() && method.isAbstract() && method.declaringClass.fPackage != type.fPackage) + checkPackagePrivateAbstractMethod(method); + continue nextMethod; + } + } + } + MethodBinding[] nonVisible = (MethodBinding[]) nonVisibleDefaultMethods.get(method.selector); + if (nonVisible != null) + for (int i = 0, l = nonVisible.length; i < l; i++) + if (areReturnTypesEqual(method, nonVisible[i]) && areParametersEqual(method, nonVisible[i])) + continue nextMethod; + + if (!method.isDefault() || method.declaringClass.fPackage == type.fPackage) { + if (existingMethods == null) { + existingMethods = new MethodBinding[] {method}; + } else { + int length = existingMethods.length; + System.arraycopy(existingMethods, 0, existingMethods = new MethodBinding[length + 1], 0, length); + existingMethods[length] = method; + } + this.inheritedMethods.put(method.selector, existingMethods); + } else { + if (nonVisible == null) { + nonVisible = new MethodBinding[] {method}; + } else { + int length = nonVisible.length; + System.arraycopy(nonVisible, 0, nonVisible = new MethodBinding[length + 1], 0, length); + nonVisible[length] = method; + } + nonVisibleDefaultMethods.put(method.selector, nonVisible); + + if (method.isAbstract() && !this.type.isAbstract()) // non visible abstract methods cannot be overridden so the type must be defined abstract + this.problemReporter().abstractMethodCannotBeOverridden(this.type, method); + + MethodBinding[] current = (MethodBinding[]) this.currentMethods.get(method.selector); + if (current != null) { // non visible methods cannot be overridden so a warning is issued + foundMatch : for (int i = 0, length = current.length; i < length; i++) { + if (areReturnTypesEqual(method, current[i]) && areParametersEqual(method, current[i])) { + this.problemReporter().overridesPackageDefaultMethod(current[i], method); + break foundMatch; + } + } + } + } + } + superType = superType.superclass(); + } + } + + for (int i = 0; i <= lastPosition; i++) { + ReferenceBinding[] interfaces = interfacesToVisit[i]; + for (int j = 0, l = interfaces.length; j < l; j++) { + superType = interfaces[j]; + if ((superType.tagBits & InterfaceVisited) == 0) { + superType.tagBits |= InterfaceVisited; + if (superType.isValidBinding()) { + if ((itsInterfaces = superType.superInterfaces()) != NoSuperInterfaces) { + if (++lastPosition == interfacesToVisit.length) + System.arraycopy(interfacesToVisit, 0, interfacesToVisit = new ReferenceBinding[lastPosition * 2][], 0, lastPosition); + interfacesToVisit[lastPosition] = itsInterfaces; + } + + MethodBinding[] methods = superType.unResolvedMethods(); + nextMethod : for (int m = methods.length; --m >= 0;) { // Interface methods are all abstract public + MethodBinding method = methods[m]; + MethodBinding[] existingMethods = (MethodBinding[]) this.inheritedMethods.get(method.selector); + if (existingMethods == null) { + existingMethods = new MethodBinding[] {method}; + } else { + int length = existingMethods.length; + for (int e = 0; e < length; e++) { + MethodBinding existing = existingMethods[e]; + if (areParametersEqual(method, existing) && existing.declaringClass.implementsInterface(superType, true)) + continue nextMethod; // skip interface method with the same signature if visible to its declaringClass + } + System.arraycopy(existingMethods, 0, existingMethods = new MethodBinding[length + 1], 0, length); + existingMethods[length] = method; + } + this.inheritedMethods.put(method.selector, existingMethods); + } + } + } + } + } + + // bit reinitialization + for (int i = 0; i <= lastPosition; i++) { + ReferenceBinding[] interfaces = interfacesToVisit[i]; + for (int j = 0, length = interfaces.length; j < length; j++) + interfaces[j].tagBits &= ~InterfaceVisited; + } +} +private void computeMethods() { + MethodBinding[] methods = type.methods(); + int size = methods.length; + this.currentMethods = new HashtableOfObject(size == 0 ? 1 : size); // maps method selectors to an array of methods... must search to match paramaters & return type + for (int m = size; --m >= 0;) { + MethodBinding method = methods[m]; + if (!(method.isConstructor() || method.isDefaultAbstract())) { // keep all methods which are NOT constructors or default abstract + MethodBinding[] existingMethods = (MethodBinding[]) this.currentMethods.get(method.selector); + if (existingMethods == null) + existingMethods = new MethodBinding[1]; + else + System.arraycopy(existingMethods, 0, + (existingMethods = new MethodBinding[existingMethods.length + 1]), 0, existingMethods.length - 1); + existingMethods[existingMethods.length - 1] = method; + this.currentMethods.put(method.selector, existingMethods); + } + } +} +private ReferenceBinding errorException() { + if (errorException == null) + this.errorException = this.type.scope.getJavaLangError(); + return errorException; +} +private boolean isAsVisible(MethodBinding newMethod, MethodBinding inheritedMethod) { + if (inheritedMethod.modifiers == newMethod.modifiers) return true; + + if (newMethod.isPublic()) return true; // Covers everything + if (inheritedMethod.isPublic()) return false; + + if (newMethod.isProtected()) return true; + if (inheritedMethod.isProtected()) return false; + + return !newMethod.isPrivate(); // The inheritedMethod cannot be private since it would not be visible +} +private boolean isSameClassOrSubclassOf(ReferenceBinding testClass, ReferenceBinding superclass) { + do { + if (testClass == superclass) return true; + } while ((testClass = testClass.superclass()) != null); + return false; +} +private boolean mustImplementAbstractMethod(MethodBinding abstractMethod) { + // if the type's superclass is an abstract class, then all abstract methods must be implemented + // otherwise, skip it if the type's superclass must implement any of the inherited methods + ReferenceBinding superclass = this.type.superclass(); + ReferenceBinding declaringClass = abstractMethod.declaringClass; + if (declaringClass.isClass()) { + while (superclass.isAbstract() && superclass != declaringClass) + superclass = superclass.superclass(); // find the first concrete superclass or the abstract declaringClass + } else { + if (this.type.implementsInterface(declaringClass, false)) { + if (this.type.isAbstract()) return false; // leave it for the subclasses + if (!superclass.implementsInterface(declaringClass, true)) // only if a superclass does not also implement the interface + return true; + } + while (superclass.isAbstract() && !superclass.implementsInterface(declaringClass, false)) + superclass = superclass.superclass(); // find the first concrete superclass or the superclass which implements the interface + } + return superclass.isAbstract(); // if it is a concrete class then we have already reported problem against it +} +private ProblemReporter problemReporter() { + return this.type.scope.problemReporter(); +} +private ProblemReporter problemReporter(MethodBinding currentMethod) { + ProblemReporter reporter = problemReporter(); + if (currentMethod.declaringClass == type) // only report against the currentMethod if its implemented by the type + reporter.referenceContext = currentMethod.sourceMethod(); + return reporter; +} +ReferenceBinding[] resolvedExceptionTypesFor(MethodBinding method) { + ReferenceBinding[] exceptions = method.thrownExceptions; + if ((method.modifiers & CompilerModifiers.AccUnresolved) == 0) + return exceptions; + + if (!(method.declaringClass instanceof BinaryTypeBinding)) + return TypeConstants.NoExceptions; // safety check + BinaryTypeBinding binaryType = (BinaryTypeBinding) method.declaringClass; + + for (int i = exceptions.length; --i >= 0;) + if (exceptions[i] instanceof UnresolvedReferenceBinding) + exceptions[i] = (ReferenceBinding) binaryType.resolveType(exceptions[i]); + return exceptions; +} +private ReferenceBinding runtimeException() { + if (runtimeException == null) + this.runtimeException = this.type.scope.getJavaLangRuntimeException(); + return runtimeException; +} +public void verify(SourceTypeBinding someType) { + this.type = someType; + this.computeMethods(); + this.computeInheritedMethods(); + this.checkMethods(); +} +public String toString() { + StringBuffer buffer = new StringBuffer(10); + buffer.append("MethodVerifier for type: "); //$NON-NLS-1$ + buffer.append(type.readableName()); + buffer.append('\n'); + buffer.append("\t-inherited methods: "); //$NON-NLS-1$ + buffer.append(this.inheritedMethods); + return buffer.toString(); +} +}