blob: 1a436f434324badb94ce0b48fb358a740039462d [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2011 GK Software AG and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Stephan Herrmann - initial API and implementation
*******************************************************************************/
package org.eclipse.objectteams.internal.jdt.nullity;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Locale;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import org.eclipse.core.runtime.preferences.IEclipsePreferences;
import org.eclipse.jdt.core.compiler.CategorizedProblem;
import org.eclipse.jdt.core.compiler.CharOperation;
import org.eclipse.jdt.internal.compiler.ast.ASTNode;
import org.eclipse.jdt.internal.compiler.ast.Annotation;
import org.eclipse.jdt.internal.compiler.ast.Argument;
import org.eclipse.jdt.internal.compiler.ast.Expression;
import org.eclipse.jdt.internal.compiler.ast.FalseLiteral;
import org.eclipse.jdt.internal.compiler.ast.MarkerAnnotation;
import org.eclipse.jdt.internal.compiler.ast.MemberValuePair;
import org.eclipse.jdt.internal.compiler.ast.QualifiedTypeReference;
import org.eclipse.jdt.internal.compiler.ast.TypeReference;
import org.eclipse.jdt.internal.compiler.ast.SubRoutineStatement;
import org.eclipse.jdt.internal.compiler.ast.SynchronizedStatement;
import org.eclipse.jdt.internal.compiler.ast.TryStatement;
import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration;
import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
import org.eclipse.jdt.internal.compiler.env.IBinaryAnnotation;
import org.eclipse.jdt.internal.compiler.env.IBinaryElementValuePair;
import org.eclipse.jdt.internal.compiler.env.IBinaryMethod;
import org.eclipse.jdt.internal.compiler.env.IBinaryType;
import org.eclipse.jdt.internal.compiler.flow.FlowContext;
import org.eclipse.jdt.internal.compiler.flow.FlowInfo;
import org.eclipse.jdt.internal.compiler.flow.InitializationFlowContext;
import org.eclipse.jdt.internal.compiler.flow.InsideSubRoutineFlowContext;
import org.eclipse.jdt.internal.compiler.flow.UnconditionalFlowInfo;
import org.eclipse.jdt.internal.compiler.impl.BooleanConstant;
import org.eclipse.jdt.internal.compiler.impl.IrritantSet;
import org.eclipse.jdt.internal.compiler.impl.ReferenceContext;
import org.eclipse.jdt.internal.compiler.lookup.AnnotationBinding;
import org.eclipse.jdt.internal.compiler.lookup.Binding;
import org.eclipse.jdt.internal.compiler.lookup.ClassScope;
import org.eclipse.jdt.internal.compiler.lookup.ImportBinding;
import org.eclipse.jdt.internal.compiler.lookup.LocalVariableBinding;
import org.eclipse.jdt.internal.compiler.lookup.MethodScope;
import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding;
import org.eclipse.jdt.internal.compiler.lookup.Scope;
import org.eclipse.jdt.internal.compiler.lookup.TypeBinding;
import org.eclipse.jdt.internal.compiler.lookup.TypeConstants;
import org.eclipse.jdt.internal.compiler.lookup.UnresolvedReferenceBinding;
import org.eclipse.jdt.internal.compiler.lookup.VariableBinding;
import org.eclipse.jdt.internal.compiler.problem.ProblemHandler;
import org.eclipse.jdt.internal.compiler.problem.ProblemSeverities;
import org.eclipse.jdt.internal.compiler.util.HashtableOfInt;
import org.objectteams.Instantiation;
import org.objectteams.InstantiationPolicy;
import static org.eclipse.objectteams.internal.jdt.nullity.Constants.IProblem;
import static org.eclipse.objectteams.internal.jdt.nullity.Constants.TagBits;
import static org.eclipse.objectteams.internal.jdt.nullity.Constants.TypeIds;
import base org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration;
import base org.eclipse.jdt.internal.compiler.ast.AllocationExpression;
import base org.eclipse.jdt.internal.compiler.ast.Assignment;
import base org.eclipse.jdt.internal.compiler.ast.ConstructorDeclaration;
import base org.eclipse.jdt.internal.compiler.ast.EqualExpression;
import base org.eclipse.jdt.internal.compiler.ast.ExplicitConstructorCall;
import base org.eclipse.jdt.internal.compiler.ast.LocalDeclaration;
import base org.eclipse.jdt.internal.compiler.ast.MessageSend;
import base org.eclipse.jdt.internal.compiler.ast.MethodDeclaration;
import base org.eclipse.jdt.internal.compiler.ast.ReturnStatement;
import base org.eclipse.jdt.internal.compiler.ast.Statement;
import base org.eclipse.jdt.internal.compiler.flow.LoopingFlowContext;
import base org.eclipse.jdt.internal.compiler.flow.FinallyFlowContext;
import base org.eclipse.jdt.internal.compiler.impl.CompilerOptions;
import base org.eclipse.jdt.internal.compiler.lookup.BinaryTypeBinding;
import base org.eclipse.jdt.internal.compiler.lookup.BlockScope;
import base org.eclipse.jdt.internal.compiler.lookup.MethodBinding;
import base org.eclipse.jdt.internal.compiler.lookup.MethodVerifier15;
import base org.eclipse.jdt.internal.compiler.lookup.LookupEnvironment;
import base org.eclipse.jdt.internal.compiler.lookup.PackageBinding;
import base org.eclipse.jdt.internal.compiler.lookup.SourceTypeBinding;
import base org.eclipse.jdt.internal.compiler.problem.DefaultProblemFactory;
import base org.eclipse.jdt.internal.compiler.problem.ProblemReporter;
import base org.eclipse.jdt.internal.core.JavaProject;
import base org.eclipse.jdt.internal.core.JavaModelManager;
/**
* This team class adds the implementation from
* https://bugs.eclipse.org/bugs/186342 - [compiler][null] Using annotations for null checking
* to the compiler.
*
* It essentially reflects the state of https://bugs.eclipse.org/bugs/attachment.cgi?id=186890
*/
@SuppressWarnings("restriction")
public team class CompilerAdaptation {
public CompilerAdaptation () {
// add more irritants to IrritantSet:
IrritantSet.COMPILER_DEFAULT_ERRORS.set( NullCompilerOptions.NullSpecViolation
|NullCompilerOptions.PotentialNullSpecViolation);
IrritantSet.COMPILER_DEFAULT_WARNINGS.set(NullCompilerOptions.NullSpecInsufficientInfo
|NullCompilerOptions.RedundantNullAnnotation);
IrritantSet.NULL.set( NullCompilerOptions.NullSpecViolation
|NullCompilerOptions.PotentialNullSpecViolation
|NullCompilerOptions.NullSpecInsufficientInfo
|NullCompilerOptions.RedundantNullAnnotation);
}
// ======================= Statement level analysis ============================
@SuppressWarnings({"abstractrelevantrole", "hidden-lifting-problem"}) // due to abstractness of this role failed lifting could theoretically block callin triggers
protected abstract class Statement playedBy Statement {
abstract Expression getExpression();
FlowContext flowContext;
void storeFlowContext(BlockScope scope, FlowContext flowContext) {
this.flowContext = flowContext;
}
// use custom hook from JDT/Core (https://bugs.eclipse.org/335093)
// TODO(SH): should calls to this method be guarded by "if (flowInfo.reachMode() == FlowInfo.REACHABLE)"?
// otherwise we'll proceed with incomplete information (normal null analysis avoids dead code).
checkAgainstNullAnnotation <- replace checkAgainstNullAnnotation;
/** Check assignment to local with null annotation. */
@SuppressWarnings("basecall")
callin int checkAgainstNullAnnotation(BlockScope currentScope, LocalVariableBinding local,int nullStatus)
{
if ( local != null
&& (local.tagBits & TagBits.AnnotationNonNull) != 0
&& nullStatus != FlowInfo.NON_NULL)
{
recordNullityMismatch0(flowContext, currentScope, getExpression(), nullStatus,
local.type, ConditionalFlowContext.ASSIGN_TO_NONNULL);
nullStatus=FlowInfo.NON_NULL;
}
return nullStatus;
}
}
protected class Assignment extends Statement playedBy Assignment {
/** Wire required method of super class. */
Expression getExpression() -> get Expression expression;
void storeFlowContext(BlockScope scope, FlowContext flowContext)
<- before FlowInfo analyseCode(BlockScope currentScope, FlowContext flowContext, FlowInfo flowInfo);
}
protected class LocalDeclaration extends Statement playedBy LocalDeclaration {
/** Wire required method of super class. */
Expression getExpression() -> get Expression initialization;
void storeFlowContext(BlockScope scope, FlowContext flowContext)
<- before FlowInfo analyseCode(BlockScope currentScope, FlowContext flowContext, FlowInfo flowInfo);
}
/** Abstraction over o.m(a), this(a), super(a), new C(a), t.new C(a). */
protected abstract class MessageSendish {
protected abstract Expression[] getArguments();
protected abstract MethodBinding getBinding();
void analyseArguments(BlockScope currentScope, FlowContext flowContext, FlowInfo flowInfo) {
// compare actual null-status against parameter annotations of the called method:
MethodBinding methodBinding = getBinding();
Expression[] arguments = getArguments();
if (arguments != null && methodBinding.parameterNonNullness != null) {
for (int i = 0; i < arguments.length; i++) {
if (methodBinding.parameterNonNullness[i] == Boolean.TRUE) {
TypeBinding expectedType = methodBinding.getParameters()[i];
Expression argument = arguments[i];
int nullStatus = argument.nullStatus(flowInfo); // slight loss of precision: should also use the null info from the receiver.
if (nullStatus != FlowInfo.NON_NULL) // if required non-null is not provided
recordNullityMismatch0(flowContext, currentScope, argument, nullStatus, expectedType, ConditionalFlowContext.ASSIGN_TO_NONNULL);
}
}
}
}
}
/** Analyse argument expressions as part of a MessageSend, check against method parameter annotation. */
@Instantiation(InstantiationPolicy.ALWAYS)
protected class MessageSend extends MessageSendish playedBy MessageSend {
Expression[] getArguments() -> get Expression[] arguments;
MethodBinding getBinding() -> get MethodBinding binding;
nullStatus <- replace nullStatus;
@SuppressWarnings("basecall")
callin int nullStatus(FlowInfo flowInfo) {
int status = getNullStatus();
if (status != FlowInfo.UNKNOWN)
return status;
return base.nullStatus(flowInfo);
}
void analyseArguments(BlockScope currentScope, FlowContext flowContext, FlowInfo flowInfo)
<- after FlowInfo analyseCode(BlockScope currentScope, FlowContext flowContext, FlowInfo flowInfo);
checkNPE <- after checkNPE;
/** Detect and signal directly dereferencing a nullable message send result. */
void checkNPE(BlockScope scope, FlowContext flowContext, FlowInfo flowInfo) {
if (getNullStatus() == FlowInfo.POTENTIALLY_NULL)
scope.problemReporter().messageSendPotentialNullReference(getBinding(), this);
}
protected int getNullStatus() {
MethodBinding binding = this.getBinding();
if (binding.isValidBinding()) {
// try to retrieve null status of this message send from an annotation of the called method:
long tagBits = binding.getTagBits();
if ((tagBits & TagBits.AnnotationNonNull) != 0)
return FlowInfo.NON_NULL;
if ((tagBits & TagBits.AnnotationNullable) != 0)
return FlowInfo.POTENTIALLY_NULL;
}
return FlowInfo.UNKNOWN;
}
}
protected class ExplicitConstructorCall extends MessageSendish playedBy ExplicitConstructorCall {
Expression[] getArguments() -> get Expression[] arguments;
MethodBinding getBinding() -> get MethodBinding binding;
void analyseArguments(BlockScope currentScope, FlowContext flowContext, FlowInfo flowInfo)
<- after FlowInfo analyseCode(BlockScope currentScope, FlowContext flowContext, FlowInfo flowInfo);
}
protected class AllocationExpression extends MessageSendish playedBy AllocationExpression {
Expression[] getArguments() -> get Expression[] arguments;
MethodBinding getBinding() -> get MethodBinding binding;
// Note: this also applies to QualifiedAllocationExpression
void analyseArguments(BlockScope currentScope, FlowContext flowContext, FlowInfo flowInfo)
<- after FlowInfo analyseCode(BlockScope currentScope, FlowContext flowContext, FlowInfo flowInfo);
}
@Instantiation(InstantiationPolicy.ALWAYS)
protected class EqualExpression playedBy EqualExpression {
MessageSend getLeftMessage() -> get Expression left
with { result <- (left instanceof MessageSend) ? (MessageSend) left : null }
int getLeftNullStatus(FlowInfo info) -> get Expression left
with { result <- left.nullStatus(info) }
MessageSend getRightMessage() -> get Expression right
with { result <- (right instanceof MessageSend) ? (MessageSend) right : null }
int getRightNullStatus(FlowInfo info)-> get Expression right
with { result <- right.nullStatus(info) }
checkNullComparison <- before checkNullComparison;
/** Detect and signal when comparing a non-null message send against null. */
void checkNullComparison(BlockScope scope, FlowContext flowContext, FlowInfo flowInfo,
FlowInfo initsWhenTrue, FlowInfo initsWhenFalse)
{
MessageSend leftMessage = getLeftMessage();
if ( leftMessage != null
&& leftMessage.getNullStatus() == FlowInfo.NON_NULL
&& getRightNullStatus(flowInfo) == FlowInfo.NULL)
{
scope.problemReporter().messageSendRedundantCheckOnNonNull(leftMessage.getBinding(), leftMessage);
}
MessageSend rightMessage = getRightMessage();
if ( rightMessage != null
&& rightMessage.getNullStatus() == FlowInfo.NON_NULL
&& getLeftNullStatus(flowInfo) == FlowInfo.NULL)
{
scope.problemReporter().messageSendRedundantCheckOnNonNull(rightMessage.getBinding(), rightMessage);
}
}
}
@SuppressWarnings("bindingconventions")
protected class NodeWithBits playedBy ASTNode {
int getBits() -> get int bits;
public void addBit(int bit) -> set int bits
with { bit | getBits() -> bits }
}
/** Analyse the expression within a return statement, check against method return annotation. */
@Instantiation(InstantiationPolicy.ALWAYS)
protected class ReturnStatement extends NodeWithBits playedBy ReturnStatement {
Expression getExpression() -> get Expression expression;
int getSourceStart() -> get int sourceStart;
int getSourceEnd() -> get int sourceEnd;
FlowInfo analyseCode(BlockScope currentScope, FlowContext flowContext, FlowInfo flowInfo)
<- replace FlowInfo analyseCode(BlockScope currentScope, FlowContext flowContext, FlowInfo flowInfo);
@SuppressWarnings({ "inferredcallout", "basecall", "decapsulation" })
callin FlowInfo analyseCode(BlockScope currentScope, FlowContext flowContext, FlowInfo flowInfo) {
MethodScope methodScope = currentScope.methodScope();
if (this.expression != null) {
// workaround for Bug 354480 - VerifyError due to bogus lowering in inferred callout-to-field
org.eclipse.jdt.internal.compiler.lookup.BlockScope blockScope = currentScope;
flowInfo = this.expression.analyseCode(blockScope, flowContext, flowInfo);
if ((this.expression.implicitConversion & org.eclipse.jdt.internal.compiler.lookup.TypeIds.UNBOXING) != 0) {
this.expression.checkNPE(blockScope, flowContext, flowInfo);
}
if (flowInfo.reachMode() == FlowInfo.REACHABLE)
checkAgainstNullAnnotation(currentScope, this.expression.nullStatus(flowInfo));
try {
Compatibility compatibility = new Compatibility();
IFakedTrackingVariable trackingVariable = compatibility.getCloseTrackingVariable(this.expression);
if (trackingVariable != null) {
if (methodScope != trackingVariable.methodScope())
trackingVariable.markClosedInNestedMethod();
// don't report issues concerning this local, since by returning
// the method passes the responsibility to the caller:
compatibility.removeTrackingVar(currentScope, trackingVariable);
}
} catch (Throwable e) {
// nop, compatibility with older JDT/Core versions
}
}
this.initStateIndex =
methodScope.recordInitializationStates(flowInfo);
// compute the return sequence (running the finally blocks)
FlowContext traversedContext = flowContext;
int subCount = 0;
boolean saveValueNeeded = false;
boolean hasValueToSave = needValueStore();
do {
SubRoutineStatement sub;
if ((sub = traversedContext.subroutine()) != null) {
if (this.subroutines == null){
this.subroutines = new SubRoutineStatement[5];
}
if (subCount == this.subroutines.length) {
System.arraycopy(this.subroutines, 0, (this.subroutines = new SubRoutineStatement[subCount*2]), 0, subCount); // grow
}
this.subroutines[subCount++] = sub;
if (sub.isSubRoutineEscaping()) {
saveValueNeeded = false;
addBit(ASTNode.IsAnySubRoutineEscaping);
break;
}
}
traversedContext.recordReturnFrom(flowInfo.unconditionalInits());
if (traversedContext instanceof InsideSubRoutineFlowContext) {
ASTNode node = traversedContext.associatedNode;
if (node instanceof SynchronizedStatement) {
addBit(ASTNode.IsSynchronized);
} else if (node instanceof TryStatement) {
TryStatement tryStatement = (TryStatement) node;
flowInfo.addInitializationsFrom(tryStatement.subRoutineInits); // collect inits
if (hasValueToSave) {
if (this.saveValueVariable == null){ // closest subroutine secret variable is used
prepareSaveValueLocation(tryStatement);
}
saveValueNeeded = true;
this.initStateIndex =
methodScope.recordInitializationStates(flowInfo);
}
}
} else if (traversedContext instanceof InitializationFlowContext) {
currentScope.problemReporter().cannotReturnInInitializer(this);
return FlowInfo.DEAD_END;
}
} while ((traversedContext = getLocalParent(traversedContext)) != null);
// resize subroutines
if ((this.subroutines != null) && (subCount != this.subroutines.length)) {
System.arraycopy(this.subroutines, 0, (this.subroutines = new SubRoutineStatement[subCount]), 0, subCount);
}
// secret local variable for return value (note that this can only occur in a real method)
if (saveValueNeeded) {
if (this.saveValueVariable != null) {
this.saveValueVariable.useFlag = LocalVariableBinding.USED;
}
} else {
this.saveValueVariable = null;
if (((getBits() & ASTNode.IsSynchronized) == 0) && this.expression != null && this.expression.resolvedType == TypeBinding.BOOLEAN) {
this.expression.bits |= ASTNode.IsReturnedValue;
}
}
try {
currentScope.checkUnclosedCloseables(flowInfo, this, currentScope);
} catch (Throwable e) {
// nop, compatibility with older JDT/Core versions.
}
return FlowInfo.DEAD_END;
}
FlowContext getLocalParent(FlowContext flowContext) {
if (flowContext.associatedNode instanceof org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration || flowContext.associatedNode instanceof TypeDeclaration)
return null;
return flowContext.parent;
}
void checkAgainstNullAnnotation(BlockScope scope, int nullStatus) {
if (nullStatus != FlowInfo.NON_NULL) {
// if we can't prove non-null check against declared null-ness of the enclosing method:
long tagBits;
org.eclipse.jdt.internal.compiler.lookup.MethodBinding methodBinding;
try {
methodBinding = scope.methodScope().referenceMethod().binding;
tagBits = methodBinding.tagBits;
} catch (NullPointerException npe) {
return;
}
if ((tagBits & TagBits.AnnotationNonNull) != 0) {
char[][] annotationName = scope.environment().getNonNullAnnotationName();
scope.problemReporter().nullityMismatch(getExpression(), methodBinding.returnType, nullStatus, annotationName);
}
}
}
}
// ======================= Method level annotations ============================
@SuppressWarnings("bindingconventions")
@Instantiation(InstantiationPolicy.ALWAYS)
protected class StandardAnnotation playedBy Annotation {
@SuppressWarnings("decapsulation")
detectStandardAnnotation <- replace detectStandardAnnotation;
callin long detectStandardAnnotation(Scope scope, ReferenceBinding annotationType, MemberValuePair valueAttribute)
{
long tagBits = base.detectStandardAnnotation(scope, annotationType, valueAttribute);
switch (annotationType.id) {
case TypeIds.T_ConfiguredAnnotationNullable :
tagBits |= TagBits.AnnotationNullable;
break;
case TypeIds.T_ConfiguredAnnotationNonNull :
tagBits |= TagBits.AnnotationNonNull;
break;
case TypeIds.T_ConfiguredAnnotationNonNullByDefault :
if (valueAttribute != null
&& valueAttribute.value instanceof FalseLiteral)
{
// parameter 'false' means: this annotation cancels any defaults
tagBits |= TagBits.AnnotationNullUnspecifiedByDefault;
break;
}
tagBits |= TagBits.AnnotationNonNullByDefault;
break;
}
return tagBits;
}
}
protected class AbstractMethodDeclaration extends NodeWithBits playedBy AbstractMethodDeclaration {
BlockScope getScope() -> get MethodScope scope;
Argument[] getArguments() -> get Argument[] arguments;
Annotation[] getAnnotations() -> get Annotation[] annotations;
void setAnnotations(Annotation[] annot) -> set Annotation[] annotations;
MethodBinding getBinding() -> get MethodBinding binding;
public void createArgumentBindingsWithAnnotations() {
MethodBinding binding = getBinding();
if (binding == null)
return;
BlockScope scope = getScope();
Argument[] arguments = this.getArguments();
if (arguments != null && binding != null) {
for (int i = 0, length = arguments.length; i < length; i++) {
Argument argument = arguments[i];
// the following three lines are copied from Argument.bind()
// luckily both are protected against double-execution.
if (argument.binding == null)
argument.binding = new LocalVariableBinding(argument, null, argument.modifiers, true);
ASTNode.resolveAnnotations(scope, argument.annotations, argument.binding);
// transfer nullness info from the argument to the method:
if ((argument.binding.tagBits & (TagBits.AnnotationNonNull|TagBits.AnnotationNullable)) != 0) {
if (binding.parameterNonNullness == null)
binding.parameterNonNullness = new Boolean[arguments.length];
binding.parameterNonNullness[i] = Boolean.valueOf((argument.binding.tagBits & TagBits.AnnotationNonNull) != 0);
}
}
}
}
void analyseArgumentNullity(FlowInfo info) {
MethodBinding binding = getBinding();
Argument[] arguments = this.getArguments();
if (arguments != null && binding.parameterNonNullness != null) {
for (int i = 0, count = arguments.length; i < count; i++) {
// leverage null-info from parameter annotations:
Boolean nonNullNess = binding.parameterNonNullness[i];
if (nonNullNess != null) {
if (nonNullNess)
info.markAsDefinitelyNonNull(arguments[i].binding);
else
info.markPotentiallyNullBit(arguments[i].binding);
}
}
}
}
/**
* Materialize a null annotation that has been added from the current default,
* in order to ensure that this annotation will be generated into the .class file, too.
*/
public void addNullnessAnnotation(ReferenceBinding annotationBinding) {
setAnnotations(addAnnotation(this, getAnnotations(), annotationBinding));
}
/**
* Materialize a null parameter annotation that has been added from the current default,
* in order to ensure that this annotation will be generated into the .class file, too.
*/
public void addParameterNonNullAnnotation(int i, ReferenceBinding annotationBinding) {
Argument argument = getArguments()[i];
if (argument.type != null) // null happens for constructors of anonymous classes
argument.annotations = addAnnotation(argument.type, argument.annotations, annotationBinding);
}
Annotation[] addAnnotation(ASTNode location, Annotation[] annotations, ReferenceBinding annotationBinding) {
int sourceStart = location.sourceStart;
long pos = ((long)sourceStart<<32) + location.sourceEnd;
long[] poss = new long[annotationBinding.compoundName.length];
Arrays.fill(poss, pos);
MarkerAnnotation annotation = new MarkerAnnotation(new QualifiedTypeReference(annotationBinding.compoundName, poss), sourceStart);
annotation.declarationSourceEnd = location.sourceEnd;
annotation.resolvedType = annotationBinding;
annotation.bits = Constants.IsSynthetic; // later use ASTNode.IsSynthetic; // prevent from conversion to DOM AST
if (annotations == null) {
annotations = new Annotation[] {annotation};
} else {
int len = annotations.length;
System.arraycopy(annotations, 0, annotations=new Annotation[len+1], 1, len);
annotations[0] = annotation;
}
return annotations;
}
}
protected class MethodDeclaration extends AbstractMethodDeclaration playedBy MethodDeclaration {
TypeReference getReturnType() -> get TypeReference returnType;
/** Feed null status from parameter annotation into the analysis of the method's body. */
void analyseArgumentNullity(FlowInfo info)
<- before void analyseCode(ClassScope classScope, FlowContext initializationContext, FlowInfo info)
with { info <- info }
// compatibility<= 3.8M2
void analyseArgumentNullity(FlowInfo info)
<- before void analyseCode(ClassScope classScope, InitializationFlowContext initializationContext, FlowInfo info)
with { info <- info }
}
protected class ConstructorDeclaration extends AbstractMethodDeclaration playedBy ConstructorDeclaration {
/** Feed null status from parameter annotation into the analysis of the method's body. */
void analyseArgumentNullity(FlowInfo info)
<- before void analyseCode(ClassScope classScope, InitializationFlowContext initializationContext, FlowInfo info, int reachMode)
with { info <- info }
}
/** Add a field to store parameter nullness information. */
protected class MethodBinding playedBy MethodBinding {
/** Store nullness information from annotation (incl. applicable default). */
public Boolean[] parameterNonNullness; // TRUE means @NonNull declared, FALSE means @Nullable declared, null means nothing declared
TypeBinding getReturnType() -> get TypeBinding returnType;
ReferenceBinding getDeclaringClass() -> get ReferenceBinding declaringClass;
SourceTypeBinding getDeclaringSourceType() -> get ReferenceBinding declaringClass
with { result <- (SourceTypeBinding)declaringClass }
TypeBinding[] getParameters() -> get TypeBinding[] parameters;
long getTagBits() -> get long tagBits;
public void addTagBit(long bit) -> set long tagBits
with { bit | getTagBits() -> tagBits }
boolean isStatic() -> boolean isStatic();
boolean isValidBinding() -> boolean isValidBinding();
AbstractMethodDeclaration sourceMethod()-> AbstractMethodDeclaration sourceMethod();
char[] readableName() -> char[] readableName();
char[] shortReadableName() -> char[] shortReadableName();
/** After method verifier has finished, fill in missing nullness values from the applicable default. */
protected void fillInDefaultNonNullness(TypeBinding annotationBinding) {
TypeBinding[] parameters = getParameters();
if (this.parameterNonNullness == null)
this.parameterNonNullness = new Boolean[parameters.length];
AbstractMethodDeclaration sourceMethod = sourceMethod();
for (int i = 0; i < this.parameterNonNullness.length; i++) {
if (parameters[i].isBaseType())
continue;
boolean added = false;
if (this.parameterNonNullness[i] == null) {
added = true;
this.parameterNonNullness[i] = Boolean.TRUE;
if (sourceMethod != null)
sourceMethod.addParameterNonNullAnnotation(i, (ReferenceBinding)annotationBinding);
} else if (this.parameterNonNullness[i].booleanValue()) {
sourceMethod.getScope().problemReporter().nullAnnotationIsRedundant(sourceMethod, i);
}
if (added)
addTagBit(TagBits.HasParameterAnnotations);
}
TypeBinding returnType = getReturnType();
if ( returnType != null
&& !returnType.isBaseType()
&& (getTagBits() & (TagBits.AnnotationNonNull|TagBits.AnnotationNullable)) == 0)
{
addTagBit(TagBits.AnnotationNonNull);
if (sourceMethod != null)
sourceMethod.addNullnessAnnotation((ReferenceBinding)annotationBinding);
} else if ((getTagBits() & TagBits.AnnotationNonNull) != 0) {
sourceMethod.getScope().problemReporter().nullAnnotationIsRedundant(sourceMethod, -1/*signifies method return*/);
}
}
}
/** Check compatibility of inherited null-specifications. */
@SuppressWarnings("decapsulation")
protected class MethodVerifier15 playedBy MethodVerifier15 {
LookupEnvironment getEnvironment() -> get LookupEnvironment environment;
SourceTypeBinding getType() -> get SourceTypeBinding type;
AbstractMethodDeclaration[] getMethodDeclarations() -> get SourceTypeBinding type
with { result <- type.scope.referenceContext.methods }
MethodBinding[] getMethodBindings() -> get SourceTypeBinding type
with { result <- type.methods() }
void checkNullSpecInheritance(MethodBinding currentMethod, MethodBinding[] methods, int length)
<- after
void checkAgainstInheritedMethods(MethodBinding currentMethod, MethodBinding[] methods, int length, MethodBinding[] allInheritedMethods);
void checkNullSpecInheritance(MethodBinding currentMethod, MethodBinding[] methods, int length)
<- after
void checkConcreteInheritedMethod(MethodBinding concreteMethod, MethodBinding[] abstractMethods)
with { currentMethod <- concreteMethod, methods <- abstractMethods, length <- abstractMethods.length }
void checkNullSpecInheritance(MethodBinding currentMethod, MethodBinding[] methods, int length) {
// TODO: change traversal: process all methods at once!
for (int i = length; --i >= 0;)
if (!currentMethod.isStatic() && !methods[i].isStatic())
checkNullSpecInheritance(currentMethod, methods[i]);
}
void checkNullSpecInheritance(MethodBinding currentMethod, MethodBinding inheritedMethod) {
long inheritedBits = inheritedMethod.getTagBits();
long currentBits = currentMethod.getTagBits();
LookupEnvironment environment = this.getEnvironment();
AbstractMethodDeclaration srcMethod = null;
if (getType().isSame(currentMethod.getDeclaringClass())) // is currentMethod from the current type?
srcMethod = currentMethod.sourceMethod();
// return type:
if ((inheritedBits & TagBits.AnnotationNonNull) != 0) {
long currentNullBits = currentBits & (TagBits.AnnotationNonNull|TagBits.AnnotationNullable);
if (currentNullBits != TagBits.AnnotationNonNull) {
if (srcMethod != null) {
getType().problemReporter().illegalReturnRedefinition(srcMethod, inheritedMethod,
environment.getNonNullAnnotationName());
} else {
getType().problemReporter().cannotImplementIncompatibleNullness(currentMethod, inheritedMethod);
return;
}
}
}
// parameters:
Argument[] currentArguments = srcMethod == null ? null : srcMethod.getArguments();
if (inheritedMethod.parameterNonNullness != null) {
// inherited method has null-annotations, check compatibility:
for (int i = 0; i < inheritedMethod.parameterNonNullness.length; i++) {
Argument currentArgument = currentArguments == null ? null : currentArguments[i];
Boolean inheritedNonNullNess = inheritedMethod.parameterNonNullness[i];
Boolean currentNonNullNess = (currentMethod.parameterNonNullness == null)
? null : currentMethod.parameterNonNullness[i];
if (inheritedNonNullNess != null) { // super has a null annotation
if (currentNonNullNess == null) { // current parameter lacks null annotation
boolean needNonNull = false;
char[][] annotationName;
if (inheritedNonNullNess == Boolean.TRUE) {
needNonNull = true;
annotationName = environment.getNonNullAnnotationName();
} else {
annotationName = environment.getNullableAnnotationName();
}
if (currentArgument != null) {
getType().problemReporter().parameterLackingNonNullAnnotation(
currentArgument,
inheritedMethod.getDeclaringClass(),
needNonNull,
annotationName);
continue;
} else {
getType().problemReporter().cannotImplementIncompatibleNullness(currentMethod, inheritedMethod);
break;
}
}
}
if (inheritedNonNullNess != Boolean.TRUE) { // super parameter is not restricted to @NonNull
if (currentNonNullNess == Boolean.TRUE) { // current parameter is restricted to @NonNull
if (currentArgument != null)
getType().problemReporter().illegalRedefinitionToNonNullParameter(
currentArgument,
inheritedMethod.getDeclaringClass(),
inheritedNonNullNess == null
? null
: environment.getNullableAnnotationName());
else
getType().problemReporter().cannotImplementIncompatibleNullness(currentMethod, inheritedMethod);
}
}
}
} else if (currentMethod.parameterNonNullness != null) {
// super method has no annotations but current has
for (int i = 0; i < currentMethod.parameterNonNullness.length; i++) {
if (currentMethod.parameterNonNullness[i] == Boolean.TRUE) { // tightening from unconstrained to @NonNull
if (currentArguments != null) {
getType().problemReporter().illegalRedefinitionToNonNullParameter(
currentArguments[i],
inheritedMethod.getDeclaringClass(),
null);
} else {
getType().problemReporter().cannotImplementIncompatibleNullness(currentMethod, inheritedMethod);
break;
}
}
}
}
}
}
/**
* Answer the nullness default applicable at the given method binding.
* Possible values:<ul>
* <li>the type binding for @NonNulByDefault</li>
* <li>the synthetic type {@link Constants#NULL_UNSPECIFIED} if a default from outer scope has been canceled</li>
* <li>null if no default has been defined</li>
* </ul>
*/
TypeBinding findDefaultNullness(MethodBinding methodBinding, LookupEnvironment environment) {
// find the applicable default inside->out:
// method
TypeBinding annotationBinding = environment.getNullAnnotationBindingFromDefault(methodBinding.getTagBits(), true/*resolve*/);
if (annotationBinding != null)
return annotationBinding;
// type
SourceTypeBinding type = methodBinding.getDeclaringSourceType();
SourceTypeBinding currentType = type;
while (currentType != null) {
annotationBinding = currentType.getNullnessDefaultAnnotation();
if (annotationBinding != null)
return annotationBinding;
currentType = currentType.enclosingType();
}
// package
annotationBinding = type.getPackage().getNullnessDefaultAnnotation();
if (annotationBinding != null)
return annotationBinding;
// global
long defaultNullness = environment.getGlobalOptions().defaultNonNullness;
if (defaultNullness != 0) {
annotationBinding = environment.getNullAnnotationBinding(defaultNullness, true/*resolve*/);
if (annotationBinding != null)
return annotationBinding;
// on this branch default was not defined using an annotation, thus annotation type can still be missing
if (defaultNullness == TagBits.AnnotationNonNull)
type.problemReporter().missingNullAnnotationType(environment.getNonNullAnnotationName());
else if (defaultNullness == TagBits.AnnotationNullable)
type.problemReporter().missingNullAnnotationType(environment.getNullableAnnotationName());
else
type.problemReporter().abortDueToInternalError("Illegal default nullness value: "+defaultNullness); //$NON-NLS-1$
// reset default to avoid duplicate errors:
environment.getGlobalOptions().defaultNonNullness = 0;
}
return null;
}
@SuppressWarnings("bindingconventions")
protected class TaggableTypeBinding playedBy TypeBinding {
long getTagBits() -> get long tagBits;
public void addTagBit(long bit) -> set long tagBits
with { bit | getTagBits() -> tagBits }
}
protected class SourceTypeBinding extends TaggableTypeBinding playedBy SourceTypeBinding {
PackageBinding getPackage() -> PackageBinding getPackage();
ProblemReporter problemReporter() -> get ClassScope scope
with { result <- scope.problemReporter() }
SourceTypeBinding enclosingType() -> ReferenceBinding enclosingType()
with { result <- (SourceTypeBinding)result }
long computeTypeAnnotationTagBits() -> long getAnnotationTagBits();
boolean isSame(Object other) -> boolean equals(Object other);
private TypeBinding nullnessDefaultAnnotation;
private int nullnessDefaultInitialized = 0; // 0: nothing; 1: type; 2: package
/** initialize a normal type */
void evaluateNullAnnotations(long tagBits) <- after long getAnnotationTagBits()
with { tagBits <- result }
/** initialize a package-info.java */
readAndEvaluateAnnotations <- after initializeDeprecatedAnnotationTagBits
base when (CharOperation.equals(base.sourceName, TypeConstants.PACKAGE_INFO_NAME));
void callBindArguments(MethodBinding method) <- after MethodBinding resolveTypesFor(MethodBinding method);
private void callBindArguments(MethodBinding method) {
switch (this.nullnessDefaultInitialized) {
case 0:
computeTypeAnnotationTagBits();
//$FALL-THROUGH$
case 1:
getPackage().computeAnnotations();
this.nullnessDefaultInitialized = 2;
}
AbstractMethodDeclaration methodDecl = method.sourceMethod();
if (methodDecl != null) {
if (method.getParameters() != Binding.NO_PARAMETERS)
methodDecl.createArgumentBindingsWithAnnotations();
TypeBinding annotationBinding = findDefaultNullness(method, methodDecl.getScope().environment());
if (annotationBinding != null && annotationBinding.id == Constants.TypeIds.T_ConfiguredAnnotationNonNull)
method.fillInDefaultNonNullness(annotationBinding);
}
}
void readAndEvaluateAnnotations() {
computeTypeAnnotationTagBits();
}
@SuppressWarnings("inferredcallout")
void evaluateNullAnnotations(long tagBits) {
if (this.nullnessDefaultInitialized > 0)
return;
this.nullnessDefaultInitialized = 1;
// transfer nullness info from tagBits to this.nullnessDefaultAnnotation
TypeBinding nullnessDefaultAnnotation = getPackage().getEnvironment()
.getNullAnnotationBindingFromDefault(tagBits, false/*resolve*/);
if (nullnessDefaultAnnotation != null) {
if (CharOperation.equals(this.sourceName, TypeConstants.PACKAGE_INFO_NAME)) {
getPackage().nullnessDefaultAnnotation = nullnessDefaultAnnotation;
} else {
this.nullnessDefaultAnnotation = nullnessDefaultAnnotation;
}
}
}
public TypeBinding getNullnessDefaultAnnotation() {
if (this.nullnessDefaultAnnotation instanceof UnresolvedReferenceBinding)
return this.nullnessDefaultAnnotation =
org.eclipse.jdt.internal.compiler.lookup.BinaryTypeBinding.resolveType(this.nullnessDefaultAnnotation,
getPackage().getEnvironment(), false);
return this.nullnessDefaultAnnotation;
}
}
/** Retrieve null annotations from binary methods. */
protected class BinaryTypeBinding extends TaggableTypeBinding playedBy BinaryTypeBinding {
@SuppressWarnings("decapsulation")
LookupEnvironment getEnvironment() -> get LookupEnvironment environment;
PackageBinding getPackage() -> PackageBinding getPackage();
char[] sourceName() -> char[] sourceName();
void scanMethodForNullAnnotation(IBinaryMethod method, MethodBinding methodBinding)
<- after MethodBinding createMethod(IBinaryMethod method, long sourceLevel, char[][][] missingTypeNames)
with { method <- method, methodBinding <- result }
void scanMethodForNullAnnotation(IBinaryMethod method, MethodBinding methodBinding) {
LookupEnvironment environment = this.getEnvironment();
char[][] nullableAnnotationName = environment.getNullableAnnotationName();
char[][] nonNullAnnotationName = environment.getNonNullAnnotationName();
if (nullableAnnotationName == null || nonNullAnnotationName == null)
return; // not configured to use null annotations
// return:
IBinaryAnnotation[] annotations = method.getAnnotations();
if (annotations != null) {
for (int i = 0; i < annotations.length; i++) {
char[] annotationTypeName = annotations[i].getTypeName();
if (annotationTypeName[0] != 'L')
continue;
char[][] typeName = CharOperation.splitOn('/', annotationTypeName, 1, annotationTypeName.length-1); // cut of leading 'L' and trailing ';'
if (CharOperation.equals(typeName, nonNullAnnotationName)) {
methodBinding.addTagBit(TagBits.AnnotationNonNull);
break;
}
if (CharOperation.equals(typeName, nullableAnnotationName)) {
methodBinding.addTagBit(TagBits.AnnotationNullable);
break;
}
}
}
// parameters:
TypeBinding[] parameters = methodBinding.getParameters();
for (int j = 0; j < parameters.length; j++) {
IBinaryAnnotation[] paramAnnotations = method.getParameterAnnotations(j);
if (paramAnnotations != null) {
for (int i = 0; i < paramAnnotations.length; i++) {
char[] annotationTypeName = paramAnnotations[i].getTypeName();
if (annotationTypeName[0] != 'L')
continue;
char[][] typeName = CharOperation.splitOn('/', annotationTypeName, 1, annotationTypeName.length-1); // cut of leading 'L' and trailing ';'
if (CharOperation.equals(typeName, nonNullAnnotationName)) {
if (methodBinding.parameterNonNullness == null)
methodBinding.parameterNonNullness = new Boolean[parameters.length];
methodBinding.parameterNonNullness[j] = Boolean.TRUE;
break;
} else if (CharOperation.equals(typeName, nullableAnnotationName)) {
if (methodBinding.parameterNonNullness == null)
methodBinding.parameterNonNullness = new Boolean[parameters.length];
methodBinding.parameterNonNullness[j] = Boolean.FALSE;
break;
}
}
}
}
}
scanTypeForNullAnnotation <- after cachePartsFrom;
void scanTypeForNullAnnotation(IBinaryType binaryType) {
LookupEnvironment environment = this.getEnvironment();
char[][] nonNullByDefaultAnnotationName = environment.getNonNullByDefaultAnnotationName();
if (nonNullByDefaultAnnotationName == null)
return; // not configured to use null annotations
IBinaryAnnotation[] annotations = binaryType.getAnnotations();
if (annotations != null) {
long annotationBit = 0L;
TypeBinding defaultNullness = null;
for (int i = 0; i < annotations.length; i++) {
char[] annotationTypeName = annotations[i].getTypeName();
if (annotationTypeName[0] != 'L')
continue;
char[][] typeName = CharOperation.splitOn('/', annotationTypeName, 1, annotationTypeName.length-1); // cut of leading 'L' and trailing ';'
if (CharOperation.equals(typeName, nonNullByDefaultAnnotationName)) {
IBinaryElementValuePair[] elementValuePairs = annotations[i].getElementValuePairs();
if (elementValuePairs != null && elementValuePairs.length == 1) {
Object value = elementValuePairs[0].getValue();
if (value instanceof BooleanConstant
&& !((BooleanConstant)value).booleanValue())
{
// parameter is 'false': this means we cancel defaults from outer scopes:
annotationBit = TagBits.AnnotationNullUnspecifiedByDefault;
defaultNullness = Constants.NULL_UNSPECIFIED;
break;
}
}
annotationBit = TagBits.AnnotationNonNullByDefault;
defaultNullness = getEnvironment().getNullAnnotationBinding(TagBits.AnnotationNonNull, false/*resolve*/);
break;
}
}
if (annotationBit != 0L) {
addTagBit(annotationBit);
if (CharOperation.equals(this.sourceName(), TypeConstants.PACKAGE_INFO_NAME))
this.getPackage().nullnessDefaultAnnotation = defaultNullness;
}
}
}
}
// ========================== Configuration of null annotation types =========================
/** Initiate setup of configured null annotation types. */
protected class LookupEnvironment playedBy LookupEnvironment {
@SuppressWarnings("decapsulation")
TypeBinding getTypeFromCompoundName(char[][] compoundName, boolean isParameterized, boolean wasMissingType)
-> ReferenceBinding getTypeFromCompoundName(char[][] compoundName, boolean isParameterized, boolean wasMissingType);
ProblemReporter getProblemReporter() -> get ProblemReporter problemReporter;
ReferenceBinding getType(char[][] compoundName) -> ReferenceBinding getType(char[][] compoundName);
PackageBinding createPackage(char[][] compoundName) -> PackageBinding createPackage(char[][] compoundName);
boolean nullAnnotationsInitialized = false;
/** The first time a package is requested initialize the null annotation type package. */
void initNullAnnotationPackages()
<- before PackageBinding getTopLevelPackage(char[] name),
PackageBinding createPackage(char[][] compoundName)
when (!this.nullAnnotationsInitialized);
private void initNullAnnotationPackages() {
this.nullAnnotationsInitialized = true;
char[][] compoundName = getNullableAnnotationName();
if (compoundName != null)
setupNullAnnotationPackage(compoundName, TypeIds.T_ConfiguredAnnotationNullable);
compoundName = getNonNullAnnotationName();
if (compoundName != null)
setupNullAnnotationPackage(compoundName, TypeIds.T_ConfiguredAnnotationNonNull);
compoundName = getNonNullByDefaultAnnotationName();
if (compoundName != null)
setupNullAnnotationPackage(compoundName, TypeIds.T_ConfiguredAnnotationNonNullByDefault);
}
/**
* Create or retrieve the package holding the specified type.
* If emulation is enabled fill it with an emulated type of the given name.
* Prepare the package with all information to do the second stage of initialization/checking.
*/
void setupNullAnnotationPackage(char[][] typeName, int typeId) {
if (typeName.length < 2) {
this.getProblemReporter().nullAnnotationNameMustBeQualified(typeName);
return;
}
char[][] packageName = CharOperation.subarray(typeName, 0, typeName.length-1);
PackageBinding packageBinding = createPackage(packageName);
char[] simpleTypeName = typeName[typeName.length-1];
if (typeId == TypeIds.T_ConfiguredAnnotationNullable)
packageBinding.nullableName = simpleTypeName;
else if (typeId == TypeIds.T_ConfiguredAnnotationNonNull)
packageBinding.nonNullName = simpleTypeName;
else if (typeId == TypeIds.T_ConfiguredAnnotationNonNullByDefault)
packageBinding.nonNullByDefaultName = simpleTypeName;
}
reset <- after reset;
void reset() {
this.nullAnnotationsInitialized = false;
}
CompilerOptions getGlobalOptions() -> get CompilerOptions globalOptions;
public char[][] getNullableAnnotationName() {
return getGlobalOptions().nullableAnnotationName;
}
public char[][] getNonNullAnnotationName() {
return getGlobalOptions().nonNullAnnotationName;
}
public char[][] getNonNullByDefaultAnnotationName() {
return getGlobalOptions().nonNullByDefaultAnnotationName;
}
public TypeBinding getNullAnnotationBinding(long annotationTagBit, boolean resolve) {
char[][] name = null;
if (annotationTagBit == TagBits.AnnotationNonNull)
name = getNonNullAnnotationName();
else if (annotationTagBit == TagBits.AnnotationNullable)
name = getNullableAnnotationName();
else
return null;
if (resolve)
return getType(name);
else
return getTypeFromCompoundName(name, false, false);
}
public TypeBinding getNullAnnotationBindingFromDefault(long defaultTagBit, boolean resolve) {
if ((defaultTagBit & TagBits.AnnotationNullUnspecifiedByDefault) != 0)
return Constants.NULL_UNSPECIFIED;
if ((defaultTagBit & TagBits.AnnotationNonNullByDefault) != 0)
return getNullAnnotationBinding(TagBits.AnnotationNonNull, resolve);
return null;
}
}
/** The package holding the configured null annotation types detects and marks these annotation types. */
protected class PackageBinding playedBy PackageBinding {
LookupEnvironment getEnvironment() -> get LookupEnvironment environment;
void computeAnnotations() -> boolean isViewedAsDeprecated();
protected char[] nullableName = null;
protected char[] nonNullName = null;
protected char[] nonNullByDefaultName = null;
protected TypeBinding nullnessDefaultAnnotation;
void setupNullAnnotationType(ReferenceBinding type) <- after void addType(ReferenceBinding type)
when (this.nullableName != null || this.nonNullName != null || this.nonNullByDefaultName != null);
void setupNullAnnotationType(ReferenceBinding type) {
int id = 0;
if (CharOperation.equals(this.nullableName, type.sourceName))
id = TypeIds.T_ConfiguredAnnotationNullable;
else if (CharOperation.equals(this.nonNullName, type.sourceName))
id = TypeIds.T_ConfiguredAnnotationNonNull;
else if (CharOperation.equals(this.nonNullByDefaultName, type.sourceName))
id = TypeIds.T_ConfiguredAnnotationNonNullByDefault;
else
return;
type.id = id; // ensure annotations of this type are detected as standard annotations.
}
public TypeBinding getNullnessDefaultAnnotation() {
if (this.nullnessDefaultAnnotation instanceof UnresolvedReferenceBinding)
return this.nullnessDefaultAnnotation =
org.eclipse.jdt.internal.compiler.lookup.BinaryTypeBinding.resolveType(this.nullnessDefaultAnnotation, getEnvironment(), false);
return this.nullnessDefaultAnnotation;
}
}
/** Intermediate role to provide access to environment and problemReporter as roles, too. */
protected class BlockScope playedBy BlockScope {
ProblemReporter problemReporter() -> ProblemReporter problemReporter();
MethodScope methodScope() -> MethodScope methodScope();
LookupEnvironment environment() -> LookupEnvironment environment();
// method introduced in 3.8M3:
void checkUnclosedCloseables(FlowInfo flowInfo, ASTNode location, BlockScope locationScope)
-> void checkUnclosedCloseables(FlowInfo flowInfo, ASTNode location, BlockScope locationScope);
}
// ---- Handling sub-classes of FlowContext that perform deferred null checking: ----
void recordNullityMismatch0(FlowContext flowContext, BlockScope currentScope, Expression expression, int nullStatus, TypeBinding expectedType, int checkType) {
// std. impl to go into class FlowContext:
if (expression.localVariableBinding() != null) { // flowContext cannot yet handle non-localvar expressions (e.g., fields)
// find the inner-most flowContext that might need deferred handling:
while (flowContext != null) {
// some flow contexts implement deferred checking, should we participate in that?
if (flowContext instanceof org.eclipse.jdt.internal.compiler.flow.FinallyFlowContext) {
// deferred reporting?
if (recordNullityMismatch1((org.eclipse.jdt.internal.compiler.flow.FinallyFlowContext)flowContext,
currentScope, expression, nullStatus, expectedType, ConditionalFlowContext.ASSIGN_TO_NONNULL))
return;
}
else if (flowContext instanceof org.eclipse.jdt.internal.compiler.flow.LoopingFlowContext) {
// deferred reporting
if (recordNullityMismatch1((org.eclipse.jdt.internal.compiler.flow.LoopingFlowContext)flowContext,
currentScope, expression, nullStatus, expectedType, ConditionalFlowContext.ASSIGN_TO_NONNULL))
return;
}
flowContext = flowContext.parent;
}
}
// no reason to defer, so report now:
char[][] annotationName = currentScope.environment().getNonNullAnnotationName();
currentScope.problemReporter().nullityMismatch(expression, expectedType, nullStatus, annotationName);
// Note: in the plain Java impl, all remaining FlowContext classes must handle ASSIGN_TO_NULL by directly calling this problemReporter method.
}
<B base ConditionalFlowContext> boolean recordNullityMismatch1(B as ConditionalFlowContext flowContext,
BlockScope scope, Expression expression, int nullStatus, TypeBinding expectedType, int checkType)
{
return flowContext.recordNullityMismatch2(expression, nullStatus, expectedType, checkType);
}
protected abstract class ConditionalFlowContext {
// new constant for checkTypes:
protected final static int ASSIGN_TO_NONNULL = 0x0080;
// declare abstract as to avoid binding to FlowContext (don't want roles for regular flowContexts):
protected abstract int getNullCount();
protected abstract long getTagBits();
protected abstract Expression[] getNullReferences();
protected abstract LocalVariableBinding[] getNullLocals();
protected abstract int[] getNullCheckTypes();
protected abstract FlowContext getParent();
protected abstract void recordNullReference(LocalVariableBinding local, Expression expression, int status);
protected abstract FlowInfo getFlowInfo(FlowInfo callerFlowInfo);
// new array to store the expected type from the potential error location:
public TypeBinding[] expectedTypes;
// and the method to add to expectedTypes:
protected void recordExpectedType(TypeBinding expectedType) {
int nullCount = getNullCount();
if (nullCount == 0) {
this.expectedTypes = new TypeBinding[5];
} else if (this.expectedTypes == null) {
int size = 5;
while (size <= nullCount) size *= 2;
this.expectedTypes = new TypeBinding[size];
}
else if (nullCount == this.expectedTypes.length) {
System.arraycopy(this.expectedTypes, 0,
this.expectedTypes = new TypeBinding[nullCount * 2], 0, nullCount);
}
this.expectedTypes[nullCount] = expectedType;
}
/** Main logic for deferred checking. To be bound by after callin. */
void complainOnNullTypeError(BlockScope scope, FlowInfo callerFlowInfo) {
FlowInfo flowInfo = getFlowInfo(callerFlowInfo);
if ((getTagBits() & FlowContext.DEFER_NULL_DIAGNOSTIC) != 0) {
LocalVariableBinding[] locals = getNullLocals();
for (int i = 0; i < getNullCount(); i++) {
int nullCheckType = getNullCheckTypes()[i];
Expression reference = getNullReferences()[i];
if (reference == null) continue; // may have been nulled out by our base method
if (nullCheckType == ASSIGN_TO_NONNULL) {
FlowContext parent = getParent();
CompilerAdaptation.this.recordNullityMismatch0(parent,
scope, getNullReferences()[i], flowInfo.nullStatus(locals[i]), this.expectedTypes[i], nullCheckType);
} else {
getParent().recordUsingNullReference(scope, getNullLocals()[i],
reference, nullCheckType, flowInfo);
}
}
} else {
// check inconsistent null checks on outermost looping context
for (int i = 0; i < getNullCount(); i++) {
Expression expression = getNullReferences()[i];
if (expression == null) continue; // may have been nulled out by our base method
// final local variable
LocalVariableBinding local = getNullLocals()[i];
if ((getNullCheckTypes()[i] & ASSIGN_TO_NONNULL) != 0) {
char[][] annotationName = scope.environment().getNonNullAnnotationName();
int nullStatus = flowInfo.nullStatus(local);
if (nullStatus != FlowInfo.NON_NULL)
scope.problemReporter().nullityMismatch(expression, this.expectedTypes[i], nullStatus, annotationName);
break;
}
}
}
}
protected abstract boolean recordNullityMismatch2(Expression expression, int nullStatus, TypeBinding expectedType, int checkType);
}
/** Straight-forward binding of implementation role to concrete base class. */
protected class FinallyFlowContext extends ConditionalFlowContext playedBy FinallyFlowContext {
getTagBits -> get tagBits;
getParent -> get parent;
@SuppressWarnings("decapsulation") getNullCount -> get nullCount;
@SuppressWarnings("decapsulation") getNullReferences -> get nullReferences;
@SuppressWarnings("decapsulation") getNullLocals -> get nullLocals;
@SuppressWarnings("decapsulation") getNullCheckTypes -> get nullCheckTypes;
// here this team enters special data:
@SuppressWarnings("decapsulation") recordNullReference -> recordNullReference;
protected FlowInfo getFlowInfo(FlowInfo callerFlowInfo) { return callerFlowInfo; } // nothing special :)
// here the recorded data is evaluated:
void complainOnNullTypeError(BlockScope scope, FlowInfo callerFlowInfo)
<- after void complainOnDeferredChecks(FlowInfo flowInfo, BlockScope scope)
with { scope <- scope, callerFlowInfo <- flowInfo }
protected boolean recordNullityMismatch2(Expression expression, int nullStatus, TypeBinding expectedType, int checkType) {
// cf. decision structure inside FinallyFlowContext.recordUsingNullReference(..)
if (nullStatus == FlowInfo.UNKNOWN ||
((getTagBits() & FlowContext.DEFER_NULL_DIAGNOSTIC) != 0 && nullStatus != FlowInfo.NULL)) {
recordExpectedType(expectedType);
recordNullReference(expression.localVariableBinding(), expression, checkType);
return true;
}
return false;
}
}
/** Straight-forward binding of implementation role to concrete base class. */
protected class LoopingFlowContext extends ConditionalFlowContext playedBy LoopingFlowContext {
getTagBits -> get tagBits;
getParent -> get parent;
@SuppressWarnings("decapsulation") getNullCount -> get nullCount;
@SuppressWarnings("decapsulation") getNullReferences -> get nullReferences;
@SuppressWarnings("decapsulation") getNullLocals -> get nullLocals;
@SuppressWarnings("decapsulation") getNullCheckTypes -> get nullCheckTypes;
// here this team enters special data:
@SuppressWarnings("decapsulation") recordNullReference -> recordNullReference;
@SuppressWarnings({ "inferredcallout", "decapsulation" })
protected FlowInfo getFlowInfo(FlowInfo callerFlowInfo) {
// copied from LoopingFlowContext.complainOnDeferredNullChecks:
return this.upstreamNullFlowInfo.
addPotentialNullInfoFrom(callerFlowInfo.unconditionalInitsWithoutSideEffect());
}
// here the recorded data is evaluated:
complainOnNullTypeError <- after complainOnDeferredNullChecks;
protected boolean recordNullityMismatch2(Expression expression, int nullStatus, TypeBinding expectedType, int checkType) {
recordExpectedType(expectedType);
recordNullReference(expression.localVariableBinding(), expression, checkType);
return true;
}
}
// ======================= Problem reporting ==================================
/**
* Adapt the base class for handling new problem ids and irritants.
* Add new problem reporting methods.
*/
protected class ProblemReporter playedBy ProblemReporter {
ReferenceContext getReferenceContext() -> get ReferenceContext referenceContext;
@SuppressWarnings("decapsulation")
void handle(int problemId, String[] problemArguments, String[] messageArguments, int severity,
int problemStartPosition, int problemEndPosition)
->
void handle(int problemId, String[] problemArguments, String[] messageArguments, int severity,
int problemStartPosition, int problemEndPosition);
@SuppressWarnings("decapsulation")
void handle(int problemId, String[] problemArguments, String[] messageArguments, int problemStartPosition, int problemEndPosition)
->
void handle(int problemId, String[] problemArguments, String[] messageArguments, int problemStartPosition, int problemEndPosition);
void abortDueToInternalError(String errorMessage) -> void abortDueToInternalError(String errorMessage);
void cannotReturnInInitializer(ASTNode location) -> void cannotReturnInInitializer(ASTNode location);
getProblemCategory <- replace getProblemCategory;
getNullIrritant <- replace getIrritant;
@SuppressWarnings("basecall")
static callin int getProblemCategory(int severity, int problemID) {
categorizeOnIrritant: {
// fatal problems even if optional are all falling into same category (not irritant based)
if ((severity & ProblemSeverities.Fatal) != 0)
break categorizeOnIrritant;
int irritant = getIrritant(problemID);
switch (irritant) {
case CompilerOptions.NullSpecViolation :
case CompilerOptions.PotentialNullSpecViolation :
case CompilerOptions.NullSpecInsufficientInfo :
return CategorizedProblem.CAT_POTENTIAL_PROGRAMMING_PROBLEM;
}
// categorize fatal problems per ID
switch (problemID) {
case IProblem.MissingNullAnnotationType :
case IProblem.NullAnnotationNameMustBeQualified :
return CategorizedProblem.CAT_BUILDPATH;
}
}
return base.getProblemCategory(severity, problemID);
}
@SuppressWarnings("basecall")
static callin int getNullIrritant(int problemID) {
int irritant = getIrritant(problemID);
if (irritant != 0)
return irritant;
return base.getNullIrritant(problemID);
}
private static int getIrritant(int problemID) {
switch(problemID) {
case IProblem.RequiredNonNullButProvidedNull:
case IProblem.IllegalReturnNullityRedefinition:
case IProblem.IllegalRedefinitionToNonNullParameter:
case IProblem.IllegalDefinitionToNonNullParameter:
return CompilerOptions.NullSpecViolation;
case IProblem.RequiredNonNullButProvidedPotentialNull:
return CompilerOptions.PotentialNullSpecViolation;
case IProblem.RequiredNonNullButProvidedUnknown:
return CompilerOptions.NullSpecInsufficientInfo;
case IProblem.NullAnnotationIsRedundant:
return CompilerOptions.RedundantNullAnnotation;
case IProblem.PotentialNullMessageSendReference:
return org.eclipse.jdt.internal.compiler.impl.CompilerOptions.PotentialNullReference;
case IProblem.RedundantNullCheckOnNonNullMessageSend:
return org.eclipse.jdt.internal.compiler.impl.CompilerOptions.RedundantNullCheck;
}
return 0;
}
public void nullityMismatch(Expression expression, TypeBinding requiredType, int nullStatus, char[][] annotationName) {
int problemId = IProblem.RequiredNonNullButProvidedUnknown;
if ((nullStatus & FlowInfo.NULL) != 0)
problemId = IProblem.RequiredNonNullButProvidedNull;
if ((nullStatus & FlowInfo.POTENTIALLY_NULL) != 0)
problemId = IProblem.RequiredNonNullButProvidedPotentialNull;
String[] arguments = new String[] {
String.valueOf(CharOperation.concatWith(annotationName, '.')),
String.valueOf(requiredType.readableName())
};
String[] argumentsShort = new String[] {
String.valueOf(annotationName[annotationName.length-1]),
String.valueOf(requiredType.shortReadableName())
};
this.handle(
problemId,
arguments,
argumentsShort,
expression.sourceStart,
expression.sourceEnd);
}
public void illegalRedefinitionToNonNullParameter(Argument argument, ReferenceBinding declaringClass, char[][] inheritedAnnotationName) {
int sourceStart = argument.type.sourceStart;
if (argument.annotations != null) {
for (int i=0; i<argument.annotations.length; i++) {
Annotation annotation = argument.annotations[i];
if ( annotation.resolvedType.id == TypeIds.T_ConfiguredAnnotationNullable
|| annotation.resolvedType.id == TypeIds.T_ConfiguredAnnotationNonNull)
{
sourceStart = annotation.sourceStart;
break;
}
}
}
if (inheritedAnnotationName == null) {
this.handle(
IProblem.IllegalDefinitionToNonNullParameter,
new String[] { new String(argument.name), new String(declaringClass.readableName()) },
new String[] { new String(argument.name), new String(declaringClass.shortReadableName()) },
sourceStart,
argument.type.sourceEnd);
} else {
this.handle(
IProblem.IllegalRedefinitionToNonNullParameter,
new String[] { new String(argument.name), new String(declaringClass.readableName()), CharOperation.toString(inheritedAnnotationName)},
new String[] { new String(argument.name), new String(declaringClass.shortReadableName()), new String(inheritedAnnotationName[inheritedAnnotationName.length-1])},
sourceStart,
argument.type.sourceEnd);
}
}
public void parameterLackingNonNullAnnotation(Argument argument, ReferenceBinding declaringClass, boolean needNonNull, char[][] inheritedAnnotationName) {
this.handle(
needNonNull ? IProblem.ParameterLackingNonNullAnnotation : IProblem.ParameterLackingNullableAnnotation,
new String[] { new String(argument.name), new String(declaringClass.readableName()), CharOperation.toString(inheritedAnnotationName)},
new String[] { new String(argument.name), new String(declaringClass.shortReadableName()), new String(inheritedAnnotationName[inheritedAnnotationName.length-1])},
argument.type.sourceStart,
argument.type.sourceEnd);
}
public void illegalReturnRedefinition(AbstractMethodDeclaration abstractMethodDecl,
MethodBinding inheritedMethod, char[][] nonNullAnnotationName)
{
MethodDeclaration methodDecl = (MethodDeclaration) abstractMethodDecl;
StringBuffer methodSignature = new StringBuffer();
methodSignature
.append(inheritedMethod.getDeclaringClass().readableName())
.append('.')
.append(inheritedMethod.readableName());
StringBuffer shortSignature = new StringBuffer();
shortSignature
.append(inheritedMethod.getDeclaringClass().shortReadableName())
.append('.')
.append(inheritedMethod.shortReadableName());
int sourceStart = methodDecl.getReturnType().sourceStart;
Annotation[] annotations = methodDecl.getAnnotations();
if (annotations != null) {
for (int i=0; i<annotations.length; i++) {
if (annotations[i].resolvedType.id == TypeIds.T_ConfiguredAnnotationNullable) {
sourceStart = annotations[i].sourceStart;
break;
}
}
}
this.handle(
IProblem.IllegalReturnNullityRedefinition,
new String[] { methodSignature.toString(), CharOperation.toString(nonNullAnnotationName)},
new String[] { shortSignature.toString(), new String(nonNullAnnotationName[nonNullAnnotationName.length-1])},
sourceStart,
methodDecl.getReturnType().sourceEnd);
}
public void messageSendPotentialNullReference(MethodBinding method, ASTNode location) {
String[] arguments = new String[] {new String(method.readableName())};
this.handle(
IProblem.PotentialNullMessageSendReference,
arguments,
arguments,
location.sourceStart,
location.sourceEnd);
}
public void messageSendRedundantCheckOnNonNull(MethodBinding method, ASTNode location) {
String[] arguments = new String[] {new String(method.readableName()) };
this.handle(
IProblem.RedundantNullCheckOnNonNullMessageSend,
arguments,
arguments,
location.sourceStart,
location.sourceEnd);
}
public void missingNullAnnotationType(char[][] nullAnnotationName) {
String[] args = { new String(CharOperation.concatWith(nullAnnotationName, '.')) };
this.handle(IProblem.MissingNullAnnotationType, args, args, 0, 0);
}
public void cannotImplementIncompatibleNullness(MethodBinding currentMethod, MethodBinding inheritedMethod) {
ReferenceContext ctx = getReferenceContext();
int sourceStart = 0, sourceEnd = 0;
if (ctx instanceof TypeDeclaration) {
sourceStart = ((TypeDeclaration) ctx).sourceStart;
sourceEnd = ((TypeDeclaration) ctx).sourceEnd;
}
String[] problemArguments = {
new String(currentMethod.readableName()),
new String(currentMethod.getDeclaringClass().readableName()),
new String(inheritedMethod.getDeclaringClass().readableName())
};
String[] messageArguments = {
new String(currentMethod.shortReadableName()),
new String(currentMethod.getDeclaringClass().shortReadableName()),
new String(inheritedMethod.getDeclaringClass().shortReadableName())
};
this.handle(
IProblem.CannotImplementIncompatibleNullness,
problemArguments,
messageArguments,
sourceStart,
sourceEnd);
}
public void nullAnnotationNameMustBeQualified(char[][] typeName) {
String[] name = {new String(typeName[0])};
this.handle(IProblem.NullAnnotationNameMustBeQualified, name, name, 0, 0);
}
public void nullAnnotationIsRedundant(AbstractMethodDeclaration sourceMethod, int i) {
int sourceStart, sourceEnd;
if (i == -1) {
org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration decl = sourceMethod;
org.eclipse.jdt.internal.compiler.ast.MethodDeclaration methodDecl = (org.eclipse.jdt.internal.compiler.ast.MethodDeclaration) decl;
sourceStart = methodDecl.returnType.sourceStart;
if (methodDecl.annotations != null) {
// should have a @NonNull annotation, search for it:
for (int j=0; j<methodDecl.annotations.length; j++) {
if (methodDecl.annotations[j].resolvedType.id == TypeIds.T_ConfiguredAnnotationNonNull) {
sourceStart = methodDecl.annotations[j].sourceStart;
break;
}
}
}
sourceEnd = methodDecl.returnType.sourceEnd;
} else {
Argument arg = sourceMethod.getArguments()[i];
sourceStart = arg.declarationSourceStart;
sourceEnd = arg.sourceEnd;
}
this.handle(IProblem.NullAnnotationIsRedundant, ProblemHandler.NoArgument, ProblemHandler.NoArgument, sourceStart, sourceEnd);
}
// NOTE: adaptation of toString() omitted
}
/** Supply more error messages. */
protected class ProblemFactory playedBy DefaultProblemFactory {
@SuppressWarnings("decapsulation")
int keyFromID(int id) -> int keyFromID(int id);
void loadMessageTemplates(HashtableOfInt templates, Locale loc)
<- after HashtableOfInt loadMessageTemplates(Locale loc)
with { templates <- result, loc <- loc }
@SuppressWarnings("rawtypes") // copied from its base class, only bundleName has been changed
public static void loadMessageTemplates(HashtableOfInt templates, Locale loc) {
ResourceBundle bundle = null;
String bundleName = "org.eclipse.objectteams.internal.jdt.nullity.problem_messages"; //$NON-NLS-1$
try {
bundle = ResourceBundle.getBundle(bundleName, loc);
} catch(MissingResourceException e) {
System.out.println("Missing resource : " + bundleName.replace('.', '/') + ".properties for locale " + loc); //$NON-NLS-1$//$NON-NLS-2$
throw e;
}
Enumeration keys = bundle.getKeys();
while (keys.hasMoreElements()) {
String key = (String)keys.nextElement();
try {
int messageID = Integer.parseInt(key);
templates.put(keyFromID(messageID), bundle.getString(key));
} catch(NumberFormatException e) {
// key ill-formed
} catch (MissingResourceException e) {
// available ID
}
}
}
}
// ================================== Compiler Options: ==================================
// DONE
@SuppressWarnings("rawtypes")
protected class CompilerOptions implements org.eclipse.objectteams.internal.jdt.nullity.NullCompilerOptions playedBy CompilerOptions
{
@SuppressWarnings("decapsulation")
void updateSeverity(int irritant, Object severityString) -> void updateSeverity(int irritant, Object severityString);
String getSeverityString(int irritant) -> String getSeverityString(int irritant);
public boolean isAnnotationBasedNullAnalysisEnabled;
/** Fully qualified name of annotation to use as marker for nullable types. */
public char[][] nullableAnnotationName;
/** Fully qualified name of annotation to use as marker for nonnull types. */
public char[][] nonNullAnnotationName;
/** Fully qualified name of annotation to use as marker for default nonnull. */
public char[][] nonNullByDefaultAnnotationName;
public long defaultNonNullness; // 0 or TagBits#AnnotationNullable or TagBits#AnnotationNonNull
String optionKeyFromIrritant(int irritant) <- replace String optionKeyFromIrritant(int irritant);
@SuppressWarnings("basecall")
static callin String optionKeyFromIrritant(int irritant) {
switch(irritant) {
case NullSpecViolation :
return OPTION_ReportNullSpecViolation;
case PotentialNullSpecViolation :
return OPTION_ReportPotentialNullSpecViolation;
case NullSpecInsufficientInfo :
return OPTION_ReportNullSpecInsufficientInfo;
case RedundantNullAnnotation :
return OPTION_ReportRedundantNullAnnotation;
default:
return base.optionKeyFromIrritant(irritant);
}
}
String warningTokenFromIrritant(int irritant) <- replace String warningTokenFromIrritant(int irritant);
@SuppressWarnings("basecall")
static callin String warningTokenFromIrritant(int irritant) {
switch(irritant) {
case NullSpecViolation :
case PotentialNullSpecViolation :
case NullSpecInsufficientInfo :
case RedundantNullAnnotation :
return "null"; //$NON-NLS-1$
default:
return base.warningTokenFromIrritant(irritant);
}
}
void getMap(Map optionsMap) <- after Map getMap()
with {optionsMap <- result}
@SuppressWarnings("unchecked")
private void getMap(Map optionsMap) {
optionsMap.put(OPTION_AnnotationBasedNullAnalysis, this.isAnnotationBasedNullAnalysisEnabled ? ENABLED : DISABLED);
if (this.isAnnotationBasedNullAnalysisEnabled) {
optionsMap.put(OPTION_ReportNullSpecViolation, getSeverityString(NullSpecViolation));
optionsMap.put(OPTION_ReportPotentialNullSpecViolation, getSeverityString(PotentialNullSpecViolation));
optionsMap.put(OPTION_ReportNullSpecInsufficientInfo, getSeverityString(NullSpecInsufficientInfo));
optionsMap.put(OPTION_ReportRedundantNullAnnotation, getSeverityString(RedundantNullAnnotation));
if (this.nullableAnnotationName != null) {
char[] compoundName = CharOperation.concatWith(this.nullableAnnotationName, '.');
optionsMap.put(OPTION_NullableAnnotationName, String.valueOf(compoundName));
}
if (this.nonNullAnnotationName != null) {
char[] compoundName = CharOperation.concatWith(this.nonNullAnnotationName, '.');
optionsMap.put(OPTION_NonNullAnnotationName, String.valueOf(compoundName));
}
if (this.nonNullByDefaultAnnotationName != null) {
char[] compoundName = CharOperation.concatWith(this.nonNullByDefaultAnnotationName, '.');
optionsMap.put(OPTION_NonNullByDefaultAnnotationName, String.valueOf(compoundName));
}
if (this.defaultNonNullness == TagBits.AnnotationNonNull)
optionsMap.put(OPTION_NonNullIsDefault, CompilerOptions.ENABLED);
else
optionsMap.put(OPTION_NonNullIsDefault, CompilerOptions.DISABLED);
}
}
void set(Map optionsMap) <- before void set(Map optionsMap);
private void set(Map optionsMap) {
Object optionValue;
if ((optionValue = optionsMap.get(OPTION_AnnotationBasedNullAnalysis)) != null) {
if (ENABLED.equals(optionValue)) {
this.isAnnotationBasedNullAnalysisEnabled = true;
// ensure that we actually have annotation names to use:
ensureNullAnnotationNames();
} else if (DISABLED.equals(optionValue)) {
this.isAnnotationBasedNullAnalysisEnabled = false;
}
}
if (this.isAnnotationBasedNullAnalysisEnabled) {
if ((optionValue = optionsMap.get(OPTION_ReportNullSpecViolation)) != null) updateSeverity(NullSpecViolation, optionValue);
if ((optionValue = optionsMap.get(OPTION_ReportPotentialNullSpecViolation)) != null) updateSeverity(PotentialNullSpecViolation, optionValue);
if ((optionValue = optionsMap.get(OPTION_ReportNullSpecInsufficientInfo)) != null) updateSeverity(NullSpecInsufficientInfo, optionValue);
if ((optionValue = optionsMap.get(OPTION_ReportRedundantNullAnnotation)) != null) updateSeverity(RedundantNullAnnotation, optionValue);
if ((optionValue = optionsMap.get(OPTION_NullableAnnotationName)) != null) {
this.nullableAnnotationName = CharOperation.splitAndTrimOn('.', ((String)optionValue).toCharArray());
}
if ((optionValue = optionsMap.get(OPTION_NonNullAnnotationName)) != null) {
this.nonNullAnnotationName = CharOperation.splitAndTrimOn('.', ((String)optionValue).toCharArray());
}
if ((optionValue = optionsMap.get(OPTION_NonNullByDefaultAnnotationName)) != null) {
this.nonNullByDefaultAnnotationName = CharOperation.splitAndTrimOn('.', ((String)optionValue).toCharArray());
}
if ((optionValue = optionsMap.get(OPTION_NonNullIsDefault)) != null) {
if (CompilerOptions.ENABLED.equals(optionValue)) {
defaultNonNullness = TagBits.AnnotationNonNull;
ensureNullAnnotationNames();
} else {
defaultNonNullness = 0;
}
}
}
}
private void ensureNullAnnotationNames() {
if (this.nullableAnnotationName == null)
this.nullableAnnotationName = NullCompilerOptions.DEFAULT_NULLABLE_ANNOTATION_NAME;
if (this.nonNullAnnotationName == null)
this.nonNullAnnotationName = NullCompilerOptions.DEFAULT_NONNULL_ANNOTATION_NAME;
if (this.nonNullByDefaultAnnotationName == null)
this.nonNullByDefaultAnnotationName = NullCompilerOptions.DEFAULT_NONNULLBYDEFAULT_ANNOTATION_NAME;
}
}
@SuppressWarnings("rawtypes")
protected class JavaModelManager playedBy JavaModelManager {
JavaModelManager getJavaModelManager() -> JavaModelManager getJavaModelManager();
@SuppressWarnings("decapsulation")
protected HashSet getOptionNames() -> get HashSet optionNames;
IEclipsePreferences getInstancePreferences() -> IEclipsePreferences getInstancePreferences();
public void storePreference(String key, char[][] value) {
getInstancePreferences().put(key, String.valueOf(CharOperation.concatWith(value, '.')));
}
}
@SuppressWarnings({"rawtypes","unchecked"})
protected class JavaProject playedBy JavaProject {
void fillOptionNames() <- before Map getOptions(boolean inheritJavaCoreOptions);
private void fillOptionNames() {
JavaModelManager javaModelManager = JavaModelManager.getJavaModelManager();
HashSet optionNames = javaModelManager.getOptionNames();
if (optionNames.contains(NullCompilerOptions.OPTION_AnnotationBasedNullAnalysis))
return;
optionNames.add(NullCompilerOptions.OPTION_AnnotationBasedNullAnalysis);
optionNames.add(NullCompilerOptions.OPTION_NonNullAnnotationName);
optionNames.add(NullCompilerOptions.OPTION_NullableAnnotationName);
optionNames.add(NullCompilerOptions.OPTION_NonNullByDefaultAnnotationName);
optionNames.add(NullCompilerOptions.OPTION_ReportNullSpecInsufficientInfo);
optionNames.add(NullCompilerOptions.OPTION_ReportNullSpecViolation);
optionNames.add(NullCompilerOptions.OPTION_ReportPotentialNullSpecViolation);
optionNames.add(NullCompilerOptions.OPTION_ReportRedundantNullAnnotation);
optionNames.add(NullCompilerOptions.OPTION_NonNullIsDefault);
// also add to the instance preferences:
javaModelManager.storePreference(NullCompilerOptions.OPTION_NullableAnnotationName, NullCompilerOptions.DEFAULT_NULLABLE_ANNOTATION_NAME);
javaModelManager.storePreference(NullCompilerOptions.OPTION_NullableAnnotationName, NullCompilerOptions.DEFAULT_NULLABLE_ANNOTATION_NAME);
javaModelManager.storePreference(NullCompilerOptions.OPTION_NonNullAnnotationName, NullCompilerOptions.DEFAULT_NONNULL_ANNOTATION_NAME);
javaModelManager.storePreference(NullCompilerOptions.OPTION_NonNullByDefaultAnnotationName, NullCompilerOptions.DEFAULT_NONNULLBYDEFAULT_ANNOTATION_NAME);
}
}
}