synchronized from quantum plugin
[phpeclipse.git] / net.sourceforge.phpeclipse / src / net / sourceforge / phpdt / internal / compiler / lookup / MethodVerifier.java
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
7  * 
8  * Contributors:
9  *     IBM Corporation - initial API and implementation
10  *******************************************************************************/
11 package net.sourceforge.phpdt.internal.compiler.lookup;
12
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;
18
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;
26 /*
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)
38         - with nested types:
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)
42 */
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;
50 }
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;
57
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);
62                 } else {
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);
72                 }
73         }
74 }
75 /*
76 "8.4.4"
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).
80 */
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]));
88                 if (j == -1)
89                         if (!(newException.isCompatibleWith(this.runtimeException()) || newException.isCompatibleWith(this.errorException())))
90                                 this.problemReporter(newMethod).incompatibleExceptionInThrowsClause(this.type, newMethod, inheritedMethod, newException);
91         }
92 }
93 private void checkInheritedMethods(MethodBinding[] methods, int length) {
94         TypeBinding returnType = methods[0].returnType;
95         int index = length;
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);
99                 return;
100         }
101
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];
107                                 break;
108                         }
109                 }
110         }
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
115
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]);
120                         } else {
121                                 this.problemReporter().abstractMethodMustBeImplemented(this.type, methods[0]);
122                         }
123                 }
124                 return;
125         }
126
127         MethodBinding[] abstractMethods = new MethodBinding[length - 1];
128         index = 0;
129         for (int i = length; --i >= 0;)
130                 if (methods[i] != concreteMethod)
131                         abstractMethods[index++] = methods[i];
132
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]);
143 }
144 /*
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
149         else
150                 if inherited methods = 1
151                         if inherited is abstract && type is NOT an interface or abstract, complain
152                 else
153                         if vm signatures do not match complain
154                         else
155                                 find the concrete implementation amongst the abstract methods (can only be 1)
156                                 if one exists then
157                                         it must be a public instance method
158                                         compare concrete's exceptions against each abstract method
159                                 else
160                                         complain about missing implementation only if type is NOT an interface or abstract
161 */
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];
169
170                         int index = -1;
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
180                                                 }
181                                         }
182                                         if (index >= 0)
183                                                 this.checkAgainstInheritedMethods(currentMethod, matchingInherited, index + 1); // pass in the length of matching
184                                 }
185                         }
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
194                                                 }
195                                         }
196                                 }
197                                 if (index > 0) {
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]);
205                                                 } else {
206                                                         this.problemReporter().abstractMethodMustBeImplemented(this.type, matchingInherited[0]);
207                                                 }
208                                         }
209                                 }
210                         }
211                 }
212         }
213 }
214 private void checkPackagePrivateAbstractMethod(MethodBinding abstractMethod) {
215         ReferenceBinding superType = this.type.superclass();
216         char[] selector = abstractMethod.selector;
217         do {
218                 if (!superType.isValidBinding()) return;
219                 if (!superType.isAbstract()) return; // closer non abstract super type will be flagged instead
220
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))
225                                 continue nextMethod;
226                         if (method.isPrivate() || method.isConstructor() || method.isDefaultAbstract())
227                                 continue nextMethod;
228                         if (superType.fPackage == abstractMethod.declaringClass.fPackage) return; // found concrete implementation of abstract method in same package
229                 }
230         } while ((superType = superType.superclass()) != abstractMethod.declaringClass);
231
232         // non visible abstract methods cannot be overridden so the type must be defined abstract
233         this.problemReporter().abstractMethodCannotBeOverridden(this.type, abstractMethod);
234 }
235 /*
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
246 */
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();
252
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;
258
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;
266                         }
267
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);
278                                                                 continue nextMethod;
279                                                         }
280                                                 }
281                                         }
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])) 
287                                                                         continue nextMethod;
288
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];
292                                                 else
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);
297                                         } else {
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;
304
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);
307
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);
313                                                                         break foundMatch;
314                                                                 }
315                                                         }
316                                                 }
317                                         }
318                                 }
319                         }
320                         superType = superType.superclass();
321                 }
322         }
323
324         for (int i = 0; i <= lastPosition; i++) {
325                 ReferenceBinding[] interfaces = interfacesToVisit[i];
326                 if (interfaces==null) {
327                         interfaces = new ReferenceBinding[0];
328                 }
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;
339                                         }
340
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];
347                                                 else
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);
352                                         }
353                                 }
354                         }
355                 }
356         }
357
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];
363                 }
364                 for (int j = 0, length = interfaces.length; j < length; j++)
365                         interfaces[j].tagBits &= ~InterfaceVisited;
366         }
367 }
368 private void computeMethods() {
369         MethodBinding[] methods = type.methods();
370         if (methods==null) {
371                 methods = new MethodBinding[0];
372         }
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];
381                         else
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);
386                 }
387         }
388 }
389 private ReferenceBinding errorException() {
390         if (errorException == null)
391                 this.errorException = this.type.scope.getJavaLangError();
392         return errorException;
393 }
394 private boolean isAsVisible(MethodBinding newMethod, MethodBinding inheritedMethod) {
395         if (inheritedMethod.modifiers == newMethod.modifiers) return true;
396
397         if (newMethod.isPublic()) return true;          // Covers everything
398         if (inheritedMethod.isPublic()) return false;
399
400         if (newMethod.isProtected()) return true;
401         if (inheritedMethod.isProtected()) return false;
402
403         return !newMethod.isPrivate();          // The inheritedMethod cannot be private since it would not be visible
404 }
405 private boolean isSameClassOrSubclassOf(ReferenceBinding testClass, ReferenceBinding superclass) {
406         do {
407                 if (testClass == superclass) return true;
408         } while ((testClass = testClass.superclass()) != null);
409         return false;
410 }
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
419         } else {
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
423                                 return true;
424                 }
425                 while (superclass.isAbstract() && !superclass.implementsInterface(declaringClass, false))
426                         superclass = superclass.superclass(); // find the first concrete superclass or the superclass which implements the interface
427         }
428         return superclass.isAbstract();         // if it is a concrete class then we have already reported problem against it
429 }
430 private ProblemReporter problemReporter() {
431         return this.type.scope.problemReporter();
432 }
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();
437         return reporter;
438 }
439 private ReferenceBinding runtimeException() {
440         if (runtimeException == null)
441                 this.runtimeException = this.type.scope.getJavaLangRuntimeException();
442         return runtimeException;
443 }
444 public void verify(SourceTypeBinding type) {
445         this.type = type;
446         this.computeMethods();
447         this.computeInheritedMethods();
448         this.checkMethods();
449 }
450 public String toString() {
451         StringBuffer buffer = new StringBuffer(10);
452         buffer.append("MethodVerifier for type: "); //$NON-NLS-1$
453         buffer.append(type.readableName());
454         buffer.append('\n');
455         buffer.append("\t-inherited methods: "); //$NON-NLS-1$
456         buffer.append(this.inheritedMethods);
457         return buffer.toString();
458 }
459 }