/******************************************************************************* * Copyright (c) 2000, 2011 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation * Benjamin Muskalla - Contribution for bug 239066 *******************************************************************************/ package org.eclipse.che.ide.ext.java.jdt.internal.compiler.lookup; import org.eclipse.che.ide.ext.java.jdt.internal.compiler.ClassFileConstants; import org.eclipse.che.ide.ext.java.jdt.internal.compiler.ast.MethodDeclaration; import org.eclipse.che.ide.ext.java.jdt.internal.compiler.ast.TypeDeclaration; import org.eclipse.che.ide.ext.java.jdt.internal.compiler.ast.TypeReference; import org.eclipse.che.ide.ext.java.jdt.internal.compiler.impl.CompilerOptions; import org.eclipse.che.ide.ext.java.jdt.internal.compiler.problem.ProblemReporter; import org.eclipse.che.ide.ext.java.jdt.internal.compiler.util.HashtableOfObject; import org.eclipse.che.ide.ext.java.jdt.internal.compiler.util.SimpleSet; import org.eclipse.che.ide.ext.java.jdt.internal.compiler.util.Util; public class MethodVerifier { SourceTypeBinding type; HashtableOfObject inheritedMethods; HashtableOfObject currentMethods; LookupEnvironment environment; private boolean allowCompatibleReturnTypes; /* * 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 (i.e. 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) */ MethodVerifier(LookupEnvironment environment) { this.type = null; // Initialized with the public method verify(SourceTypeBinding) this.inheritedMethods = null; this.currentMethods = null; this.environment = environment; this.allowCompatibleReturnTypes = environment.globalOptions.complianceLevel >= ClassFileConstants.JDK1_5 && environment.globalOptions.sourceLevel < ClassFileConstants.JDK1_5; } boolean areMethodsCompatible(MethodBinding one, MethodBinding two) { return isParameterSubsignature(one, two) && areReturnTypesCompatible(one, two); } 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; } boolean areReturnTypesCompatible(MethodBinding one, MethodBinding two) { if (one.returnType == two.returnType) return true; if (areTypesEqual(one.returnType, two.returnType)) return true; // when sourceLevel < 1.5 but compliance >= 1.5, allow return types in binaries to be compatible instead of just equal if (this.allowCompatibleReturnTypes && one.declaringClass instanceof BinaryTypeBinding && two.declaringClass instanceof BinaryTypeBinding) { return areReturnTypesCompatible0(one, two); } return false; } boolean areReturnTypesCompatible0(MethodBinding one, MethodBinding two) { // short is compatible with int, but as far as covariance is concerned, its not if (one.returnType.isBaseType()) return false; if (!one.declaringClass.isInterface() && one.declaringClass.id == TypeIds.T_JavaLangObject) return two.returnType.isCompatibleWith(one.returnType); // interface methods inherit from Object return one.returnType.isCompatibleWith(two.returnType); } boolean areTypesEqual(TypeBinding one, TypeBinding two) { if (one == two) return true; // its possible that an UnresolvedReferenceBinding can be compared to its resolved type // when they're both UnresolvedReferenceBindings then they must be identical like all other types // all wrappers of UnresolvedReferenceBindings are converted as soon as the type is resolved // so its not possible to have 2 arrays where one is UnresolvedX[] and the other is X[] if (one instanceof UnresolvedReferenceBinding) return ((UnresolvedReferenceBinding)one).resolvedType == two; if (two instanceof UnresolvedReferenceBinding) return ((UnresolvedReferenceBinding)two).resolvedType == one; return false; // all other type bindings are identical } boolean canSkipInheritedMethods() { if (this.type.superclass() != null && this.type.superclass().isAbstract()) return false; return this.type.superInterfaces() == Binding.NO_SUPERINTERFACES; } boolean canSkipInheritedMethods(MethodBinding one, MethodBinding two) { return two == null // already know one is not null || one.declaringClass == two.declaringClass; } void checkAbstractMethod(MethodBinding abstractMethod) { if (mustImplementAbstractMethod(abstractMethod.declaringClass)) { TypeDeclaration typeDeclaration = this.type.scope.referenceContext; if (typeDeclaration != null) { MethodDeclaration missingAbstractMethod = typeDeclaration.addMissingAbstractMethodFor(abstractMethod); missingAbstractMethod.scope.problemReporter().abstractMethodMustBeImplemented(this.type, abstractMethod); } else { problemReporter().abstractMethodMustBeImplemented(this.type, abstractMethod); } } } void checkAgainstInheritedMethods(MethodBinding currentMethod, MethodBinding[] methods, int length, MethodBinding[] allInheritedMethods) { if (this.type.isAnnotationType()) { // annotation cannot override any method problemReporter().annotationCannotOverrideMethod(currentMethod, methods[length - 1]); return; // do not repoort against subsequent inherited methods } CompilerOptions options = this.type.scope.compilerOptions(); // need to find the overridden methods to avoid blaming this type for issues which are already reported against a supertype // but cannot ignore an overridden inherited method completely when it comes to checking for bridge methods int[] overriddenInheritedMethods = length > 1 ? findOverriddenInheritedMethods(methods, length) : null; nextMethod: for (int i = length; --i >= 0; ) { MethodBinding inheritedMethod = methods[i]; if (overriddenInheritedMethods == null || overriddenInheritedMethods[i] == 0) { if (currentMethod.isStatic() != inheritedMethod.isStatic()) { // Cannot override a static method or hide an instance method problemReporter(currentMethod).staticAndInstanceConflict(currentMethod, inheritedMethod); continue nextMethod; } // want to tag currentMethod even if return types are not equal if (inheritedMethod.isAbstract()) { if (inheritedMethod.declaringClass.isInterface()) { currentMethod.modifiers |= ExtraCompilerModifiers.AccImplementing; } else { currentMethod.modifiers |= ExtraCompilerModifiers.AccImplementing | ExtraCompilerModifiers.AccOverriding; } // with the above change an abstract method is tagged as implementing the inherited abstract method // if (!currentMethod.isAbstract() && inheritedMethod.isAbstract()) { // if ((currentMethod.modifiers & CompilerModifiers.AccOverriding) == 0) // currentMethod.modifiers |= CompilerModifiers.AccImplementing; } else if (inheritedMethod.isPublic() || !this.type.isInterface()) { // interface I { @Override Object clone(); } does not override Object#clone() currentMethod.modifiers |= ExtraCompilerModifiers.AccOverriding; } if (!areReturnTypesCompatible(currentMethod, inheritedMethod) && (currentMethod.returnType.tagBits & TagBits.HasMissingType) == 0) { if (reportIncompatibleReturnTypeError(currentMethod, inheritedMethod)) continue nextMethod; } reportRawReferences(currentMethod, inheritedMethod); // if they were deferred, emit them now. if (currentMethod.thrownExceptions != Binding.NO_EXCEPTIONS) checkExceptions(currentMethod, inheritedMethod); if (inheritedMethod.isFinal()) problemReporter(currentMethod).finalMethodCannotBeOverridden(currentMethod, inheritedMethod); if (!isAsVisible(currentMethod, inheritedMethod)) problemReporter(currentMethod).visibilityConflict(currentMethod, inheritedMethod); if (inheritedMethod.isSynchronized() && !currentMethod.isSynchronized()) { problemReporter(currentMethod).missingSynchronizedOnInheritedMethod(currentMethod, inheritedMethod); } if (options.reportDeprecationWhenOverridingDeprecatedMethod && inheritedMethod.isViewedAsDeprecated()) { if (!currentMethod.isViewedAsDeprecated() || 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; problemReporter(currentMethod).overridesDeprecatedMethod(currentMethod, inheritedMethod); } } } checkForBridgeMethod(currentMethod, inheritedMethod, allInheritedMethods); } } public void reportRawReferences(MethodBinding currentMethod, MethodBinding inheritedMethod) { // nothing to do here. Real action happens at 1.5+ } void checkConcreteInheritedMethod(MethodBinding concreteMethod, MethodBinding[] abstractMethods) { // 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 problemReporter().staticInheritedMethodConflicts(this.type, concreteMethod, abstractMethods); if (!concreteMethod.isPublic()) { int index = 0, length = abstractMethods.length; if (concreteMethod.isProtected()) { for (; index < length; index++) if (abstractMethods[index].isPublic()) break; } else if (concreteMethod.isDefault()) { for (; index < length; index++) if (!abstractMethods[index].isDefault()) break; } if (index < length) problemReporter().inheritedMethodReducesVisibility(this.type, concreteMethod, abstractMethods); } if (concreteMethod.thrownExceptions != Binding.NO_EXCEPTIONS) for (int i = abstractMethods.length; --i >= 0; ) checkExceptions(concreteMethod, abstractMethods[i]); // A subclass inheriting this method and putting it up as the implementation to meet its own // obligations should qualify as a use. if (concreteMethod.isOrEnclosedByPrivateType()) concreteMethod.original().modifiers |= ExtraCompilerModifiers.AccLocallyUsed; } /* * "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). */ 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 && !isSameClassOrSubclassOf(newException, inheritedExceptions[j])) {/* empty */ } if (j == -1) if (!newException.isUncheckedException(false) && (newException.tagBits & TagBits.HasMissingType) == 0) { problemReporter(newMethod).incompatibleExceptionInThrowsClause(this.type, newMethod, inheritedMethod, newException); } } } void checkForBridgeMethod(MethodBinding currentMethod, MethodBinding inheritedMethod, MethodBinding[] allInheritedMethods) { // no op before 1.5 } void checkForMissingHashCodeMethod() { MethodBinding[] choices = this.type.getMethods(TypeConstants.EQUALS); boolean overridesEquals = false; for (int i = choices.length; !overridesEquals && --i >= 0; ) overridesEquals = choices[i].parameters.length == 1 && choices[i].parameters[0].id == TypeIds.T_JavaLangObject; if (overridesEquals) { MethodBinding hashCodeMethod = this.type.getExactMethod(TypeConstants.HASHCODE, Binding.NO_PARAMETERS, null); if (hashCodeMethod != null && hashCodeMethod.declaringClass.id == TypeIds.T_JavaLangObject) this.problemReporter().shouldImplementHashcode(this.type); } } void checkForRedundantSuperinterfaces(ReferenceBinding superclass, ReferenceBinding[] superInterfaces) { if (superInterfaces == Binding.NO_SUPERINTERFACES) return; SimpleSet interfacesToCheck = new SimpleSet(superInterfaces.length); SimpleSet redundantInterfaces = null; // bark but once. for (int i = 0, l = superInterfaces.length; i < l; i++) { ReferenceBinding toCheck = superInterfaces[i]; for (int j = 0; j < l; j++) { ReferenceBinding implementedInterface = superInterfaces[j]; if (i != j && toCheck.implementsInterface(implementedInterface, true)) { if (redundantInterfaces == null) { redundantInterfaces = new SimpleSet(3); } else if (redundantInterfaces.includes(implementedInterface)) { continue; } redundantInterfaces.add(implementedInterface); TypeReference[] refs = this.type.scope.referenceContext.superInterfaces; for (int r = 0, rl = refs.length; r < rl; r++) { if (refs[r].resolvedType == toCheck) { problemReporter().redundantSuperInterface(this.type, refs[j], implementedInterface, toCheck); break; // https://bugs.eclipse.org/bugs/show_bug.cgi?id=320911 } } } } interfacesToCheck.add(toCheck); } ReferenceBinding[] itsInterfaces = null; SimpleSet inheritedInterfaces = new SimpleSet(5); ReferenceBinding superType = superclass; while (superType != null && superType.isValidBinding()) { if ((itsInterfaces = superType.superInterfaces()) != Binding.NO_SUPERINTERFACES) { for (int i = 0, l = itsInterfaces.length; i < l; i++) { ReferenceBinding inheritedInterface = itsInterfaces[i]; if (!inheritedInterfaces.includes(inheritedInterface) && inheritedInterface.isValidBinding()) { if (interfacesToCheck.includes(inheritedInterface)) { if (redundantInterfaces == null) { redundantInterfaces = new SimpleSet(3); } else if (redundantInterfaces.includes(inheritedInterface)) { continue; } redundantInterfaces.add(inheritedInterface); TypeReference[] refs = this.type.scope.referenceContext.superInterfaces; for (int r = 0, rl = refs.length; r < rl; r++) { if (refs[r].resolvedType == inheritedInterface) { problemReporter().redundantSuperInterface(this.type, refs[r], inheritedInterface, superType); break; } } } else { inheritedInterfaces.add(inheritedInterface); } } } } superType = superType.superclass(); } int nextPosition = inheritedInterfaces.size(); if (nextPosition == 0) return; ReferenceBinding[] interfacesToVisit = new ReferenceBinding[nextPosition]; inheritedInterfaces.asArray(interfacesToVisit); for (int i = 0; i < nextPosition; i++) { superType = interfacesToVisit[i]; if ((itsInterfaces = superType.superInterfaces()) != Binding.NO_SUPERINTERFACES) { int itsLength = itsInterfaces.length; if (nextPosition + itsLength >= interfacesToVisit.length) System.arraycopy(interfacesToVisit, 0, interfacesToVisit = new ReferenceBinding[nextPosition + itsLength + 5], 0, nextPosition); for (int a = 0; a < itsLength; a++) { ReferenceBinding inheritedInterface = itsInterfaces[a]; if (!inheritedInterfaces.includes(inheritedInterface) && inheritedInterface.isValidBinding()) { if (interfacesToCheck.includes(inheritedInterface)) { if (redundantInterfaces == null) { redundantInterfaces = new SimpleSet(3); } else if (redundantInterfaces.includes(inheritedInterface)) { continue; } redundantInterfaces.add(inheritedInterface); TypeReference[] refs = this.type.scope.referenceContext.superInterfaces; for (int r = 0, rl = refs.length; r < rl; r++) { if (refs[r].resolvedType == inheritedInterface) { problemReporter().redundantSuperInterface(this.type, refs[r], inheritedInterface, superType); break; } } } else { inheritedInterfaces.add(inheritedInterface); interfacesToVisit[nextPosition++] = inheritedInterface; } } } } } } void checkInheritedMethods(MethodBinding[] methods, int length) { /* * 1. find concrete method 2. if it doesn't exist then find first inherited abstract method whose return type is compatible * with all others if no such method exists then report incompatible return type error otherwise report abstract method must * be implemented 3. if concrete method exists, check to see if its return type is compatible with all others if it is then * check concrete method against abstract methods if its not, then find most specific abstract method & report abstract * method must be implemented since concrete method is insufficient if no most specific return type abstract method exists, * then report incompatible return type with all inherited methods */ MethodBinding concreteMethod = this.type.isInterface() || methods[0].isAbstract() ? null : methods[0]; if (concreteMethod == null) { MethodBinding bestAbstractMethod = length == 1 ? methods[0] : findBestInheritedAbstractMethod(methods, length); boolean noMatch = bestAbstractMethod == null; if (noMatch) bestAbstractMethod = methods[0]; if (mustImplementAbstractMethod(bestAbstractMethod.declaringClass)) { TypeDeclaration typeDeclaration = this.type.scope.referenceContext; MethodBinding superclassAbstractMethod = methods[0]; if (superclassAbstractMethod == bestAbstractMethod || superclassAbstractMethod.declaringClass.isInterface()) { if (typeDeclaration != null) { MethodDeclaration missingAbstractMethod = typeDeclaration.addMissingAbstractMethodFor(bestAbstractMethod); missingAbstractMethod.scope.problemReporter().abstractMethodMustBeImplemented(this.type, bestAbstractMethod); } else { problemReporter().abstractMethodMustBeImplemented(this.type, bestAbstractMethod); } } else { if (typeDeclaration != null) { MethodDeclaration missingAbstractMethod = typeDeclaration.addMissingAbstractMethodFor(bestAbstractMethod); missingAbstractMethod.scope.problemReporter().abstractMethodMustBeImplemented(this.type, bestAbstractMethod, superclassAbstractMethod); } else { problemReporter().abstractMethodMustBeImplemented(this.type, bestAbstractMethod, superclassAbstractMethod); } } } else if (noMatch) { problemReporter().inheritedMethodsHaveIncompatibleReturnTypes(this.type, methods, length); } return; } if (length < 2) return; // nothing else to check int index = length; while (--index > 0 && checkInheritedReturnTypes(concreteMethod, methods[index])) {/* empty */ } if (index > 0) { // concreteMethod is not the best match MethodBinding bestAbstractMethod = findBestInheritedAbstractMethod(methods, length); if (bestAbstractMethod == null) problemReporter().inheritedMethodsHaveIncompatibleReturnTypes(this.type, methods, length); else // can only happen in >= 1.5 since return types must be equal prior to 1.5 problemReporter().abstractMethodMustBeImplemented(this.type, bestAbstractMethod, concreteMethod); return; } MethodBinding[] abstractMethods = new MethodBinding[length - 1]; index = 0; for (int i = 0; i < length; i++) if (methods[i].isAbstract()) abstractMethods[index++] = methods[i]; if (index == 0) return; // can happen with methods that contain 'equal' Missing Types, see bug 257384 if (index < abstractMethods.length) System.arraycopy(abstractMethods, 0, abstractMethods = new MethodBinding[index], 0, index); checkConcreteInheritedMethod(concreteMethod, abstractMethods); } boolean checkInheritedReturnTypes(MethodBinding method, MethodBinding otherMethod) { if (areReturnTypesCompatible(method, otherMethod)) return true; if (!this.type.isInterface()) if (method.declaringClass.isClass() || !this.type.implementsInterface(method.declaringClass, false)) if (otherMethod.declaringClass.isClass() || !this.type.implementsInterface(otherMethod.declaringClass, false)) return true; // do not complain since the superclass already got blamed return false; } /* * 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 */ void checkMethods() { boolean mustImplementAbstractMethods = mustImplementAbstractMethods(); boolean skipInheritedMethods = mustImplementAbstractMethods && canSkipInheritedMethods(); // have a single concrete // superclass so only check // overridden methods boolean isOrEnclosedByPrivateType = this.type.isOrEnclosedByPrivateType(); char[][] methodSelectors = Util.toArrays(this.inheritedMethods.getKeys()); nextSelector: for (int s = methodSelectors.length; --s >= 0; ) { if (methodSelectors[s] == null) continue nextSelector; MethodBinding[] current = (MethodBinding[])this.currentMethods.get(methodSelectors[s]); MethodBinding[] inherited = (MethodBinding[])this.inheritedMethods.getArrayValues()[s]; // https://bugs.eclipse.org/bugs/show_bug.cgi?id=296660, if current type is exposed, // inherited methods of super classes are too. current != null case handled below. if (current == null && !isOrEnclosedByPrivateType) { int length = inherited.length; for (int i = 0; i < length; i++) { inherited[i].original().modifiers |= ExtraCompilerModifiers.AccLocallyUsed; } } if (current == null && skipInheritedMethods) continue nextSelector; 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++) { MethodBinding currentMethod = current[i]; for (int j = 0, length2 = inherited.length; j < length2; j++) { MethodBinding inheritedMethod = computeSubstituteMethod(inherited[j], currentMethod); if (inheritedMethod != null) { if (isParameterSubsignature(currentMethod, inheritedMethod)) { matchingInherited[++index] = inheritedMethod; inherited[j] = null; // do not want to find it again } } } if (index >= 0) { checkAgainstInheritedMethods(currentMethod, matchingInherited, index + 1, inherited); // pass in the length of // matching while (index >= 0) matchingInherited[index--] = null; // clear the contents of the matching methods } } } for (int i = 0, length = inherited.length; i < length; i++) { MethodBinding inheritedMethod = inherited[i]; if (inheritedMethod == null) continue; // https://bugs.eclipse.org/bugs/show_bug.cgi?id=296660, if current type is exposed, // inherited methods of super classes are too. current == null case handled already. if (!isOrEnclosedByPrivateType && current != null) { inheritedMethod.original().modifiers |= ExtraCompilerModifiers.AccLocallyUsed; } matchingInherited[++index] = inheritedMethod; for (int j = i + 1; j < length; j++) { MethodBinding otherInheritedMethod = inherited[j]; if (canSkipInheritedMethods(inheritedMethod, otherInheritedMethod)) continue; otherInheritedMethod = computeSubstituteMethod(otherInheritedMethod, inheritedMethod); if (otherInheritedMethod != null) { if (isParameterSubsignature(inheritedMethod, otherInheritedMethod)) { matchingInherited[++index] = otherInheritedMethod; inherited[j] = null; // do not want to find it again } } } if (index == -1) continue; if (index > 0) checkInheritedMethods(matchingInherited, index + 1); // pass in the length of matching else if (mustImplementAbstractMethods && matchingInherited[0].isAbstract()) checkAbstractMethod(matchingInherited[0]); while (index >= 0) matchingInherited[index--] = null; // clear the contents of the matching methods } } } void checkPackagePrivateAbstractMethod(MethodBinding abstractMethod) { // check that the inherited abstract method (package private visibility) is implemented within the same package PackageBinding necessaryPackage = abstractMethod.declaringClass.fPackage; if (necessaryPackage == this.type.fPackage) return; // not a problem 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 if (necessaryPackage == superType.fPackage) { MethodBinding[] methods = superType.getMethods(selector); nextMethod: for (int m = methods.length; --m >= 0; ) { MethodBinding method = methods[m]; if (method.isPrivate() || method.isConstructor() || method.isDefaultAbstract()) continue nextMethod; if (areMethodsCompatible(method, abstractMethod)) 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 problemReporter().abstractMethodCannotBeOverridden(this.type, abstractMethod); } void computeInheritedMethods() { ReferenceBinding superclass = this.type.isInterface() ? this.type.scope.getJavaLangObject() // check interface methods // against Object : this.type.superclass(); // class or enum computeInheritedMethods(superclass, this.type.superInterfaces()); checkForRedundantSuperinterfaces(superclass, this.type.superInterfaces()); } /* * 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 */ void computeInheritedMethods(ReferenceBinding superclass, ReferenceBinding[] superInterfaces) { // only want to remember inheritedMethods that can have an impact on the current type // if an inheritedMethod has been 'replaced' by a supertype's method then skip it, however // see usage of canOverridingMethodDifferInErasure below. this.inheritedMethods = new HashtableOfObject(51); // maps method selectors to an array of methods... must search to match // paramaters & return type ReferenceBinding[] interfacesToVisit = null; int nextPosition = 0; ReferenceBinding[] itsInterfaces = superInterfaces; if (itsInterfaces != Binding.NO_SUPERINTERFACES) { nextPosition = itsInterfaces.length; interfacesToVisit = itsInterfaces; } ReferenceBinding superType = superclass; HashtableOfObject nonVisibleDefaultMethods = new HashtableOfObject(3); // maps method selectors to an array of methods while (superType != null && superType.isValidBinding()) { // We used to only include superinterfaces if immediate superclasses are abstract // but that is problematic. See https://bugs.eclipse.org/bugs/show_bug.cgi?id=302358 if ((itsInterfaces = superType.superInterfaces()) != Binding.NO_SUPERINTERFACES) { if (interfacesToVisit == null) { interfacesToVisit = itsInterfaces; nextPosition = interfacesToVisit.length; } else { int itsLength = itsInterfaces.length; if (nextPosition + itsLength >= interfacesToVisit.length) System.arraycopy(interfacesToVisit, 0, interfacesToVisit = new ReferenceBinding[nextPosition + itsLength + 5], 0, nextPosition); nextInterface: for (int a = 0; a < itsLength; a++) { ReferenceBinding next = itsInterfaces[a]; for (int b = 0; b < nextPosition; b++) if (next == interfacesToVisit[b]) continue nextInterface; interfacesToVisit[nextPosition++] = next; } } } MethodBinding[] methods = superType.unResolvedMethods(); nextMethod: for (int m = methods.length; --m >= 0; ) { MethodBinding inheritedMethod = methods[m]; if (inheritedMethod.isPrivate() || inheritedMethod.isConstructor() || inheritedMethod.isDefaultAbstract()) continue nextMethod; MethodBinding[] existingMethods = (MethodBinding[])this.inheritedMethods.get(inheritedMethod.selector); if (existingMethods != null) { existing: for (int i = 0, length = existingMethods.length; i < length; i++) { MethodBinding existingMethod = existingMethods[i]; // https://bugs.eclipse.org/bugs/show_bug.cgi?id=302358, skip inherited method only if any overriding version // in a subclass is guaranteed to have the same erasure as an existing method. if (existingMethod.declaringClass != inheritedMethod.declaringClass && areMethodsCompatible(existingMethod, inheritedMethod) && !canOverridingMethodDifferInErasure(existingMethod, inheritedMethod)) { if (inheritedMethod.isDefault()) { if (inheritedMethod.isAbstract()) { checkPackagePrivateAbstractMethod(inheritedMethod); } else if (existingMethod.declaringClass.fPackage != inheritedMethod.declaringClass.fPackage) { if (this.type.fPackage == inheritedMethod.declaringClass.fPackage && !areReturnTypesCompatible(inheritedMethod, existingMethod)) continue existing; // may need to record incompatible return type } } continue nextMethod; } } } if (!inheritedMethod.isDefault() || inheritedMethod.declaringClass.fPackage == this.type.fPackage) { if (existingMethods == null) { existingMethods = new MethodBinding[]{inheritedMethod}; } else { int length = existingMethods.length; System.arraycopy(existingMethods, 0, existingMethods = new MethodBinding[length + 1], 0, length); existingMethods[length] = inheritedMethod; } this.inheritedMethods.put(inheritedMethod.selector, existingMethods); } else { MethodBinding[] nonVisible = (MethodBinding[])nonVisibleDefaultMethods.get(inheritedMethod.selector); if (nonVisible != null) for (int i = 0, l = nonVisible.length; i < l; i++) if (areMethodsCompatible(nonVisible[i], inheritedMethod)) continue nextMethod; if (nonVisible == null) { nonVisible = new MethodBinding[]{inheritedMethod}; } else { int length = nonVisible.length; System.arraycopy(nonVisible, 0, nonVisible = new MethodBinding[length + 1], 0, length); nonVisible[length] = inheritedMethod; } nonVisibleDefaultMethods.put(inheritedMethod.selector, nonVisible); if (inheritedMethod.isAbstract() && !this.type.isAbstract()) // non visible abstract methods cannot be overridden // so the type must be defined abstract problemReporter().abstractMethodCannotBeOverridden(this.type, inheritedMethod); MethodBinding[] current = (MethodBinding[])this.currentMethods.get(inheritedMethod.selector); if (current != null && !inheritedMethod.isStatic()) { // non visible methods cannot be overridden so a warning is issued foundMatch: for (int i = 0, length = current.length; i < length; i++) { if (!current[i].isStatic() && areMethodsCompatible(current[i], inheritedMethod)) { problemReporter().overridesPackageDefaultMethod(current[i], inheritedMethod); break foundMatch; } } } } } superType = superType.superclass(); } if (nextPosition == 0) return; SimpleSet skip = findSuperinterfaceCollisions(superclass, superInterfaces); for (int i = 0; i < nextPosition; i++) { superType = interfacesToVisit[i]; if (superType.isValidBinding()) { if (skip != null && skip.includes(superType)) continue; if ((itsInterfaces = superType.superInterfaces()) != Binding.NO_SUPERINTERFACES) { int itsLength = itsInterfaces.length; if (nextPosition + itsLength >= interfacesToVisit.length) System.arraycopy(interfacesToVisit, 0, interfacesToVisit = new ReferenceBinding[nextPosition + itsLength + 5], 0, nextPosition); nextInterface: for (int a = 0; a < itsLength; a++) { ReferenceBinding next = itsInterfaces[a]; for (int b = 0; b < nextPosition; b++) if (next == interfacesToVisit[b]) continue nextInterface; interfacesToVisit[nextPosition++] = next; } } MethodBinding[] methods = superType.unResolvedMethods(); nextMethod: for (int m = methods.length; --m >= 0; ) { // Interface methods are all abstract public MethodBinding inheritedMethod = methods[m]; MethodBinding[] existingMethods = (MethodBinding[])this.inheritedMethods.get(inheritedMethod.selector); if (existingMethods == null) { existingMethods = new MethodBinding[]{inheritedMethod}; } else { int length = existingMethods.length; // look to see if any of the existingMethods implement this inheritedMethod // https://bugs.eclipse.org/bugs/show_bug.cgi?id=302358, skip inherited method only if any overriding version // in a subclass is guaranteed to have the same erasure as an existing method. for (int e = 0; e < length; e++) if (isInterfaceMethodImplemented(inheritedMethod, existingMethods[e], superType) && !canOverridingMethodDifferInErasure(existingMethods[e], inheritedMethod)) 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] = inheritedMethod; } this.inheritedMethods.put(inheritedMethod.selector, existingMethods); } } } } // Given `overridingMethod' which overrides `inheritedMethod' answer whether some subclass method that // differs in erasure from overridingMethod could override `inheritedMethod' protected boolean canOverridingMethodDifferInErasure(MethodBinding overridingMethod, MethodBinding inheritedMethod) { return false; // the case for <= 1.4 (cannot differ) } void computeMethods() { MethodBinding[] methods = this.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); } } } MethodBinding computeSubstituteMethod(MethodBinding inheritedMethod, MethodBinding currentMethod) { if (inheritedMethod == null) return null; if (currentMethod.parameters.length != inheritedMethod.parameters.length) return null; // no match return inheritedMethod; } boolean couldMethodOverride(MethodBinding method, MethodBinding inheritedMethod) { if (!org.eclipse.che.ide.ext.java.jdt.core.compiler.CharOperation.equals(method.selector, inheritedMethod.selector)) return false; if (method == inheritedMethod || method.isStatic() || inheritedMethod.isStatic()) return false; if (inheritedMethod.isPrivate()) return false; if (inheritedMethod.isDefault() && method.declaringClass.getPackage() != inheritedMethod.declaringClass.getPackage()) return false; if (!method.isPublic()) { // inheritedMethod is either public or protected & method is less than public if (inheritedMethod.isPublic()) return false; if (inheritedMethod.isProtected() && !method.isProtected()) return false; } return true; } // Answer whether the method overrides the inheritedMethod // Check the necessary visibility rules & inheritance from the inheritedMethod's declaringClass // See isMethodSubsignature() for parameter comparisons public boolean doesMethodOverride(MethodBinding method, MethodBinding inheritedMethod) { if (!couldMethodOverride(method, inheritedMethod)) return false; inheritedMethod = inheritedMethod.original(); TypeBinding match = method.declaringClass.findSuperTypeOriginatingFrom(inheritedMethod.declaringClass); if (!(match instanceof ReferenceBinding)) return false; // method's declaringClass does not inherit from inheritedMethod's return isParameterSubsignature(method, inheritedMethod); } SimpleSet findSuperinterfaceCollisions(ReferenceBinding superclass, ReferenceBinding[] superInterfaces) { return null; // noop in 1.4 } MethodBinding findBestInheritedAbstractMethod(MethodBinding[] methods, int length) { findMethod: for (int i = 0; i < length; i++) { MethodBinding method = methods[i]; if (!method.isAbstract()) continue findMethod; for (int j = 0; j < length; j++) { if (i == j) continue; if (!checkInheritedReturnTypes(method, methods[j])) { if (this.type.isInterface() && methods[j].declaringClass.id == TypeIds.T_JavaLangObject) return method; // do not complain since the super interface already got blamed continue findMethod; } } return method; } return null; } int[] findOverriddenInheritedMethods(MethodBinding[] methods, int length) { // NOTE assumes length > 1 // inherited methods are added as we walk up the superclass hierarchy, then each superinterface // so method[1] from a class can NOT override method[0], but methods from superinterfaces can // since superinterfaces can be added from different superclasses or other superinterfaces int[] toSkip = null; int i = 0; ReferenceBinding declaringClass = methods[i].declaringClass; if (!declaringClass.isInterface()) { // in the first pass, skip overridden methods from superclasses // only keep methods from the closest superclass, all others from higher superclasses can be skipped // NOTE: methods were added in order by walking up the superclass hierarchy ReferenceBinding declaringClass2 = methods[++i].declaringClass; while (declaringClass == declaringClass2) { if (++i == length) return null; declaringClass2 = methods[i].declaringClass; } if (!declaringClass2.isInterface()) { // skip all methods from different superclasses if (declaringClass.fPackage != declaringClass2.fPackage && methods[i].isDefault()) return null; toSkip = new int[length]; do { toSkip[i] = -1; if (++i == length) return toSkip; declaringClass2 = methods[i].declaringClass; } while (!declaringClass2.isInterface()); } } // in the second pass, skip overridden methods from superinterfaces // NOTE: superinterfaces can appear in 'random' order nextMethod: for (; i < length; i++) { if (toSkip != null && toSkip[i] == -1) continue nextMethod; declaringClass = methods[i].declaringClass; for (int j = i + 1; j < length; j++) { if (toSkip != null && toSkip[j] == -1) continue; ReferenceBinding declaringClass2 = methods[j].declaringClass; if (declaringClass == declaringClass2) continue; if (declaringClass.implementsInterface(declaringClass2, true)) { if (toSkip == null) toSkip = new int[length]; toSkip[j] = -1; } else if (declaringClass2.implementsInterface(declaringClass, true)) { if (toSkip == null) toSkip = new int[length]; toSkip[i] = -1; continue nextMethod; } } } return toSkip; } 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 } boolean isInterfaceMethodImplemented(MethodBinding inheritedMethod, MethodBinding existingMethod, ReferenceBinding superType) { // skip interface method with the same signature if visible to its declaringClass return areParametersEqual(existingMethod, inheritedMethod) && existingMethod.declaringClass.implementsInterface(superType, true); } public boolean isMethodSubsignature(MethodBinding method, MethodBinding inheritedMethod) { return org.eclipse.che.ide.ext.java.jdt.core.compiler.CharOperation.equals(method.selector, inheritedMethod.selector) && isParameterSubsignature(method, inheritedMethod); } boolean isParameterSubsignature(MethodBinding method, MethodBinding inheritedMethod) { return areParametersEqual(method, inheritedMethod); } boolean isSameClassOrSubclassOf(ReferenceBinding testClass, ReferenceBinding superclass) { do { if (testClass == superclass) return true; } while ((testClass = testClass.superclass()) != null); return false; } boolean mustImplementAbstractMethod(ReferenceBinding declaringClass) { // 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 if (!mustImplementAbstractMethods()) return false; ReferenceBinding superclass = this.type.superclass(); 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 (!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 } boolean mustImplementAbstractMethods() { return !this.type.isInterface() && !this.type.isAbstract(); } ProblemReporter problemReporter() { return this.type.scope.problemReporter(); } ProblemReporter problemReporter(MethodBinding currentMethod) { ProblemReporter reporter = problemReporter(); if (currentMethod.declaringClass == this.type && currentMethod.sourceMethod() != null) // only report against the // currentMethod if its implemented // by the type reporter.referenceContext = currentMethod.sourceMethod(); return reporter; } /** * Return true and report an incompatibleReturnType error if currentMethod's return type is strictly incompatible with * inheritedMethod's, else return false and report an unchecked conversion warning. Do not call when * areReturnTypesCompatible(currentMethod, inheritedMethod) returns true. * * @param currentMethod * the (potentially) inheriting method * @param inheritedMethod * the inherited method * @return true if currentMethod's return type is strictly incompatible with inheritedMethod's */ boolean reportIncompatibleReturnTypeError(MethodBinding currentMethod, MethodBinding inheritedMethod) { problemReporter(currentMethod).incompatibleReturnType(currentMethod, inheritedMethod); return true; } ReferenceBinding[] resolvedExceptionTypesFor(MethodBinding method) { ReferenceBinding[] exceptions = method.thrownExceptions; if ((method.modifiers & ExtraCompilerModifiers.AccUnresolved) == 0) return exceptions; if (!(method.declaringClass instanceof BinaryTypeBinding)) return Binding.NO_EXCEPTIONS; // safety check for (int i = exceptions.length; --i >= 0; ) exceptions[i] = (ReferenceBinding)BinaryTypeBinding.resolveType(exceptions[i], this.environment, true /* * raw conversion */); return exceptions; } void verify() { computeMethods(); computeInheritedMethods(); checkMethods(); if (this.type.isClass()) checkForMissingHashCodeMethod(); } void verify(SourceTypeBinding someType) { if (this.type == null) { try { this.type = someType; verify(); } finally { this.type = null; } } else { this.environment.newMethodVerifier().verify(someType); } } public String toString() { StringBuffer buffer = new StringBuffer(10); buffer.append("MethodVerifier for type: "); //$NON-NLS-1$ buffer.append(this.type.readableName()); buffer.append('\n'); buffer.append("\t-inherited methods: "); //$NON-NLS-1$ buffer.append(this.inheritedMethods); return buffer.toString(); } }