1 /*******************************************************************************
2 * Copyright (c) 2000, 2003 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 net.sourceforge.phpdt.internal.compiler.lookup;
13 import net.sourceforge.phpdt.core.compiler.CharOperation;
14 import net.sourceforge.phpdt.internal.compiler.problem.ProblemReporter;
15 import net.sourceforge.phpdt.internal.compiler.util.HashtableOfObject;
16 import net.sourceforge.phpeclipse.internal.compiler.ast.MethodDeclaration;
17 import net.sourceforge.phpeclipse.internal.compiler.ast.TypeDeclaration;
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 void checkAgainstInheritedMethods(MethodBinding currentMethod, MethodBinding[] methods, int length) {
52 currentMethod.modifiers |= CompilerModifiers.AccOverriding;
53 for (int i = length; --i >= 0;) {
54 MethodBinding inheritedMethod = methods[i];
55 if (!currentMethod.isAbstract() && inheritedMethod.isAbstract())
56 currentMethod.modifiers |= CompilerModifiers.AccImplementing;
58 if (currentMethod.returnType != inheritedMethod.returnType) {
59 this.problemReporter(currentMethod).incompatibleReturnType(currentMethod, inheritedMethod);
60 } else if (currentMethod.isStatic() != inheritedMethod.isStatic()) { // Cannot override a static method or hide an instance method
61 this.problemReporter(currentMethod).staticAndInstanceConflict(currentMethod, inheritedMethod);
63 if (currentMethod.thrownExceptions != NoExceptions)
64 this.checkExceptions(currentMethod, inheritedMethod);
65 if (inheritedMethod.isFinal())
66 this.problemReporter(currentMethod).finalMethodCannotBeOverridden(currentMethod, inheritedMethod);
67 if (!this.isAsVisible(currentMethod, inheritedMethod))
68 this.problemReporter(currentMethod).visibilityConflict(currentMethod, inheritedMethod);
69 // if (inheritedMethod.isViewedAsDeprecated())
70 // if (!currentMethod.isViewedAsDeprecated() || environment.options.reportDeprecationInsideDeprecatedCode)
71 // this.problemReporter(currentMethod).overridesDeprecatedMethod(currentMethod, inheritedMethod);
77 Verify that newExceptions are all included in inheritedExceptions.
78 Assumes all exceptions are valid and throwable.
79 Unchecked exceptions (compatible with runtime & error) are ignored (see the spec on pg. 203).
81 private void checkExceptions(MethodBinding newMethod, MethodBinding inheritedMethod) {
82 ReferenceBinding[] newExceptions = newMethod.thrownExceptions;
83 ReferenceBinding[] inheritedExceptions = inheritedMethod.thrownExceptions;
84 for (int i = newExceptions.length; --i >= 0;) {
85 ReferenceBinding newException = newExceptions[i];
86 int j = inheritedExceptions.length;
87 while (--j > -1 && !this.isSameClassOrSubclassOf(newException, inheritedExceptions[j]));
89 if (!(newException.isCompatibleWith(this.runtimeException()) || newException.isCompatibleWith(this.errorException())))
90 this.problemReporter(newMethod).incompatibleExceptionInThrowsClause(this.type, newMethod, inheritedMethod, newException);
93 private void checkInheritedMethods(MethodBinding[] methods, int length) {
94 TypeBinding returnType = methods[0].returnType;
96 while (--index > 0 && returnType == methods[index].returnType);
97 if (index > 0) { // All inherited methods do NOT have the same vmSignature
98 this.problemReporter().inheritedMethodsHaveIncompatibleReturnTypes(this.type, methods, length);
102 MethodBinding concreteMethod = null;
103 if (!type.isInterface()) { // ignore concrete methods for interfaces
104 for (int i = length; --i >= 0;) { // Remember that only one of the methods can be non-abstract
105 if (!methods[i].isAbstract()) {
106 concreteMethod = methods[i];
111 if (concreteMethod == null) {
112 if (this.type.isClass() && !this.type.isAbstract()) {
113 for (int i = length; --i >= 0;)
114 if (!mustImplementAbstractMethod(methods[i])) return; // have already reported problem against the concrete superclass
116 TypeDeclaration typeDeclaration = this.type.scope.referenceContext;
117 if (typeDeclaration != null) {
118 MethodDeclaration missingAbstractMethod = typeDeclaration.addMissingAbstractMethodFor(methods[0]);
119 missingAbstractMethod.scope.problemReporter().abstractMethodMustBeImplemented(this.type, methods[0]);
121 this.problemReporter().abstractMethodMustBeImplemented(this.type, methods[0]);
127 MethodBinding[] abstractMethods = new MethodBinding[length - 1];
129 for (int i = length; --i >= 0;)
130 if (methods[i] != concreteMethod)
131 abstractMethods[index++] = methods[i];
133 // Remember that interfaces can only define public instance methods
134 if (concreteMethod.isStatic())
135 // Cannot inherit a static method which is specified as an instance method by an interface
136 this.problemReporter().staticInheritedMethodConflicts(type, concreteMethod, abstractMethods);
137 if (!concreteMethod.isPublic())
138 // Cannot reduce visibility of a public method specified by an interface
139 this.problemReporter().inheritedMethodReducesVisibility(type, concreteMethod, abstractMethods);
140 if (concreteMethod.thrownExceptions != NoExceptions)
141 for (int i = abstractMethods.length; --i >= 0;)
142 this.checkExceptions(concreteMethod, abstractMethods[i]);
145 For each inherited method identifier (message pattern - vm signature minus the return type)
146 if current method exists
147 if current's vm signature does not match an inherited signature then complain
148 else compare current's exceptions & visibility against each inherited method
150 if inherited methods = 1
151 if inherited is abstract && type is NOT an interface or abstract, complain
153 if vm signatures do not match complain
155 find the concrete implementation amongst the abstract methods (can only be 1)
157 it must be a public instance method
158 compare concrete's exceptions against each abstract method
160 complain about missing implementation only if type is NOT an interface or abstract
162 private void checkMethods() {
163 boolean mustImplementAbstractMethods = this.type.isClass() && !this.type.isAbstract();
164 char[][] methodSelectors = this.inheritedMethods.keyTable;
165 for (int s = methodSelectors.length; --s >= 0;) {
166 if (methodSelectors[s] != null) {
167 MethodBinding[] current = (MethodBinding[]) this.currentMethods.get(methodSelectors[s]);
168 MethodBinding[] inherited = (MethodBinding[]) this.inheritedMethods.valueTable[s];
171 MethodBinding[] matchingInherited = new MethodBinding[inherited.length];
172 if (current != null) {
173 for (int i = 0, length1 = current.length; i < length1; i++) {
174 while (index >= 0) matchingInherited[index--] = null; // clear the previous contents of the matching methods
175 MethodBinding currentMethod = current[i];
176 for (int j = 0, length2 = inherited.length; j < length2; j++) {
177 if (inherited[j] != null && currentMethod.areParametersEqual(inherited[j])) {
178 matchingInherited[++index] = inherited[j];
179 inherited[j] = null; // do not want to find it again
183 this.checkAgainstInheritedMethods(currentMethod, matchingInherited, index + 1); // pass in the length of matching
186 for (int i = 0, length = inherited.length; i < length; i++) {
187 while (index >= 0) matchingInherited[index--] = null; // clear the previous contents of the matching methods
188 if (inherited[i] != null) {
189 matchingInherited[++index] = inherited[i];
190 for (int j = i + 1; j < length; j++) {
191 if (inherited[j] != null && inherited[i].areParametersEqual(inherited[j])) {
192 matchingInherited[++index] = inherited[j];
193 inherited[j] = null; // do not want to find it again
198 this.checkInheritedMethods(matchingInherited, index + 1); // pass in the length of matching
199 } else if (mustImplementAbstractMethods && index == 0 && matchingInherited[0].isAbstract()) {
200 if (mustImplementAbstractMethod(matchingInherited[0])) {
201 TypeDeclaration typeDeclaration = this.type.scope.referenceContext;
202 if (typeDeclaration != null) {
203 MethodDeclaration missingAbstractMethod = typeDeclaration.addMissingAbstractMethodFor(matchingInherited[0]);
204 missingAbstractMethod.scope.problemReporter().abstractMethodMustBeImplemented(this.type, matchingInherited[0]);
206 this.problemReporter().abstractMethodMustBeImplemented(this.type, matchingInherited[0]);
214 private void checkPackagePrivateAbstractMethod(MethodBinding abstractMethod) {
215 ReferenceBinding superType = this.type.superclass();
216 char[] selector = abstractMethod.selector;
218 if (!superType.isValidBinding()) return;
219 if (!superType.isAbstract()) return; // closer non abstract super type will be flagged instead
221 MethodBinding[] methods = superType.getMethods(selector);
222 nextMethod : for (int m = methods.length; --m >= 0;) {
223 MethodBinding method = methods[m];
224 if (method.returnType != abstractMethod.returnType || !method.areParametersEqual(abstractMethod))
226 if (method.isPrivate() || method.isConstructor() || method.isDefaultAbstract())
228 if (superType.fPackage == abstractMethod.declaringClass.fPackage) return; // found concrete implementation of abstract method in same package
230 } while ((superType = superType.superclass()) != abstractMethod.declaringClass);
232 // non visible abstract methods cannot be overridden so the type must be defined abstract
233 this.problemReporter().abstractMethodCannotBeOverridden(this.type, abstractMethod);
236 Binding creation is responsible for reporting:
237 - all modifier problems (duplicates & multiple visibility modifiers + incompatible combinations)
238 - plus invalid modifiers given the context... examples:
239 - interface methods can only be public
240 - abstract methods can only be defined by abstract classes
241 - collisions... 2 methods with identical vmSelectors
242 - multiple methods with the same message pattern but different return types
243 - ambiguous, invisible or missing return/argument/exception types
244 - check the type of any array is not void
245 - check that each exception type is Throwable or a subclass of it
247 private void computeInheritedMethods() {
248 this.inheritedMethods = new HashtableOfObject(51); // maps method selectors to an array of methods... must search to match paramaters & return type
249 ReferenceBinding[][] interfacesToVisit = new ReferenceBinding[5][];
250 int lastPosition = 0;
251 interfacesToVisit[lastPosition] = type.superInterfaces();
253 ReferenceBinding superType = this.type.isClass()
254 ? this.type.superclass()
255 : this.type.scope.getJavaLangObject(); // check interface methods against Object
256 MethodBinding[] nonVisibleDefaultMethods = null;
257 int nonVisibleCount = 0;
259 while (superType != null) {
260 if (superType.isValidBinding()) {
261 ReferenceBinding[] itsInterfaces = superType.superInterfaces();
262 if (itsInterfaces != NoSuperInterfaces) {
263 if (++lastPosition == interfacesToVisit.length)
264 System.arraycopy(interfacesToVisit, 0, interfacesToVisit = new ReferenceBinding[lastPosition * 2][], 0, lastPosition);
265 interfacesToVisit[lastPosition] = itsInterfaces;
268 MethodBinding[] methods = superType.methods();
269 nextMethod : for (int m = methods.length; --m >= 0;) {
270 MethodBinding method = methods[m];
271 if (!(method.isPrivate() || method.isConstructor() || method.isDefaultAbstract())) { // look at all methods which are NOT private or constructors or default abstract
272 MethodBinding[] existingMethods = (MethodBinding[]) this.inheritedMethods.get(method.selector);
273 if (existingMethods != null) {
274 for (int i = 0, length = existingMethods.length; i < length; i++) {
275 if (method.returnType == existingMethods[i].returnType && method.areParametersEqual(existingMethods[i])) {
276 if (method.isDefault() && method.isAbstract() && method.declaringClass.fPackage != type.fPackage)
277 checkPackagePrivateAbstractMethod(method);
282 if (nonVisibleDefaultMethods != null)
283 for (int i = 0; i < nonVisibleCount; i++)
284 if (method.returnType == nonVisibleDefaultMethods[i].returnType
285 && CharOperation.equals(method.selector, nonVisibleDefaultMethods[i].selector)
286 && method.areParametersEqual(nonVisibleDefaultMethods[i]))
289 if (!(method.isDefault() && method.declaringClass.fPackage != type.fPackage)) { // ignore methods which have default visibility and are NOT defined in another package
290 if (existingMethods == null)
291 existingMethods = new MethodBinding[1];
293 System.arraycopy(existingMethods, 0,
294 (existingMethods = new MethodBinding[existingMethods.length + 1]), 0, existingMethods.length - 1);
295 existingMethods[existingMethods.length - 1] = method;
296 this.inheritedMethods.put(method.selector, existingMethods);
298 if (nonVisibleDefaultMethods == null)
299 nonVisibleDefaultMethods = new MethodBinding[10];
300 else if (nonVisibleCount == nonVisibleDefaultMethods.length)
301 System.arraycopy(nonVisibleDefaultMethods, 0,
302 (nonVisibleDefaultMethods = new MethodBinding[nonVisibleCount * 2]), 0, nonVisibleCount);
303 nonVisibleDefaultMethods[nonVisibleCount++] = method;
305 if (method.isAbstract() && !this.type.isAbstract()) // non visible abstract methods cannot be overridden so the type must be defined abstract
306 this.problemReporter().abstractMethodCannotBeOverridden(this.type, method);
308 MethodBinding[] current = (MethodBinding[]) this.currentMethods.get(method.selector);
309 if (current != null) { // non visible methods cannot be overridden so a warning is issued
310 foundMatch : for (int i = 0, length = current.length; i < length; i++) {
311 if (method.returnType == current[i].returnType && method.areParametersEqual(current[i])) {
312 this.problemReporter().overridesPackageDefaultMethod(current[i], method);
320 superType = superType.superclass();
324 for (int i = 0; i <= lastPosition; i++) {
325 ReferenceBinding[] interfaces = interfacesToVisit[i];
326 if (interfaces==null) {
327 interfaces = new ReferenceBinding[0];
329 for (int j = 0, length = interfaces.length; j < length; j++) {
330 superType = interfaces[j];
331 if ((superType.tagBits & InterfaceVisited) == 0) {
332 superType.tagBits |= InterfaceVisited;
333 if (superType.isValidBinding()) {
334 ReferenceBinding[] itsInterfaces = superType.superInterfaces();
335 if (itsInterfaces != NoSuperInterfaces) {
336 if (++lastPosition == interfacesToVisit.length)
337 System.arraycopy(interfacesToVisit, 0, interfacesToVisit = new ReferenceBinding[lastPosition * 2][], 0, lastPosition);
338 interfacesToVisit[lastPosition] = itsInterfaces;
341 MethodBinding[] methods = superType.methods();
342 for (int m = methods.length; --m >= 0;) { // Interface methods are all abstract public
343 MethodBinding method = methods[m];
344 MethodBinding[] existingMethods = (MethodBinding[]) this.inheritedMethods.get(method.selector);
345 if (existingMethods == null)
346 existingMethods = new MethodBinding[1];
348 System.arraycopy(existingMethods, 0,
349 (existingMethods = new MethodBinding[existingMethods.length + 1]), 0, existingMethods.length - 1);
350 existingMethods[existingMethods.length - 1] = method;
351 this.inheritedMethods.put(method.selector, existingMethods);
358 // bit reinitialization
359 for (int i = 0; i <= lastPosition; i++) {
360 ReferenceBinding[] interfaces = interfacesToVisit[i];
361 if (interfaces==null) {
362 interfaces = new ReferenceBinding[0];
364 for (int j = 0, length = interfaces.length; j < length; j++)
365 interfaces[j].tagBits &= ~InterfaceVisited;
368 private void computeMethods() {
369 MethodBinding[] methods = type.methods();
371 methods = new MethodBinding[0];
373 int size = methods.length;
374 this.currentMethods = new HashtableOfObject(size == 0 ? 1 : size); // maps method selectors to an array of methods... must search to match paramaters & return type
375 for (int m = size; --m >= 0;) {
376 MethodBinding method = methods[m];
377 if (!(method.isConstructor() || method.isDefaultAbstract())) { // keep all methods which are NOT constructors or default abstract
378 MethodBinding[] existingMethods = (MethodBinding[]) this.currentMethods.get(method.selector);
379 if (existingMethods == null)
380 existingMethods = new MethodBinding[1];
382 System.arraycopy(existingMethods, 0,
383 (existingMethods = new MethodBinding[existingMethods.length + 1]), 0, existingMethods.length - 1);
384 existingMethods[existingMethods.length - 1] = method;
385 this.currentMethods.put(method.selector, existingMethods);
389 private ReferenceBinding errorException() {
390 if (errorException == null)
391 this.errorException = this.type.scope.getJavaLangError();
392 return errorException;
394 private boolean isAsVisible(MethodBinding newMethod, MethodBinding inheritedMethod) {
395 if (inheritedMethod.modifiers == newMethod.modifiers) return true;
397 if (newMethod.isPublic()) return true; // Covers everything
398 if (inheritedMethod.isPublic()) return false;
400 if (newMethod.isProtected()) return true;
401 if (inheritedMethod.isProtected()) return false;
403 return !newMethod.isPrivate(); // The inheritedMethod cannot be private since it would not be visible
405 private boolean isSameClassOrSubclassOf(ReferenceBinding testClass, ReferenceBinding superclass) {
407 if (testClass == superclass) return true;
408 } while ((testClass = testClass.superclass()) != null);
411 private boolean mustImplementAbstractMethod(MethodBinding abstractMethod) {
412 // if the type's superclass is an abstract class, then all abstract methods must be implemented
413 // otherwise, skip it if the type's superclass must implement any of the inherited methods
414 ReferenceBinding superclass = this.type.superclass();
415 ReferenceBinding declaringClass = abstractMethod.declaringClass;
416 if (declaringClass.isClass()) {
417 while (superclass.isAbstract() && superclass != declaringClass)
418 superclass = superclass.superclass(); // find the first concrete superclass or the abstract declaringClass
420 if (this.type.implementsInterface(declaringClass, false)) {
421 if (this.type.isAbstract()) return false; // leave it for the subclasses
422 if (!superclass.implementsInterface(declaringClass, true)) // only if a superclass does not also implement the interface
425 while (superclass.isAbstract() && !superclass.implementsInterface(declaringClass, false))
426 superclass = superclass.superclass(); // find the first concrete superclass or the superclass which implements the interface
428 return superclass.isAbstract(); // if it is a concrete class then we have already reported problem against it
430 private ProblemReporter problemReporter() {
431 return this.type.scope.problemReporter();
433 private ProblemReporter problemReporter(MethodBinding currentMethod) {
434 ProblemReporter reporter = problemReporter();
435 if (currentMethod.declaringClass == type) // only report against the currentMethod if its implemented by the type
436 reporter.referenceContext = currentMethod.sourceMethod();
439 private ReferenceBinding runtimeException() {
440 if (runtimeException == null)
441 this.runtimeException = this.type.scope.getJavaLangRuntimeException();
442 return runtimeException;
444 public void verify(SourceTypeBinding type) {
446 this.computeMethods();
447 this.computeInheritedMethods();
450 public String toString() {
451 StringBuffer buffer = new StringBuffer(10);
452 buffer.append("MethodVerifier for type: "); //$NON-NLS-1$
453 buffer.append(type.readableName());
455 buffer.append("\t-inherited methods: "); //$NON-NLS-1$
456 buffer.append(this.inheritedMethods);
457 return buffer.toString();