1 /*******************************************************************************
2 * Copyright (c) 2000, 2004 IBM Corporation and others.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the Common Public License v1.0
5 * which accompanies this distribution, and is available at
6 * http://www.eclipse.org/legal/cpl-v10.html
9 * IBM Corporation - initial API and implementation
10 *******************************************************************************/
11 package org.eclipse.jdt.internal.compiler.lookup;
13 import org.eclipse.jdt.core.compiler.CharOperation;
14 import org.eclipse.jdt.internal.compiler.ast.MethodDeclaration;
15 import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration;
16 import org.eclipse.jdt.internal.compiler.problem.ProblemReporter;
17 import org.eclipse.jdt.internal.compiler.util.HashtableOfObject;
19 public final class MethodVerifier implements TagBits, TypeConstants {
20 SourceTypeBinding type;
21 HashtableOfObject inheritedMethods;
22 HashtableOfObject currentMethods;
23 ReferenceBinding runtimeException;
24 ReferenceBinding errorException;
25 LookupEnvironment environment;
27 Binding creation is responsible for reporting all problems with types:
28 - all modifier problems (duplicates & multiple visibility modifiers + incompatible combinations - abstract/final)
29 - plus invalid modifiers given the context (the verifier did not do this before)
30 - qualified name collisions between a type and a package (types in default packages are excluded)
31 - all type hierarchy problems:
32 - cycles in the superclass or superinterface hierarchy
33 - an ambiguous, invisible or missing superclass or superinterface
34 - extending a final class
35 - extending an interface instead of a class
36 - implementing a class instead of an interface
37 - implementing the same interface more than once (ie. duplicate interfaces)
39 - shadowing an enclosing type's source name
40 - defining a static class or interface inside a non-static nested class
41 - defining an interface as a local type (local types can only be classes)
43 public MethodVerifier(LookupEnvironment environment) {
44 this.type = null; // Initialized with the public method verify(SourceTypeBinding)
45 this.inheritedMethods = null;
46 this.currentMethods = null;
47 this.runtimeException = null;
48 this.errorException = null;
49 this.environment = environment;
51 private boolean areParametersEqual(MethodBinding one, MethodBinding two) {
52 TypeBinding[] oneArgs = one.parameters;
53 TypeBinding[] twoArgs = two.parameters;
54 if (oneArgs == twoArgs) return true;
56 int length = oneArgs.length;
57 if (length != twoArgs.length) return false;
59 for (int i = 0; i < length; i++)
60 if (!areTypesEqual(oneArgs[i], twoArgs[i])) return false;
63 private boolean areReturnTypesEqual(MethodBinding one, MethodBinding two) {
64 return areTypesEqual(one.returnType, two.returnType);
66 private boolean areTypesEqual(TypeBinding one, TypeBinding two) {
67 if (one == two) return true;
68 if (one instanceof ReferenceBinding && two instanceof ReferenceBinding)
69 // can compare unresolved to resolved reference bindings
70 return CharOperation.equals(((ReferenceBinding) one).compoundName, ((ReferenceBinding) two).compoundName);
71 return false; // all other type bindings are identical
73 private void checkAbstractMethod(MethodBinding abstractMethod) {
74 if (mustImplementAbstractMethod(abstractMethod)) {
75 TypeDeclaration typeDeclaration = this.type.scope.referenceContext;
76 if (typeDeclaration != null) {
77 MethodDeclaration missingAbstractMethod = typeDeclaration.addMissingAbstractMethodFor(abstractMethod);
78 missingAbstractMethod.scope.problemReporter().abstractMethodMustBeImplemented(this.type, abstractMethod);
80 this.problemReporter().abstractMethodMustBeImplemented(this.type, abstractMethod);
84 private void checkAgainstInheritedMethods(MethodBinding currentMethod, MethodBinding[] methods, int length) {
85 nextMethod : for (int i = length; --i >= 0;) {
86 MethodBinding inheritedMethod = methods[i];
87 if (currentMethod.isStatic() != inheritedMethod.isStatic()) { // Cannot override a static method or hide an instance method
88 this.problemReporter(currentMethod).staticAndInstanceConflict(currentMethod, inheritedMethod);
92 if (!currentMethod.isAbstract() && inheritedMethod.isAbstract()) {
93 if ((currentMethod.modifiers & CompilerModifiers.AccOverriding) == 0)
94 currentMethod.modifiers |= CompilerModifiers.AccImplementing;
96 currentMethod.modifiers |= CompilerModifiers.AccOverriding;
99 if (!areReturnTypesEqual(currentMethod, inheritedMethod)) {
100 this.problemReporter(currentMethod).incompatibleReturnType(currentMethod, inheritedMethod);
102 if (currentMethod.thrownExceptions != NoExceptions)
103 this.checkExceptions(currentMethod, inheritedMethod);
104 if (inheritedMethod.isFinal())
105 this.problemReporter(currentMethod).finalMethodCannotBeOverridden(currentMethod, inheritedMethod);
106 if (!this.isAsVisible(currentMethod, inheritedMethod))
107 this.problemReporter(currentMethod).visibilityConflict(currentMethod, inheritedMethod);
108 if (environment.options.reportDeprecationWhenOverridingDeprecatedMethod && inheritedMethod.isViewedAsDeprecated()) {
109 if (!currentMethod.isViewedAsDeprecated() || environment.options.reportDeprecationInsideDeprecatedCode) {
110 // check against the other inherited methods to see if they hide this inheritedMethod
111 ReferenceBinding declaringClass = inheritedMethod.declaringClass;
112 if (declaringClass.isInterface())
113 for (int j = length; --j >= 0;)
114 if (i != j && methods[j].declaringClass.implementsInterface(declaringClass, false))
117 this.problemReporter(currentMethod).overridesDeprecatedMethod(currentMethod, inheritedMethod);
125 Verify that newExceptions are all included in inheritedExceptions.
126 Assumes all exceptions are valid and throwable.
127 Unchecked exceptions (compatible with runtime & error) are ignored (see the spec on pg. 203).
129 private void checkExceptions(MethodBinding newMethod, MethodBinding inheritedMethod) {
130 ReferenceBinding[] newExceptions = resolvedExceptionTypesFor(newMethod);
131 ReferenceBinding[] inheritedExceptions = resolvedExceptionTypesFor(inheritedMethod);
132 for (int i = newExceptions.length; --i >= 0;) {
133 ReferenceBinding newException = newExceptions[i];
134 int j = inheritedExceptions.length;
135 while (--j > -1 && !this.isSameClassOrSubclassOf(newException, inheritedExceptions[j])){/*empty*/}
137 if (!(newException.isCompatibleWith(this.runtimeException()) || newException.isCompatibleWith(this.errorException())))
138 this.problemReporter(newMethod).incompatibleExceptionInThrowsClause(this.type, newMethod, inheritedMethod, newException);
141 private void checkInheritedMethods(MethodBinding[] methods, int length) {
142 MethodBinding first = methods[0];
144 while (--index > 0 && areReturnTypesEqual(first, methods[index])){/*empty*/}
145 if (index > 0) { // All inherited methods do NOT have the same vmSignature
146 this.problemReporter().inheritedMethodsHaveIncompatibleReturnTypes(this.type, methods, length);
150 MethodBinding concreteMethod = null;
151 if (!type.isInterface()) { // ignore concrete methods for interfaces
152 for (int i = length; --i >= 0;) { // Remember that only one of the methods can be non-abstract
153 if (!methods[i].isAbstract()) {
154 concreteMethod = methods[i];
159 if (concreteMethod == null) {
160 if (this.type.isClass() && !this.type.isAbstract()) {
161 for (int i = length; --i >= 0;) {
162 if (mustImplementAbstractMethod(methods[i])) {
163 TypeDeclaration typeDeclaration = this.type.scope.referenceContext;
164 if (typeDeclaration != null) {
165 MethodDeclaration missingAbstractMethod = typeDeclaration.addMissingAbstractMethodFor(methods[0]);
166 missingAbstractMethod.scope.problemReporter().abstractMethodMustBeImplemented(this.type, methods[0]);
168 this.problemReporter().abstractMethodMustBeImplemented(this.type, methods[0]);
177 MethodBinding[] abstractMethods = new MethodBinding[length - 1];
179 for (int i = length; --i >= 0;)
180 if (methods[i] != concreteMethod)
181 abstractMethods[index++] = methods[i];
183 // Remember that interfaces can only define public instance methods
184 if (concreteMethod.isStatic())
185 // Cannot inherit a static method which is specified as an instance method by an interface
186 this.problemReporter().staticInheritedMethodConflicts(type, concreteMethod, abstractMethods);
187 if (!concreteMethod.isPublic())
188 // Cannot reduce visibility of a public method specified by an interface
189 this.problemReporter().inheritedMethodReducesVisibility(type, concreteMethod, abstractMethods);
190 if (concreteMethod.thrownExceptions != NoExceptions)
191 for (int i = abstractMethods.length; --i >= 0;)
192 this.checkExceptions(concreteMethod, abstractMethods[i]);
195 For each inherited method identifier (message pattern - vm signature minus the return type)
196 if current method exists
197 if current's vm signature does not match an inherited signature then complain
198 else compare current's exceptions & visibility against each inherited method
200 if inherited methods = 1
201 if inherited is abstract && type is NOT an interface or abstract, complain
203 if vm signatures do not match complain
205 find the concrete implementation amongst the abstract methods (can only be 1)
207 it must be a public instance method
208 compare concrete's exceptions against each abstract method
210 complain about missing implementation only if type is NOT an interface or abstract
212 private void checkMethods() {
213 boolean mustImplementAbstractMethods = this.type.isClass() && !this.type.isAbstract();
214 boolean skipInheritedMethods = mustImplementAbstractMethods && this.type.superInterfaces() == NoSuperInterfaces
215 && this.type.superclass() != null && !this.type.superclass().isAbstract(); // have a single concrete superclass so only check overridden methods
216 char[][] methodSelectors = this.inheritedMethods.keyTable;
217 nextSelector : for (int s = methodSelectors.length; --s >= 0;) {
218 if (methodSelectors[s] == null) continue nextSelector;
220 MethodBinding[] current = (MethodBinding[]) this.currentMethods.get(methodSelectors[s]);
221 if (current == null && skipInheritedMethods)
222 continue nextSelector;
224 MethodBinding[] inherited = (MethodBinding[]) this.inheritedMethods.valueTable[s];
225 if (inherited.length == 1 && current == null) { // handle the common case
226 if (mustImplementAbstractMethods && inherited[0].isAbstract())
227 checkAbstractMethod(inherited[0]);
228 continue nextSelector;
232 MethodBinding[] matchingInherited = new MethodBinding[inherited.length];
233 if (current != null) {
234 for (int i = 0, length1 = current.length; i < length1; i++) {
235 while (index >= 0) matchingInherited[index--] = null; // clear the previous contents of the matching methods
236 MethodBinding currentMethod = current[i];
237 for (int j = 0, length2 = inherited.length; j < length2; j++) {
238 MethodBinding inheritedMethod = inherited[j];
239 if (inheritedMethod != null && areParametersEqual(currentMethod, inheritedMethod)) {
240 matchingInherited[++index] = inheritedMethod;
241 inherited[j] = null; // do not want to find it again
245 this.checkAgainstInheritedMethods(currentMethod, matchingInherited, index + 1); // pass in the length of matching
249 for (int i = 0, length = inherited.length; i < length; i++) {
250 while (index >= 0) matchingInherited[index--] = null; // clear the previous contents of the matching methods
251 MethodBinding inheritedMethod = inherited[i];
252 if (inheritedMethod != null) {
253 matchingInherited[++index] = inheritedMethod;
254 for (int j = i + 1; j < length; j++) {
255 if (inherited[j] != null && areParametersEqual(inheritedMethod, inherited[j])) {
256 matchingInherited[++index] = inherited[j];
257 inherited[j] = null; // do not want to find it again
262 this.checkInheritedMethods(matchingInherited, index + 1); // pass in the length of matching
263 else if (mustImplementAbstractMethods && index == 0 && matchingInherited[0].isAbstract())
264 checkAbstractMethod(matchingInherited[0]);
268 private void checkPackagePrivateAbstractMethod(MethodBinding abstractMethod) {
269 ReferenceBinding superType = this.type.superclass();
270 char[] selector = abstractMethod.selector;
272 if (!superType.isValidBinding()) return;
273 if (!superType.isAbstract()) return; // closer non abstract super type will be flagged instead
275 MethodBinding[] methods = superType.getMethods(selector);
276 nextMethod : for (int m = methods.length; --m >= 0;) {
277 MethodBinding method = methods[m];
278 if (!areReturnTypesEqual(method, abstractMethod) || !areParametersEqual(method, abstractMethod))
280 if (method.isPrivate() || method.isConstructor() || method.isDefaultAbstract())
282 if (superType.fPackage == abstractMethod.declaringClass.fPackage) return; // found concrete implementation of abstract method in same package
284 } while ((superType = superType.superclass()) != abstractMethod.declaringClass);
286 // non visible abstract methods cannot be overridden so the type must be defined abstract
287 this.problemReporter().abstractMethodCannotBeOverridden(this.type, abstractMethod);
290 Binding creation is responsible for reporting:
291 - all modifier problems (duplicates & multiple visibility modifiers + incompatible combinations)
292 - plus invalid modifiers given the context... examples:
293 - interface methods can only be public
294 - abstract methods can only be defined by abstract classes
295 - collisions... 2 methods with identical vmSelectors
296 - multiple methods with the same message pattern but different return types
297 - ambiguous, invisible or missing return/argument/exception types
298 - check the type of any array is not void
299 - check that each exception type is Throwable or a subclass of it
301 private void computeInheritedMethods() {
302 this.inheritedMethods = new HashtableOfObject(51); // maps method selectors to an array of methods... must search to match paramaters & return type
303 ReferenceBinding[][] interfacesToVisit = new ReferenceBinding[3][];
304 int lastPosition = -1;
305 ReferenceBinding[] itsInterfaces = type.superInterfaces();
306 if (itsInterfaces != NoSuperInterfaces)
307 interfacesToVisit[++lastPosition] = itsInterfaces;
309 ReferenceBinding superType = this.type.isClass()
310 ? this.type.superclass()
311 : this.type.scope.getJavaLangObject(); // check interface methods against Object
312 HashtableOfObject nonVisibleDefaultMethods = new HashtableOfObject(3); // maps method selectors to an array of methods
313 boolean allSuperclassesAreAbstract = true;
315 while (superType != null) {
316 if (superType.isValidBinding()) {
317 if (allSuperclassesAreAbstract) {
318 if (superType.isAbstract()) {
319 // only need to include superinterfaces if immediate superclasses are abstract
320 if ((itsInterfaces = superType.superInterfaces()) != NoSuperInterfaces) {
321 if (++lastPosition == interfacesToVisit.length)
322 System.arraycopy(interfacesToVisit, 0, interfacesToVisit = new ReferenceBinding[lastPosition * 2][], 0, lastPosition);
323 interfacesToVisit[lastPosition] = itsInterfaces;
326 allSuperclassesAreAbstract = false;
330 MethodBinding[] methods = superType.unResolvedMethods();
331 nextMethod : for (int m = methods.length; --m >= 0;) {
332 MethodBinding method = methods[m];
333 if (method.isPrivate() || method.isConstructor() || method.isDefaultAbstract())
335 MethodBinding[] existingMethods = (MethodBinding[]) this.inheritedMethods.get(method.selector);
336 if (existingMethods != null) {
337 for (int i = 0, length = existingMethods.length; i < length; i++) {
338 if (areReturnTypesEqual(method, existingMethods[i]) && areParametersEqual(method, existingMethods[i])) {
339 if (method.isDefault() && method.isAbstract() && method.declaringClass.fPackage != type.fPackage)
340 checkPackagePrivateAbstractMethod(method);
345 MethodBinding[] nonVisible = (MethodBinding[]) nonVisibleDefaultMethods.get(method.selector);
346 if (nonVisible != null)
347 for (int i = 0, l = nonVisible.length; i < l; i++)
348 if (areReturnTypesEqual(method, nonVisible[i]) && areParametersEqual(method, nonVisible[i]))
351 if (!method.isDefault() || method.declaringClass.fPackage == type.fPackage) {
352 if (existingMethods == null) {
353 existingMethods = new MethodBinding[] {method};
355 int length = existingMethods.length;
356 System.arraycopy(existingMethods, 0, existingMethods = new MethodBinding[length + 1], 0, length);
357 existingMethods[length] = method;
359 this.inheritedMethods.put(method.selector, existingMethods);
361 if (nonVisible == null) {
362 nonVisible = new MethodBinding[] {method};
364 int length = nonVisible.length;
365 System.arraycopy(nonVisible, 0, nonVisible = new MethodBinding[length + 1], 0, length);
366 nonVisible[length] = method;
368 nonVisibleDefaultMethods.put(method.selector, nonVisible);
370 if (method.isAbstract() && !this.type.isAbstract()) // non visible abstract methods cannot be overridden so the type must be defined abstract
371 this.problemReporter().abstractMethodCannotBeOverridden(this.type, method);
373 MethodBinding[] current = (MethodBinding[]) this.currentMethods.get(method.selector);
374 if (current != null) { // non visible methods cannot be overridden so a warning is issued
375 foundMatch : for (int i = 0, length = current.length; i < length; i++) {
376 if (areReturnTypesEqual(method, current[i]) && areParametersEqual(method, current[i])) {
377 this.problemReporter().overridesPackageDefaultMethod(current[i], method);
384 superType = superType.superclass();
388 for (int i = 0; i <= lastPosition; i++) {
389 ReferenceBinding[] interfaces = interfacesToVisit[i];
390 for (int j = 0, l = interfaces.length; j < l; j++) {
391 superType = interfaces[j];
392 if ((superType.tagBits & InterfaceVisited) == 0) {
393 superType.tagBits |= InterfaceVisited;
394 if (superType.isValidBinding()) {
395 if ((itsInterfaces = superType.superInterfaces()) != NoSuperInterfaces) {
396 if (++lastPosition == interfacesToVisit.length)
397 System.arraycopy(interfacesToVisit, 0, interfacesToVisit = new ReferenceBinding[lastPosition * 2][], 0, lastPosition);
398 interfacesToVisit[lastPosition] = itsInterfaces;
401 MethodBinding[] methods = superType.unResolvedMethods();
402 nextMethod : for (int m = methods.length; --m >= 0;) { // Interface methods are all abstract public
403 MethodBinding method = methods[m];
404 MethodBinding[] existingMethods = (MethodBinding[]) this.inheritedMethods.get(method.selector);
405 if (existingMethods == null) {
406 existingMethods = new MethodBinding[] {method};
408 int length = existingMethods.length;
409 for (int e = 0; e < length; e++) {
410 MethodBinding existing = existingMethods[e];
411 if (areParametersEqual(method, existing) && existing.declaringClass.implementsInterface(superType, true))
412 continue nextMethod; // skip interface method with the same signature if visible to its declaringClass
414 System.arraycopy(existingMethods, 0, existingMethods = new MethodBinding[length + 1], 0, length);
415 existingMethods[length] = method;
417 this.inheritedMethods.put(method.selector, existingMethods);
424 // bit reinitialization
425 for (int i = 0; i <= lastPosition; i++) {
426 ReferenceBinding[] interfaces = interfacesToVisit[i];
427 for (int j = 0, length = interfaces.length; j < length; j++)
428 interfaces[j].tagBits &= ~InterfaceVisited;
431 private void computeMethods() {
432 MethodBinding[] methods = type.methods();
433 int size = methods.length;
434 this.currentMethods = new HashtableOfObject(size == 0 ? 1 : size); // maps method selectors to an array of methods... must search to match paramaters & return type
435 for (int m = size; --m >= 0;) {
436 MethodBinding method = methods[m];
437 if (!(method.isConstructor() || method.isDefaultAbstract())) { // keep all methods which are NOT constructors or default abstract
438 MethodBinding[] existingMethods = (MethodBinding[]) this.currentMethods.get(method.selector);
439 if (existingMethods == null)
440 existingMethods = new MethodBinding[1];
442 System.arraycopy(existingMethods, 0,
443 (existingMethods = new MethodBinding[existingMethods.length + 1]), 0, existingMethods.length - 1);
444 existingMethods[existingMethods.length - 1] = method;
445 this.currentMethods.put(method.selector, existingMethods);
449 private ReferenceBinding errorException() {
450 if (errorException == null)
451 this.errorException = this.type.scope.getJavaLangError();
452 return errorException;
454 private boolean isAsVisible(MethodBinding newMethod, MethodBinding inheritedMethod) {
455 if (inheritedMethod.modifiers == newMethod.modifiers) return true;
457 if (newMethod.isPublic()) return true; // Covers everything
458 if (inheritedMethod.isPublic()) return false;
460 if (newMethod.isProtected()) return true;
461 if (inheritedMethod.isProtected()) return false;
463 return !newMethod.isPrivate(); // The inheritedMethod cannot be private since it would not be visible
465 private boolean isSameClassOrSubclassOf(ReferenceBinding testClass, ReferenceBinding superclass) {
467 if (testClass == superclass) return true;
468 } while ((testClass = testClass.superclass()) != null);
471 private boolean mustImplementAbstractMethod(MethodBinding abstractMethod) {
472 // if the type's superclass is an abstract class, then all abstract methods must be implemented
473 // otherwise, skip it if the type's superclass must implement any of the inherited methods
474 ReferenceBinding superclass = this.type.superclass();
475 ReferenceBinding declaringClass = abstractMethod.declaringClass;
476 if (declaringClass.isClass()) {
477 while (superclass.isAbstract() && superclass != declaringClass)
478 superclass = superclass.superclass(); // find the first concrete superclass or the abstract declaringClass
480 if (this.type.implementsInterface(declaringClass, false)) {
481 if (this.type.isAbstract()) return false; // leave it for the subclasses
482 if (!superclass.implementsInterface(declaringClass, true)) // only if a superclass does not also implement the interface
485 while (superclass.isAbstract() && !superclass.implementsInterface(declaringClass, false))
486 superclass = superclass.superclass(); // find the first concrete superclass or the superclass which implements the interface
488 return superclass.isAbstract(); // if it is a concrete class then we have already reported problem against it
490 private ProblemReporter problemReporter() {
491 return this.type.scope.problemReporter();
493 private ProblemReporter problemReporter(MethodBinding currentMethod) {
494 ProblemReporter reporter = problemReporter();
495 if (currentMethod.declaringClass == type) // only report against the currentMethod if its implemented by the type
496 reporter.referenceContext = currentMethod.sourceMethod();
499 ReferenceBinding[] resolvedExceptionTypesFor(MethodBinding method) {
500 ReferenceBinding[] exceptions = method.thrownExceptions;
501 if ((method.modifiers & CompilerModifiers.AccUnresolved) == 0)
504 if (!(method.declaringClass instanceof BinaryTypeBinding))
505 return TypeConstants.NoExceptions; // safety check
506 BinaryTypeBinding binaryType = (BinaryTypeBinding) method.declaringClass;
508 for (int i = exceptions.length; --i >= 0;)
509 if (exceptions[i] instanceof UnresolvedReferenceBinding)
510 exceptions[i] = (ReferenceBinding) binaryType.resolveType(exceptions[i]);
513 private ReferenceBinding runtimeException() {
514 if (runtimeException == null)
515 this.runtimeException = this.type.scope.getJavaLangRuntimeException();
516 return runtimeException;
518 public void verify(SourceTypeBinding someType) {
519 this.type = someType;
520 this.computeMethods();
521 this.computeInheritedMethods();
524 public String toString() {
525 StringBuffer buffer = new StringBuffer(10);
526 buffer.append("MethodVerifier for type: "); //$NON-NLS-1$
527 buffer.append(type.readableName());
529 buffer.append("\t-inherited methods: "); //$NON-NLS-1$
530 buffer.append(this.inheritedMethods);
531 return buffer.toString();