/******************************************************************************* * Copyright (c) 2000, 2003 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 net.sourceforge.phpdt.internal.compiler.lookup; import net.sourceforge.phpdt.core.compiler.CharOperation; import net.sourceforge.phpdt.internal.compiler.ast.MethodDeclaration; import net.sourceforge.phpdt.internal.compiler.ast.TypeDeclaration; import net.sourceforge.phpdt.internal.compiler.problem.ProblemReporter; import net.sourceforge.phpdt.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 void checkAgainstInheritedMethods(MethodBinding currentMethod, MethodBinding[] methods, int length) { currentMethod.modifiers |= CompilerModifiers.AccOverriding; for (int i = length; --i >= 0;) { MethodBinding inheritedMethod = methods[i]; if (!currentMethod.isAbstract() && inheritedMethod.isAbstract()) currentMethod.modifiers |= CompilerModifiers.AccImplementing; if (currentMethod.returnType != inheritedMethod.returnType) { this.problemReporter(currentMethod).incompatibleReturnType( currentMethod, inheritedMethod); } else if (currentMethod.isStatic() != inheritedMethod.isStatic()) { // Cannot // override // a // static // method // or // hide // an // instance // method this.problemReporter(currentMethod).staticAndInstanceConflict( 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 (inheritedMethod.isViewedAsDeprecated()) // if (!currentMethod.isViewedAsDeprecated() || // environment.options.reportDeprecationInsideDeprecatedCode) // 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 = newMethod.thrownExceptions; ReferenceBinding[] inheritedExceptions = inheritedMethod.thrownExceptions; for (int i = newExceptions.length; --i >= 0;) { ReferenceBinding newException = newExceptions[i]; int j = inheritedExceptions.length; while (--j > -1 && !this.isSameClassOrSubclassOf(newException, inheritedExceptions[j])) ; 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) { TypeBinding returnType = methods[0].returnType; int index = length; while (--index > 0 && returnType == methods[index].returnType) ; 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])) return; // have already reported problem against the // concrete superclass 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; } 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(); char[][] methodSelectors = this.inheritedMethods.keyTable; for (int s = methodSelectors.length; --s >= 0;) { if (methodSelectors[s] != null) { MethodBinding[] current = (MethodBinding[]) this.currentMethods .get(methodSelectors[s]); MethodBinding[] inherited = (MethodBinding[]) this.inheritedMethods.valueTable[s]; 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++) { if (inherited[j] != null && currentMethod .areParametersEqual(inherited[j])) { matchingInherited[++index] = inherited[j]; 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 if (inherited[i] != null) { matchingInherited[++index] = inherited[i]; for (int j = i + 1; j < length; j++) { if (inherited[j] != null && inherited[i] .areParametersEqual(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()) { if (mustImplementAbstractMethod(matchingInherited[0])) { TypeDeclaration typeDeclaration = this.type.scope.referenceContext; if (typeDeclaration != null) { MethodDeclaration missingAbstractMethod = typeDeclaration .addMissingAbstractMethodFor(matchingInherited[0]); missingAbstractMethod.scope .problemReporter() .abstractMethodMustBeImplemented( this.type, matchingInherited[0]); } else { this .problemReporter() .abstractMethodMustBeImplemented( this.type, 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 (method.returnType != abstractMethod.returnType || !method.areParametersEqual(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[5][]; int lastPosition = 0; interfacesToVisit[lastPosition] = type.superInterfaces(); ReferenceBinding superType = this.type.isClass() ? this.type .superclass() : this.type.scope.getJavaLangObject(); // check // interface // methods // against // Object MethodBinding[] nonVisibleDefaultMethods = null; int nonVisibleCount = 0; while (superType != null) { if (superType.isValidBinding()) { ReferenceBinding[] itsInterfaces = superType.superInterfaces(); if (itsInterfaces != NoSuperInterfaces) { if (++lastPosition == interfacesToVisit.length) System .arraycopy( interfacesToVisit, 0, interfacesToVisit = new ReferenceBinding[lastPosition * 2][], 0, lastPosition); interfacesToVisit[lastPosition] = itsInterfaces; } MethodBinding[] methods = superType.methods(); nextMethod: for (int m = methods.length; --m >= 0;) { MethodBinding method = methods[m]; if (!(method.isPrivate() || method.isConstructor() || method .isDefaultAbstract())) { // look at all methods // which are NOT private // or constructors or // default abstract MethodBinding[] existingMethods = (MethodBinding[]) this.inheritedMethods .get(method.selector); if (existingMethods != null) { for (int i = 0, length = existingMethods.length; i < length; i++) { if (method.returnType == existingMethods[i].returnType && method .areParametersEqual(existingMethods[i])) { if (method.isDefault() && method.isAbstract() && method.declaringClass.fPackage != type.fPackage) checkPackagePrivateAbstractMethod(method); continue nextMethod; } } } if (nonVisibleDefaultMethods != null) for (int i = 0; i < nonVisibleCount; i++) if (method.returnType == nonVisibleDefaultMethods[i].returnType && CharOperation .equals( method.selector, nonVisibleDefaultMethods[i].selector) && method .areParametersEqual(nonVisibleDefaultMethods[i])) continue nextMethod; if (!(method.isDefault() && method.declaringClass.fPackage != type.fPackage)) { // ignore // methods // which // have // default // visibility // and // are // NOT // defined // in // another // package 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.inheritedMethods.put(method.selector, existingMethods); } else { if (nonVisibleDefaultMethods == null) nonVisibleDefaultMethods = new MethodBinding[10]; else if (nonVisibleCount == nonVisibleDefaultMethods.length) System .arraycopy( nonVisibleDefaultMethods, 0, (nonVisibleDefaultMethods = new MethodBinding[nonVisibleCount * 2]), 0, nonVisibleCount); nonVisibleDefaultMethods[nonVisibleCount++] = method; 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 (method.returnType == current[i].returnType && method .areParametersEqual(current[i])) { this.problemReporter() .overridesPackageDefaultMethod( current[i], method); break foundMatch; } } } } } } superType = superType.superclass(); } } for (int i = 0; i <= lastPosition; i++) { ReferenceBinding[] interfaces = interfacesToVisit[i]; if (interfaces == null) { interfaces = new ReferenceBinding[0]; } for (int j = 0, length = interfaces.length; j < length; j++) { superType = interfaces[j]; if ((superType.tagBits & InterfaceVisited) == 0) { superType.tagBits |= InterfaceVisited; if (superType.isValidBinding()) { ReferenceBinding[] itsInterfaces = superType .superInterfaces(); if (itsInterfaces != NoSuperInterfaces) { if (++lastPosition == interfacesToVisit.length) System .arraycopy( interfacesToVisit, 0, interfacesToVisit = new ReferenceBinding[lastPosition * 2][], 0, lastPosition); interfacesToVisit[lastPosition] = itsInterfaces; } MethodBinding[] methods = superType.methods(); 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[1]; else System .arraycopy( existingMethods, 0, (existingMethods = new MethodBinding[existingMethods.length + 1]), 0, existingMethods.length - 1); existingMethods[existingMethods.length - 1] = method; this.inheritedMethods.put(method.selector, existingMethods); } } } } } // bit reinitialization for (int i = 0; i <= lastPosition; i++) { ReferenceBinding[] interfaces = interfacesToVisit[i]; if (interfaces == null) { interfaces = new ReferenceBinding[0]; } for (int j = 0, length = interfaces.length; j < length; j++) interfaces[j].tagBits &= ~InterfaceVisited; } } private void computeMethods() { MethodBinding[] methods = type.methods(); if (methods == null) { methods = new MethodBinding[0]; } 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; } private ReferenceBinding runtimeException() { if (runtimeException == null) this.runtimeException = this.type.scope .getJavaLangRuntimeException(); return runtimeException; } public void verify(SourceTypeBinding type) { this.type = type; 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(); } }