/******************************************************************************* * Copyright (c) 2012, 2020 GK Software SE, IBM Corporation and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 * which accompanies this distribution, and is available at * https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Stephan Herrmann - initial API and implementation *******************************************************************************/ package org.eclipse.jdt.internal.compiler.lookup; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import org.eclipse.jdt.core.compiler.CharOperation; import org.eclipse.jdt.internal.compiler.ast.ASTNode; import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration; import org.eclipse.jdt.internal.compiler.ast.Argument; import org.eclipse.jdt.internal.compiler.ast.MethodDeclaration; import org.eclipse.jdt.internal.compiler.ast.NullAnnotationMatching; import org.eclipse.jdt.internal.compiler.ast.NullAnnotationMatching.CheckMode; import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration; import org.eclipse.jdt.internal.compiler.impl.CompilerOptions; import org.eclipse.objectteams.otdt.internal.core.compiler.lookup.RoleTypeBinding; import org.eclipse.objectteams.otdt.internal.core.compiler.lookup.WeakenedTypeBinding; import org.eclipse.objectteams.otdt.internal.core.compiler.util.RoleTypeCreator; import org.eclipse.objectteams.otdt.internal.core.compiler.util.TypeAnalyzer; /** * Extracted slice from MethodVerifier15, which is responsible only for implicit null annotations. * First, if enabled, it detects overridden methods from which null annotations are inherited. * Next, also default nullness is filled into remaining empty slots. * After all implicit annotations have been filled in compatibility is checked and problems are complained. */ @SuppressWarnings({"rawtypes", "unchecked"}) public class ImplicitNullAnnotationVerifier { public static void ensureNullnessIsKnown(MethodBinding methodBinding, Scope scope) { if ((methodBinding.tagBits & TagBits.IsNullnessKnown) == 0) { LookupEnvironment environment2 = scope.environment(); // ensure nullness of methodBinding is known (but we are not interested in reporting problems against methodBinding) new ImplicitNullAnnotationVerifier(environment2, environment2.globalOptions.inheritNullAnnotations) .checkImplicitNullAnnotations(methodBinding, null/*srcMethod*/, false, scope); } } /** * Simple record to store nullness info for one argument or return type * while iterating over a set of overridden methods. */ static class InheritedNonNullnessInfo { Boolean inheritedNonNullness; MethodBinding annotationOrigin; boolean complained; } // delegate which to ask for recursive analysis of super methods // can be 'this', but is never a MethodVerifier (to avoid infinite recursion). ImplicitNullAnnotationVerifier buddyImplicitNullAnnotationsVerifier; private boolean inheritNullAnnotations; protected LookupEnvironment environment; public ImplicitNullAnnotationVerifier(LookupEnvironment environment, boolean inheritNullAnnotations) { this.buddyImplicitNullAnnotationsVerifier = this; this.inheritNullAnnotations = inheritNullAnnotations; this.environment = environment; } // for sub-classes: ImplicitNullAnnotationVerifier(LookupEnvironment environment) { CompilerOptions options = environment.globalOptions; this.buddyImplicitNullAnnotationsVerifier = new ImplicitNullAnnotationVerifier(environment, options.inheritNullAnnotations); this.inheritNullAnnotations = options.inheritNullAnnotations; this.environment = environment; } /** * Check and fill in implicit annotations from overridden methods and from default. * Precondition: caller has checked whether annotation-based null analysis is enabled. */ public void checkImplicitNullAnnotations(MethodBinding currentMethod, AbstractMethodDeclaration srcMethod, boolean complain, Scope scope) { // check inherited nullness from superclass and superInterfaces try { ReferenceBinding currentType = currentMethod.declaringClass; if (currentType.id == TypeIds.T_JavaLangObject) { return; } //{ObjectTeams: other top types: if (TypeAnalyzer.isTopConfined(currentType)) return; // SH} boolean usesTypeAnnotations = scope.environment().usesNullTypeAnnotations(); boolean needToApplyReturnNonNullDefault = currentMethod.hasNonNullDefaultForReturnType(srcMethod); ParameterNonNullDefaultProvider needToApplyParameterNonNullDefault = currentMethod.hasNonNullDefaultForParameter(srcMethod); boolean needToApplyNonNullDefault = needToApplyReturnNonNullDefault | needToApplyParameterNonNullDefault.hasAnyNonNullDefault(); // compatibility & inheritance do not consider constructors / static methods: boolean isInstanceMethod = !currentMethod.isConstructor() && !currentMethod.isStatic(); complain &= isInstanceMethod; if (!needToApplyNonNullDefault && !complain && !(this.inheritNullAnnotations && isInstanceMethod)) { return; // short cut, no work to be done } if (isInstanceMethod) { List superMethodList = new ArrayList(); // need super types connected: if (currentType instanceof SourceTypeBinding && !currentType.isHierarchyConnected() && !currentType.isAnonymousType()) { ((SourceTypeBinding) currentType).scope.connectTypeHierarchy(); } int paramLen = currentMethod.parameters.length; findAllOverriddenMethods(currentMethod.original(), currentMethod.selector, paramLen, currentType, new HashSet(), superMethodList); // prepare interim storage for nullness info so we don't pollute currentMethod before we know its conflict-free: InheritedNonNullnessInfo[] inheritedNonNullnessInfos = new InheritedNonNullnessInfo[paramLen+1]; // index 0 is for the return type for (int i=0; i= 0;) { MethodBinding currentSuper = (MethodBinding) superMethodList.get(i); if ((currentSuper.tagBits & TagBits.IsNullnessKnown) == 0) { // recurse to prepare currentSuper checkImplicitNullAnnotations(currentSuper, null, false, scope); // TODO (stephan) complain=true if currentSuper is source method?? } checkNullSpecInheritance(currentMethod, srcMethod, needToApplyReturnNonNullDefault, needToApplyParameterNonNullDefault, complain, currentSuper, null, scope, inheritedNonNullnessInfos); needToApplyNonNullDefault = false; } // transfer collected information into currentMethod: InheritedNonNullnessInfo info = inheritedNonNullnessInfos[0]; if (!info.complained) { long tagBits = 0; if (info.inheritedNonNullness == Boolean.TRUE) { tagBits = TagBits.AnnotationNonNull; } else if (info.inheritedNonNullness == Boolean.FALSE) { tagBits = TagBits.AnnotationNullable; } if (tagBits != 0) { if (!usesTypeAnnotations) { currentMethod.tagBits |= tagBits; } else { if (!currentMethod.returnType.isBaseType()) { LookupEnvironment env = scope.environment(); currentMethod.returnType = env.createAnnotatedType(currentMethod.returnType, env.nullAnnotationsFromTagBits(tagBits)); } } } } for (int i=0; i and X as the same 'type' if (one.isParameterizedType() && two.isParameterizedType()) return one.isEquivalentTo(two) && two.isEquivalentTo(one); // Can skip this since we resolved each method before comparing it, see computeSubstituteMethod() // if (one instanceof UnresolvedReferenceBinding) // return ((UnresolvedReferenceBinding) one).resolvedType == two; // if (two instanceof UnresolvedReferenceBinding) // return ((UnresolvedReferenceBinding) two).resolvedType == one; return false; // all other type bindings are identical } //{ObjectTeams: specific check for role types and arrays thereof: public static boolean areEqualRoleTypes(TypeBinding one, TypeBinding two, ReferenceBinding site, LookupEnvironment environment) { if (one instanceof ArrayBinding) { if (! (two instanceof ArrayBinding)) return false; ArrayBinding array1 = (ArrayBinding)one; ArrayBinding array2 = (ArrayBinding)two; if (array1.dimensions != array2.dimensions) return false; one = array1.leafComponentType(); two = array2.leafComponentType(); } if (one instanceof WeakenedTypeBinding) one = ((WeakenedTypeBinding)one).weakenedType; if (two instanceof WeakenedTypeBinding) two = ((WeakenedTypeBinding)two).weakenedType; if (one instanceof RoleTypeBinding) { if (two instanceof UnresolvedReferenceBinding) { // resolve type (incl. type wrapping) without resolving all of the method: // (cf. comment in BinaryTypeBinding.unResolvedMethods()). two = ((UnresolvedReferenceBinding)two).resolve(environment, false/*convertGenericToRaw*/); two = RoleTypeCreator.maybeWrapUnqualifiedRoleType(two, site); } if (two instanceof RoleTypeBinding) return TypeAnalyzer.areRoleTypesEqual((RoleTypeBinding)one, (RoleTypeBinding)two); } return false; } // SH} //{ObjectTeams: is method the static implementation of a role ifc's abstract static? private boolean staticRoleMethodImpl(MethodBinding method, MethodBinding inheritedMethod) { if (inheritedMethod.declaringClass.isSynthInterface()) return method.isStatic() && inheritedMethod.isStatic(); return false; } // SH} }