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.ast.MethodDeclaration;
15 import net.sourceforge.phpdt.internal.compiler.ast.TypeDeclaration;
16 import net.sourceforge.phpdt.internal.compiler.problem.ProblemReporter;
17 import net.sourceforge.phpdt.internal.compiler.util.HashtableOfObject;
19 public final class MethodVerifier implements TagBits, TypeConstants {
20 SourceTypeBinding type;
22 HashtableOfObject inheritedMethods;
24 HashtableOfObject currentMethods;
26 ReferenceBinding runtimeException;
28 ReferenceBinding errorException;
30 LookupEnvironment environment;
33 * Binding creation is responsible for reporting all problems with types: -
34 * all modifier problems (duplicates & multiple visibility modifiers +
35 * incompatible combinations - abstract/final) - plus invalid modifiers
36 * given the context (the verifier did not do this before) - qualified name
37 * collisions between a type and a package (types in default packages are
38 * excluded) - all type hierarchy problems: - cycles in the superclass or
39 * superinterface hierarchy - an ambiguous, invisible or missing superclass
40 * or superinterface - extending a final class - extending an interface
41 * instead of a class - implementing a class instead of an interface -
42 * implementing the same interface more than once (ie. duplicate interfaces) -
43 * with nested types: - shadowing an enclosing type's source name - defining
44 * a static class or interface inside a non-static nested class - defining
45 * an interface as a local type (local types can only be classes)
47 public MethodVerifier(LookupEnvironment environment) {
48 this.type = null; // Initialized with the public method
49 // verify(SourceTypeBinding)
50 this.inheritedMethods = null;
51 this.currentMethods = null;
52 this.runtimeException = null;
53 this.errorException = null;
54 this.environment = environment;
57 private void checkAgainstInheritedMethods(MethodBinding currentMethod,
58 MethodBinding[] methods, int length) {
59 currentMethod.modifiers |= CompilerModifiers.AccOverriding;
60 for (int i = length; --i >= 0;) {
61 MethodBinding inheritedMethod = methods[i];
62 if (!currentMethod.isAbstract() && inheritedMethod.isAbstract())
63 currentMethod.modifiers |= CompilerModifiers.AccImplementing;
65 if (currentMethod.returnType != inheritedMethod.returnType) {
66 this.problemReporter(currentMethod).incompatibleReturnType(
67 currentMethod, inheritedMethod);
68 } else if (currentMethod.isStatic() != inheritedMethod.isStatic()) { // Cannot
78 this.problemReporter(currentMethod).staticAndInstanceConflict(
79 currentMethod, inheritedMethod);
81 if (currentMethod.thrownExceptions != NoExceptions)
82 this.checkExceptions(currentMethod, inheritedMethod);
83 if (inheritedMethod.isFinal())
84 this.problemReporter(currentMethod)
85 .finalMethodCannotBeOverridden(currentMethod,
87 if (!this.isAsVisible(currentMethod, inheritedMethod))
88 this.problemReporter(currentMethod).visibilityConflict(
89 currentMethod, inheritedMethod);
90 // if (inheritedMethod.isViewedAsDeprecated())
91 // if (!currentMethod.isViewedAsDeprecated() ||
92 // environment.options.reportDeprecationInsideDeprecatedCode)
93 // this.problemReporter(currentMethod).overridesDeprecatedMethod(currentMethod,
100 * "8.4.4" Verify that newExceptions are all included in
101 * inheritedExceptions. Assumes all exceptions are valid and throwable.
102 * Unchecked exceptions (compatible with runtime & error) are ignored (see
103 * the spec on pg. 203).
105 private void checkExceptions(MethodBinding newMethod,
106 MethodBinding inheritedMethod) {
107 ReferenceBinding[] newExceptions = newMethod.thrownExceptions;
108 ReferenceBinding[] inheritedExceptions = inheritedMethod.thrownExceptions;
109 for (int i = newExceptions.length; --i >= 0;) {
110 ReferenceBinding newException = newExceptions[i];
111 int j = inheritedExceptions.length;
113 && !this.isSameClassOrSubclassOf(newException,
114 inheritedExceptions[j]))
117 if (!(newException.isCompatibleWith(this.runtimeException()) || newException
118 .isCompatibleWith(this.errorException())))
119 this.problemReporter(newMethod)
120 .incompatibleExceptionInThrowsClause(this.type,
121 newMethod, inheritedMethod, newException);
125 private void checkInheritedMethods(MethodBinding[] methods, int length) {
126 TypeBinding returnType = methods[0].returnType;
128 while (--index > 0 && returnType == methods[index].returnType)
130 if (index > 0) { // All inherited methods do NOT have the same
132 this.problemReporter().inheritedMethodsHaveIncompatibleReturnTypes(
133 this.type, methods, length);
137 MethodBinding concreteMethod = null;
138 if (!type.isInterface()) { // ignore concrete methods for interfaces
139 for (int i = length; --i >= 0;) { // Remember that only one of the
140 // methods can be non-abstract
141 if (!methods[i].isAbstract()) {
142 concreteMethod = methods[i];
147 if (concreteMethod == null) {
148 if (this.type.isClass() && !this.type.isAbstract()) {
149 for (int i = length; --i >= 0;)
150 if (!mustImplementAbstractMethod(methods[i]))
151 return; // have already reported problem against the
152 // concrete superclass
154 TypeDeclaration typeDeclaration = this.type.scope.referenceContext;
155 if (typeDeclaration != null) {
156 MethodDeclaration missingAbstractMethod = typeDeclaration
157 .addMissingAbstractMethodFor(methods[0]);
158 missingAbstractMethod.scope.problemReporter()
159 .abstractMethodMustBeImplemented(this.type,
162 this.problemReporter().abstractMethodMustBeImplemented(
163 this.type, methods[0]);
169 MethodBinding[] abstractMethods = new MethodBinding[length - 1];
171 for (int i = length; --i >= 0;)
172 if (methods[i] != concreteMethod)
173 abstractMethods[index++] = methods[i];
175 // Remember that interfaces can only define public instance methods
176 if (concreteMethod.isStatic())
177 // Cannot inherit a static method which is specified as an instance
178 // method by an interface
179 this.problemReporter().staticInheritedMethodConflicts(type,
180 concreteMethod, abstractMethods);
181 if (!concreteMethod.isPublic())
182 // Cannot reduce visibility of a public method specified by an
184 this.problemReporter().inheritedMethodReducesVisibility(type,
185 concreteMethod, abstractMethods);
186 if (concreteMethod.thrownExceptions != NoExceptions)
187 for (int i = abstractMethods.length; --i >= 0;)
188 this.checkExceptions(concreteMethod, abstractMethods[i]);
192 * For each inherited method identifier (message pattern - vm signature
193 * minus the return type) if current method exists if current's vm signature
194 * does not match an inherited signature then complain else compare
195 * current's exceptions & visibility against each inherited method else if
196 * inherited methods = 1 if inherited is abstract && type is NOT an
197 * interface or abstract, complain else if vm signatures do not match
198 * complain else find the concrete implementation amongst the abstract
199 * methods (can only be 1) if one exists then it must be a public instance
200 * method compare concrete's exceptions against each abstract method else
201 * complain about missing implementation only if type is NOT an interface or
204 private void checkMethods() {
205 boolean mustImplementAbstractMethods = this.type.isClass()
206 && !this.type.isAbstract();
207 char[][] methodSelectors = this.inheritedMethods.keyTable;
208 for (int s = methodSelectors.length; --s >= 0;) {
209 if (methodSelectors[s] != null) {
210 MethodBinding[] current = (MethodBinding[]) this.currentMethods
211 .get(methodSelectors[s]);
212 MethodBinding[] inherited = (MethodBinding[]) this.inheritedMethods.valueTable[s];
215 MethodBinding[] matchingInherited = new MethodBinding[inherited.length];
216 if (current != null) {
217 for (int i = 0, length1 = current.length; i < length1; i++) {
219 matchingInherited[index--] = null; // clear the
224 MethodBinding currentMethod = current[i];
225 for (int j = 0, length2 = inherited.length; j < length2; j++) {
226 if (inherited[j] != null
228 .areParametersEqual(inherited[j])) {
229 matchingInherited[++index] = inherited[j];
230 inherited[j] = null; // do not want to find
235 this.checkAgainstInheritedMethods(currentMethod,
236 matchingInherited, index + 1); // pass in
242 for (int i = 0, length = inherited.length; i < length; i++) {
244 matchingInherited[index--] = null; // clear the
248 if (inherited[i] != null) {
249 matchingInherited[++index] = inherited[i];
250 for (int j = i + 1; j < length; j++) {
251 if (inherited[j] != null
253 .areParametersEqual(inherited[j])) {
254 matchingInherited[++index] = inherited[j];
255 inherited[j] = null; // do not want to find
262 .checkInheritedMethods(matchingInherited,
263 index + 1); // pass in the length of
265 } else if (mustImplementAbstractMethods && index == 0
266 && matchingInherited[0].isAbstract()) {
267 if (mustImplementAbstractMethod(matchingInherited[0])) {
268 TypeDeclaration typeDeclaration = this.type.scope.referenceContext;
269 if (typeDeclaration != null) {
270 MethodDeclaration missingAbstractMethod = typeDeclaration
271 .addMissingAbstractMethodFor(matchingInherited[0]);
272 missingAbstractMethod.scope
274 .abstractMethodMustBeImplemented(
275 this.type, matchingInherited[0]);
279 .abstractMethodMustBeImplemented(
280 this.type, matchingInherited[0]);
289 private void checkPackagePrivateAbstractMethod(MethodBinding abstractMethod) {
290 ReferenceBinding superType = this.type.superclass();
291 char[] selector = abstractMethod.selector;
293 if (!superType.isValidBinding())
295 if (!superType.isAbstract())
296 return; // closer non abstract super type will be flagged
299 MethodBinding[] methods = superType.getMethods(selector);
300 nextMethod: for (int m = methods.length; --m >= 0;) {
301 MethodBinding method = methods[m];
302 if (method.returnType != abstractMethod.returnType
303 || !method.areParametersEqual(abstractMethod))
305 if (method.isPrivate() || method.isConstructor()
306 || method.isDefaultAbstract())
308 if (superType.fPackage == abstractMethod.declaringClass.fPackage)
309 return; // found concrete implementation of abstract method
312 } while ((superType = superType.superclass()) != abstractMethod.declaringClass);
314 // non visible abstract methods cannot be overridden so the type must be
316 this.problemReporter().abstractMethodCannotBeOverridden(this.type,
321 * Binding creation is responsible for reporting: - all modifier problems
322 * (duplicates & multiple visibility modifiers + incompatible combinations) -
323 * plus invalid modifiers given the context... examples: - interface methods
324 * can only be public - abstract methods can only be defined by abstract
325 * classes - collisions... 2 methods with identical vmSelectors - multiple
326 * methods with the same message pattern but different return types -
327 * ambiguous, invisible or missing return/argument/exception types - check
328 * the type of any array is not void - check that each exception type is
329 * Throwable or a subclass of it
331 private void computeInheritedMethods() {
332 this.inheritedMethods = new HashtableOfObject(51); // maps method
339 ReferenceBinding[][] interfacesToVisit = new ReferenceBinding[5][];
340 int lastPosition = 0;
341 interfacesToVisit[lastPosition] = type.superInterfaces();
343 ReferenceBinding superType = this.type.isClass() ? this.type
344 .superclass() : this.type.scope.getJavaLangObject(); // check
349 MethodBinding[] nonVisibleDefaultMethods = null;
350 int nonVisibleCount = 0;
352 while (superType != null) {
353 if (superType.isValidBinding()) {
354 ReferenceBinding[] itsInterfaces = superType.superInterfaces();
355 if (itsInterfaces != NoSuperInterfaces) {
356 if (++lastPosition == interfacesToVisit.length)
361 interfacesToVisit = new ReferenceBinding[lastPosition * 2][],
363 interfacesToVisit[lastPosition] = itsInterfaces;
366 MethodBinding[] methods = superType.methods();
367 nextMethod: for (int m = methods.length; --m >= 0;) {
368 MethodBinding method = methods[m];
369 if (!(method.isPrivate() || method.isConstructor() || method
370 .isDefaultAbstract())) { // look at all methods
371 // which are NOT private
372 // or constructors or
374 MethodBinding[] existingMethods = (MethodBinding[]) this.inheritedMethods
375 .get(method.selector);
376 if (existingMethods != null) {
377 for (int i = 0, length = existingMethods.length; i < length; i++) {
378 if (method.returnType == existingMethods[i].returnType
380 .areParametersEqual(existingMethods[i])) {
381 if (method.isDefault()
382 && method.isAbstract()
383 && method.declaringClass.fPackage != type.fPackage)
384 checkPackagePrivateAbstractMethod(method);
389 if (nonVisibleDefaultMethods != null)
390 for (int i = 0; i < nonVisibleCount; i++)
391 if (method.returnType == nonVisibleDefaultMethods[i].returnType
395 nonVisibleDefaultMethods[i].selector)
397 .areParametersEqual(nonVisibleDefaultMethods[i]))
400 if (!(method.isDefault() && method.declaringClass.fPackage != type.fPackage)) { // ignore
413 if (existingMethods == null)
414 existingMethods = new MethodBinding[1];
420 (existingMethods = new MethodBinding[existingMethods.length + 1]),
421 0, existingMethods.length - 1);
422 existingMethods[existingMethods.length - 1] = method;
423 this.inheritedMethods.put(method.selector,
426 if (nonVisibleDefaultMethods == null)
427 nonVisibleDefaultMethods = new MethodBinding[10];
428 else if (nonVisibleCount == nonVisibleDefaultMethods.length)
431 nonVisibleDefaultMethods,
433 (nonVisibleDefaultMethods = new MethodBinding[nonVisibleCount * 2]),
435 nonVisibleDefaultMethods[nonVisibleCount++] = method;
437 if (method.isAbstract() && !this.type.isAbstract()) // non
451 this.problemReporter()
452 .abstractMethodCannotBeOverridden(
455 MethodBinding[] current = (MethodBinding[]) this.currentMethods
456 .get(method.selector);
457 if (current != null) { // non visible methods
458 // cannot be overridden so a
460 foundMatch: for (int i = 0, length = current.length; i < length; i++) {
461 if (method.returnType == current[i].returnType
463 .areParametersEqual(current[i])) {
464 this.problemReporter()
465 .overridesPackageDefaultMethod(
474 superType = superType.superclass();
478 for (int i = 0; i <= lastPosition; i++) {
479 ReferenceBinding[] interfaces = interfacesToVisit[i];
480 if (interfaces == null) {
481 interfaces = new ReferenceBinding[0];
483 for (int j = 0, length = interfaces.length; j < length; j++) {
484 superType = interfaces[j];
485 if ((superType.tagBits & InterfaceVisited) == 0) {
486 superType.tagBits |= InterfaceVisited;
487 if (superType.isValidBinding()) {
488 ReferenceBinding[] itsInterfaces = superType
490 if (itsInterfaces != NoSuperInterfaces) {
491 if (++lastPosition == interfacesToVisit.length)
496 interfacesToVisit = new ReferenceBinding[lastPosition * 2][],
498 interfacesToVisit[lastPosition] = itsInterfaces;
501 MethodBinding[] methods = superType.methods();
502 for (int m = methods.length; --m >= 0;) { // Interface
507 MethodBinding method = methods[m];
508 MethodBinding[] existingMethods = (MethodBinding[]) this.inheritedMethods
509 .get(method.selector);
510 if (existingMethods == null)
511 existingMethods = new MethodBinding[1];
517 (existingMethods = new MethodBinding[existingMethods.length + 1]),
518 0, existingMethods.length - 1);
519 existingMethods[existingMethods.length - 1] = method;
520 this.inheritedMethods.put(method.selector,
528 // bit reinitialization
529 for (int i = 0; i <= lastPosition; i++) {
530 ReferenceBinding[] interfaces = interfacesToVisit[i];
531 if (interfaces == null) {
532 interfaces = new ReferenceBinding[0];
534 for (int j = 0, length = interfaces.length; j < length; j++)
535 interfaces[j].tagBits &= ~InterfaceVisited;
539 private void computeMethods() {
540 MethodBinding[] methods = type.methods();
541 if (methods == null) {
542 methods = new MethodBinding[0];
544 int size = methods.length;
545 this.currentMethods = new HashtableOfObject(size == 0 ? 1 : size); // maps
561 for (int m = size; --m >= 0;) {
562 MethodBinding method = methods[m];
563 if (!(method.isConstructor() || method.isDefaultAbstract())) { // keep
573 MethodBinding[] existingMethods = (MethodBinding[]) this.currentMethods
574 .get(method.selector);
575 if (existingMethods == null)
576 existingMethods = new MethodBinding[1];
582 (existingMethods = new MethodBinding[existingMethods.length + 1]),
583 0, existingMethods.length - 1);
584 existingMethods[existingMethods.length - 1] = method;
585 this.currentMethods.put(method.selector, existingMethods);
590 private ReferenceBinding errorException() {
591 if (errorException == null)
592 this.errorException = this.type.scope.getJavaLangError();
593 return errorException;
596 private boolean isAsVisible(MethodBinding newMethod,
597 MethodBinding inheritedMethod) {
598 if (inheritedMethod.modifiers == newMethod.modifiers)
601 if (newMethod.isPublic())
602 return true; // Covers everything
603 if (inheritedMethod.isPublic())
606 if (newMethod.isProtected())
608 if (inheritedMethod.isProtected())
611 return !newMethod.isPrivate(); // The inheritedMethod cannot be private
612 // since it would not be visible
615 private boolean isSameClassOrSubclassOf(ReferenceBinding testClass,
616 ReferenceBinding superclass) {
618 if (testClass == superclass)
620 } while ((testClass = testClass.superclass()) != null);
624 private boolean mustImplementAbstractMethod(MethodBinding abstractMethod) {
625 // if the type's superclass is an abstract class, then all abstract
626 // methods must be implemented
627 // otherwise, skip it if the type's superclass must implement any of the
629 ReferenceBinding superclass = this.type.superclass();
630 ReferenceBinding declaringClass = abstractMethod.declaringClass;
631 if (declaringClass.isClass()) {
632 while (superclass.isAbstract() && superclass != declaringClass)
633 superclass = superclass.superclass(); // find the first
634 // concrete superclass
638 if (this.type.implementsInterface(declaringClass, false)) {
639 if (this.type.isAbstract())
640 return false; // leave it for the subclasses
641 if (!superclass.implementsInterface(declaringClass, true)) // only
652 while (superclass.isAbstract()
653 && !superclass.implementsInterface(declaringClass, false))
654 superclass = superclass.superclass(); // find the first
655 // concrete superclass
657 // which implements the
660 return superclass.isAbstract(); // if it is a concrete class then we
661 // have already reported problem against
665 private ProblemReporter problemReporter() {
666 return this.type.scope.problemReporter();
669 private ProblemReporter problemReporter(MethodBinding currentMethod) {
670 ProblemReporter reporter = problemReporter();
671 if (currentMethod.declaringClass == type) // only report against the
672 // currentMethod if its
673 // implemented by the type
674 reporter.referenceContext = currentMethod.sourceMethod();
678 private ReferenceBinding runtimeException() {
679 if (runtimeException == null)
680 this.runtimeException = this.type.scope
681 .getJavaLangRuntimeException();
682 return runtimeException;
685 public void verify(SourceTypeBinding type) {
687 this.computeMethods();
688 this.computeInheritedMethods();
692 public String toString() {
693 StringBuffer buffer = new StringBuffer(10);
694 buffer.append("MethodVerifier for type: "); //$NON-NLS-1$
695 buffer.append(type.readableName());
697 buffer.append("\t-inherited methods: "); //$NON-NLS-1$
698 buffer.append(this.inheritedMethods);
699 return buffer.toString();