blob: f19dd010df45bc58d42b129ebe024b22b496fb54 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2020 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:
* IBM Corporation - initial API and implementation
* Fraunhofer FIRST - extended API and implementation
* Technical University Berlin - extended API and implementation
* Stephan Herrmann <stephan@cs.tu-berlin.de> - Contributions for
* bug 319201 - [null] no warning when unboxing SingleNameReference causes NPE
* bug 292478 - Report potentially null across variable assignment
* bug 335093 - [compiler][null] minimal hook for future null annotation support
* bug 349326 - [1.7] new warning for missing try-with-resources
* bug 186342 - [compiler][null] Using annotations for null checking
* bug 358903 - Filter practically unimportant resource leak warnings
* bug 370639 - [compiler][resource] restore the default for resource leak warnings
* bug 365859 - [compiler][null] distinguish warnings based on flow analysis vs. null annotations
* bug 388996 - [compiler][resource] Incorrect 'potential resource leak'
* bug 394768 - [compiler][resource] Incorrect resource leak warning when creating stream in conditional
* bug 395002 - Self bound generic class doesn't resolve bounds properly for wildcards for certain parametrisation.
* bug 383368 - [compiler][null] syntactic null analysis for field references
* bug 400761 - [compiler][null] null may be return as boolean without a diagnostic
* Bug 392238 - [1.8][compiler][null] Detect semantically invalid null type annotations
* Bug 392099 - [1.8][compiler][null] Apply null annotation on types for null analysis
* Bug 427438 - [1.8][compiler] NPE at org.eclipse.jdt.internal.compiler.ast.ConditionalExpression.generateCode(ConditionalExpression.java:280)
* Bug 430150 - [1.8][null] stricter checking against type variables
* Bug 453483 - [compiler][null][loop] Improve null analysis for loops
* Jesper S Moller - Contributions for
* Bug 378674 - "The method can be declared as static" is wrong
* Bug 527554 - [18.3] Compiler support for JEP 286 Local-Variable Type
* Bug 529556 - [18.3] Add content assist support for 'var' as a type
* Andy Clement (GoPivotal, Inc) aclement@gopivotal.com - Contributions for
* Bug 409250 - [1.8][compiler] Various loose ends in 308 code generation
* Bug 426616 - [1.8][compiler] Type Annotations, multiple problems
*******************************************************************************/
package org.eclipse.jdt.internal.compiler.ast;
import static org.eclipse.jdt.internal.compiler.ast.ExpressionContext.ASSIGNMENT_CONTEXT;
import static org.eclipse.jdt.internal.compiler.ast.ExpressionContext.VANILLA_CONTEXT;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.eclipse.jdt.internal.compiler.ASTVisitor;
import org.eclipse.jdt.internal.compiler.impl.*;
import org.eclipse.jdt.internal.compiler.ast.TypeReference.AnnotationCollector;
import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
import org.eclipse.jdt.internal.compiler.codegen.*;
import org.eclipse.jdt.internal.compiler.flow.*;
import org.eclipse.jdt.internal.compiler.lookup.*;
import org.eclipse.jdt.internal.compiler.parser.RecoveryScanner;
import org.eclipse.objectteams.otdt.internal.core.compiler.control.Config;
import org.eclipse.objectteams.otdt.internal.core.compiler.lookup.DependentTypeBinding;
import org.eclipse.objectteams.otdt.internal.core.compiler.util.IAlienScopeTypeReference;
import org.eclipse.objectteams.otdt.internal.core.compiler.util.RoleTypeCreator;
/**
* OTDT changes:
*
* What: Flag "isGenerated"
* Why: Signal that generated variables have no source position
*
* What: Locals with flag "isPlaceHolde" reserve a slot for an argument to be added later
* Why: Arguments and locals are assigned consecutive slots, if a marker arg
* is to be added later during copy, a slot must have been reserved for it.
*
* What: Locals with flag "isPreparingForLifting" support declared lifting in constructors.
* How: See class comment in class Lifting (item C.2.).
*
* What: Wrap role types for declared type and initialization expression.
*
* What: Record team anchor equivalence in TeamAnchor.bestNamePath.
*
* @version $Id: LocalDeclaration.java 23405 2010-02-03 17:02:18Z stephan $
*/
public class LocalDeclaration extends AbstractVariableDeclaration {
public LocalVariableBinding binding;
//{ObjectTeams: additional flags for generated local vars:
/** is this a faked var preparing a team ctor for arg-lifting? */
public boolean isPreparingForLifting = false;
/** is this a dummy variable just reserving a slot
* for a marker arg that might be added during copy? */
public boolean isPlaceHolder = false;
/** Has this declaration been generated when translating specific OT constructs? */
public boolean isGenerated = false;
// SH}
public LocalDeclaration(
char[] name,
int sourceStart,
int sourceEnd) {
this.name = name;
this.sourceStart = sourceStart;
this.sourceEnd = sourceEnd;
this.declarationEnd = sourceEnd;
}
@Override
public FlowInfo analyseCode(BlockScope currentScope, FlowContext flowContext, FlowInfo flowInfo) {
// record variable initialization if any
if ((flowInfo.tagBits & FlowInfo.UNREACHABLE_OR_DEAD) == 0) {
this.bits |= ASTNode.IsLocalDeclarationReachable; // only set if actually reached
}
//{ObjectTeams: pretend place holder is used:
if (this.isPlaceHolder)
this.binding.useFlag = LocalVariableBinding.USED;
// SH}
if (this.initialization == null) {
return flowInfo;
}
this.initialization.checkNPEbyUnboxing(currentScope, flowContext, flowInfo);
FlowInfo preInitInfo = null;
boolean shouldAnalyseResource = this.binding != null
&& flowInfo.reachMode() == FlowInfo.REACHABLE
&& currentScope.compilerOptions().analyseResourceLeaks
//{ObjectTeams: notably lift methods of Closeable roles would trigger warnings against synthetic code
&& !currentScope.isGeneratedScope()
// SH}
&& FakedTrackingVariable.isAnyCloseable(this.initialization.resolvedType);
if (shouldAnalyseResource) {
preInitInfo = flowInfo.unconditionalCopy();
// analysis of resource leaks needs additional context while analyzing the RHS:
FakedTrackingVariable.preConnectTrackerAcrossAssignment(this, this.binding, this.initialization, flowInfo);
}
flowInfo =
this.initialization
.analyseCode(currentScope, flowContext, flowInfo)
.unconditionalInits();
if (shouldAnalyseResource)
FakedTrackingVariable.handleResourceAssignment(currentScope, preInitInfo, flowInfo, flowContext, this, this.initialization, this.binding);
else
FakedTrackingVariable.cleanUpAfterAssignment(currentScope, Binding.LOCAL, this.initialization);
int nullStatus = this.initialization.nullStatus(flowInfo, flowContext);
if (!flowInfo.isDefinitelyAssigned(this.binding)){// for local variable debug attributes
this.bits |= FirstAssignmentToLocal;
} else {
this.bits &= ~FirstAssignmentToLocal; // int i = (i = 0);
}
flowInfo.markAsDefinitelyAssigned(this.binding);
if (currentScope.compilerOptions().isAnnotationBasedNullAnalysisEnabled) {
nullStatus = NullAnnotationMatching.checkAssignment(currentScope, flowContext, this.binding, flowInfo, nullStatus, this.initialization, this.initialization.resolvedType);
}
if ((this.binding.type.tagBits & TagBits.IsBaseType) == 0) {
flowInfo.markNullStatus(this.binding, nullStatus);
// no need to inform enclosing try block since its locals won't get
// known by the finally block
}
return flowInfo;
}
public void checkModifiers() {
//only potential valid modifier is <<final>>
if (((this.modifiers & ExtraCompilerModifiers.AccJustFlag) & ~ClassFileConstants.AccFinal) != 0)
//AccModifierProblem -> other (non-visibility problem)
//AccAlternateModifierProblem -> duplicate modifier
//AccModifierProblem | AccAlternateModifierProblem -> visibility problem"
this.modifiers = (this.modifiers & ~ExtraCompilerModifiers.AccAlternateModifierProblem) | ExtraCompilerModifiers.AccModifierProblem;
}
/**
* Code generation for a local declaration:
* i.e.&nbsp;normal assignment to a local variable + unused variable handling
*/
@Override
public void generateCode(BlockScope currentScope, CodeStream codeStream) {
// even if not reachable, variable must be added to visible if allocated (28298)
if (this.binding.resolvedPosition != -1) {
codeStream.addVisibleLocalVariable(this.binding);
}
if ((this.bits & IsReachable) == 0) {
return;
}
int pc = codeStream.position;
// something to initialize?
generateInit: {
if (this.initialization == null)
break generateInit;
// forget initializing unused or final locals set to constant value (final ones are inlined)
if (this.binding.resolvedPosition < 0) {
if (this.initialization.constant != Constant.NotAConstant)
break generateInit;
// if binding unused generate then discard the value
this.initialization.generateCode(currentScope, codeStream, false);
break generateInit;
}
//{ObjectTeams: prepare for lifting (see comments in Lifting!)
if (this.isPreparingForLifting) {
for (int i=0;i<6;i++)
codeStream.nop(); // space for: aload_0; invokevirtual _OT$initCaches();pop;
// aload_0;
}
// SH}
this.initialization.generateCode(currentScope, codeStream, true);
//{ObjectTeams: prepare for lifting:
if (this.isPreparingForLifting)
for (int i=0;i<3;i++)
codeStream.nop(); // space for: invokevirtual _OT$liftTo$<R>(base);
// SH}
// 26903, need extra cast to store null in array local var
if (this.binding.type.isArrayType()
&& ((this.initialization instanceof CastExpression) // arrayLoc = (type[])null
&& (((CastExpression)this.initialization).innermostCastedExpression().resolvedType == TypeBinding.NULL))){
codeStream.checkcast(this.binding.type);
}
codeStream.store(this.binding, false);
if ((this.bits & ASTNode.FirstAssignmentToLocal) != 0) {
/* Variable may have been initialized during the code initializing it
e.g. int i = (i = 1);
*/
this.binding.recordInitializationStartPC(codeStream.position);
}
}
codeStream.recordPositionsFrom(pc, this.sourceStart);
}
/**
* @see org.eclipse.jdt.internal.compiler.ast.AbstractVariableDeclaration#getKind()
*/
@Override
public int getKind() {
return LOCAL_VARIABLE;
}
// for local variables
public void getAllAnnotationContexts(int targetType, LocalVariableBinding localVariable, List<AnnotationContext> allAnnotationContexts) {
AnnotationCollector collector = new AnnotationCollector(this, targetType, localVariable, allAnnotationContexts);
this.traverseWithoutInitializer(collector, (BlockScope) null);
}
// for arguments
public void getAllAnnotationContexts(int targetType, int parameterIndex, List<AnnotationContext> allAnnotationContexts) {
AnnotationCollector collector = new AnnotationCollector(this, targetType, parameterIndex, allAnnotationContexts);
this.traverse(collector, (BlockScope) null);
}
public boolean isArgument() {
return false;
}
public boolean isReceiver() {
return false;
}
public TypeBinding patchType(TypeBinding newType) {
// Perform upwards projection on type wrt mentioned type variables
TypeBinding[] mentionedTypeVariables= findCapturedTypeVariables(newType);
if (mentionedTypeVariables != null && mentionedTypeVariables.length > 0) {
newType = newType.upwardsProjection(this.binding.declaringScope, mentionedTypeVariables);
}
this.type.resolvedType = newType;
if (this.binding != null) {
this.binding.type = newType;
this.binding.markInitialized();
}
return this.type.resolvedType;
}
private TypeVariableBinding[] findCapturedTypeVariables(TypeBinding typeBinding) {
final Set<TypeVariableBinding> mentioned = new HashSet<>();
TypeBindingVisitor.visit(new TypeBindingVisitor() {
@Override
public boolean visit(TypeVariableBinding typeVariable) {
if (typeVariable.isCapture())
mentioned.add(typeVariable);
return super.visit(typeVariable);
}
}, typeBinding);
if (mentioned.isEmpty()) return null;
return mentioned.toArray(new TypeVariableBinding[mentioned.size()]);
}
private static Expression findPolyExpression(Expression e) {
// This is simpler than using an ASTVisitor, since we only care about a very few select cases.
if (e instanceof FunctionalExpression) {
return e;
}
if (e instanceof ConditionalExpression) {
ConditionalExpression ce = (ConditionalExpression)e;
Expression candidate = findPolyExpression(ce.valueIfTrue);
if (candidate == null) {
candidate = findPolyExpression(ce.valueIfFalse);
}
if (candidate != null) return candidate;
}
if (e instanceof SwitchExpression) {
SwitchExpression se = (SwitchExpression)e;
for (Expression re : se.resultExpressions) {
Expression candidate = findPolyExpression(re);
if (candidate != null) return candidate;
}
}
return null;
}
@Override
public void resolve(BlockScope scope) {
resolve(scope, false);
}
public void resolve(BlockScope scope, boolean isPatternVariable) {
// prescan NNBD
handleNonNullByDefault(scope, this.annotations, this);
TypeBinding variableType = null;
boolean variableTypeInferenceError = false;
boolean isTypeNameVar = isTypeNameVar(scope);
//{ObjectTeams: avoid duplicate resolving:
if (this.binding == null) {
// SH}
if (isTypeNameVar) {
if ((this.bits & ASTNode.IsForeachElementVariable) == 0) {
// infer a type from the initializer
if (this.initialization != null) {
variableType = checkInferredLocalVariableInitializer(scope);
variableTypeInferenceError = variableType != null;
} else {
// That's always an error
scope.problemReporter().varLocalWithoutInitizalier(this);
variableType = scope.getJavaLangObject();
variableTypeInferenceError = true;
}
}
} else {
variableType = this.type.resolveType(scope, true /* check bounds*/);
}
//{ObjectTeams: wrap declared type
{
Scope typeScope = scope;
if (this.type instanceof IAlienScopeTypeReference)
typeScope = ((IAlienScopeTypeReference)this.type).getAlienScope();
variableType = RoleTypeCreator.maybeWrapUnqualifiedRoleType(variableType, typeScope, this);
}
// SH}
this.bits |= (this.type.bits & ASTNode.HasTypeAnnotations);
checkModifiers();
if (variableType != null) {
if (variableType == TypeBinding.VOID) {
scope.problemReporter().variableTypeCannotBeVoid(this);
return;
}
if (variableType.isArrayType() && ((ArrayBinding) variableType).leafComponentType == TypeBinding.VOID) {
scope.problemReporter().variableTypeCannotBeVoidArray(this);
return;
}
}
Binding existingVariable = scope.getBinding(this.name, Binding.VARIABLE, this, false /*do not resolve hidden field*/);
if (existingVariable != null && existingVariable.isValidBinding()){
boolean localExists = existingVariable instanceof LocalVariableBinding;
if (localExists && (this.bits & ASTNode.ShadowsOuterLocal) != 0 && scope.isLambdaSubscope() && this.hiddenVariableDepth == 0) {
scope.problemReporter().lambdaRedeclaresLocal(this);
} else if (localExists && this.hiddenVariableDepth == 0) {
scope.problemReporter().redefineLocal(this);
} else {
scope.problemReporter().localVariableHiding(this, existingVariable, false);
}
}
if ((this.modifiers & ClassFileConstants.AccFinal)!= 0 && this.initialization == null) {
this.modifiers |= ExtraCompilerModifiers.AccBlankFinal;
}
if (isTypeNameVar) {
// Create binding for the initializer's type
// In order to resolve self-referential initializers, we must declare the variable with a placeholder type (j.l.Object), and then patch it later
this.binding = new LocalVariableBinding(this, variableType != null ? variableType : scope.getJavaLangObject(), this.modifiers, false) {
private boolean isInitialized = false;
@Override
public void markReferenced() {
if (! this.isInitialized) {
scope.problemReporter().varLocalReferencesItself(LocalDeclaration.this);
this.type = null;
this.isInitialized = true; // Quell additional type errors
}
}
@Override
public void markInitialized() {
this.isInitialized = true;
}
};
} else {
// create a binding from the specified type
this.binding = new LocalVariableBinding(this, variableType, this.modifiers, false /*isArgument*/);
}
scope.addLocalVariable(this.binding);
this.binding.setConstant(Constant.NotAConstant);
// allow to recursivelly target the binding....
// the correct constant is harmed if correctly computed at the end of this method
//{ObjectTeams: end if (this.binding == null)
} else {
variableType = this.binding.type;
}
// SH}
if (variableType == null) {
if (this.initialization != null) {
if (this.initialization instanceof CastExpression) {
((CastExpression)this.initialization).setVarTypeDeclaration(true);
}
this.initialization.resolveType(scope); // want to report all possible errors
if (isTypeNameVar && this.initialization.resolvedType != null) {
if (TypeBinding.equalsEquals(TypeBinding.NULL, this.initialization.resolvedType)) {
scope.problemReporter().varLocalInitializedToNull(this);
variableTypeInferenceError = true;
} else if (TypeBinding.equalsEquals(TypeBinding.VOID, this.initialization.resolvedType)) {
scope.problemReporter().varLocalInitializedToVoid(this);
variableTypeInferenceError = true;
}
variableType = patchType(this.initialization.resolvedType);
} else {
variableTypeInferenceError = true;
}
}
}
this.binding.markInitialized();
if (variableTypeInferenceError) {
return;
}
boolean resolveAnnotationsEarly = false;
if (scope.environment().usesNullTypeAnnotations()
&& !isTypeNameVar // 'var' does not provide a target type
&& variableType != null && variableType.isValidBinding()) {
resolveAnnotationsEarly = this.initialization instanceof Invocation
|| this.initialization instanceof ConditionalExpression
|| this.initialization instanceof SwitchExpression
|| this.initialization instanceof ArrayInitializer;
}
if (resolveAnnotationsEarly) {
// these are definitely no constants, so resolving annotations early should be safe
resolveAnnotations(scope, this.annotations, this.binding, true);
// for type inference having null annotations upfront gives better results
variableType = this.type.resolvedType;
}
if (this.initialization != null) {
if (this.initialization instanceof ArrayInitializer) {
TypeBinding initializationType = this.initialization.resolveTypeExpecting(scope, variableType);
if (initializationType != null) {
((ArrayInitializer) this.initialization).binding = (ArrayBinding) initializationType;
this.initialization.computeConversion(scope, variableType, initializationType);
}
} else {
this.initialization.setExpressionContext(isTypeNameVar ? VANILLA_CONTEXT : ASSIGNMENT_CONTEXT);
this.initialization.setExpectedType(variableType);
TypeBinding initializationType = this.initialization.resolvedType != null ? this.initialization.resolvedType : this.initialization.resolveType(scope);
if (initializationType != null) {
//{ObjectTeams: wrap rhs type:
initializationType = RoleTypeCreator.maybeWrapUnqualifiedRoleType(initializationType, scope, this.initialization);
// SH}
if (TypeBinding.notEquals(variableType, initializationType)) // must call before computeConversion() and typeMismatchError()
scope.compilationUnitScope().recordTypeConversion(variableType, initializationType);
if (this.initialization.isConstantValueOfTypeAssignableToType(initializationType, variableType)
|| initializationType.isCompatibleWith(variableType, scope)) {
this.initialization.computeConversion(scope, variableType, initializationType);
if (initializationType.needsUncheckedConversion(variableType)) {
scope.problemReporter().unsafeTypeConversion(this.initialization, initializationType, variableType);
}
if (this.initialization instanceof CastExpression
&& (this.initialization.bits & ASTNode.UnnecessaryCast) == 0) {
CastExpression.checkNeedForAssignedCast(scope, variableType, (CastExpression) this.initialization);
}
} else if (isBoxingCompatible(initializationType, variableType, this.initialization, scope)) {
this.initialization.computeConversion(scope, variableType, initializationType);
if (this.initialization instanceof CastExpression
&& (this.initialization.bits & ASTNode.UnnecessaryCast) == 0) {
CastExpression.checkNeedForAssignedCast(scope, variableType, (CastExpression) this.initialization);
}
} else {
if ((variableType.tagBits & TagBits.HasMissingType) == 0) {
// if problem already got signaled on type, do not report secondary problem
scope.problemReporter().typeMismatchError(initializationType, variableType, this.initialization, null);
}
}
//{ObjectTeams: additional re-check:
if (Config.getLoweringPossible() && initializationType != null && initializationType.isRoleType())
((DependentTypeBinding)initializationType).recheckAmbiguousLowering(variableType, this, scope, null);
// SH}
}
}
// check for assignment with no effect
if (this.binding == Expression.getDirectBinding(this.initialization)) {
scope.problemReporter().assignmentHasNoEffect(this, this.name);
}
// change the constant in the binding when it is final
// (the optimization of the constant propagation will be done later on)
// cast from constant actual type to variable type
this.binding.setConstant(
this.binding.isFinal()
? this.initialization.constant.castTo((variableType.id << 4) + this.initialization.constant.typeID())
: Constant.NotAConstant);
//{ObjectTeams: record team anchor equivalence:
this.binding.setBestNameFromStat(this.initialization);
// SH}
}
// if init could be a constant only resolve annotation at the end, for constant to be positioned before (96991)
if (!resolveAnnotationsEarly)
resolveAnnotations(scope, this.annotations, this.binding, true);
Annotation.isTypeUseCompatible(this.type, scope, this.annotations);
validateNullAnnotations(scope);
}
void validateNullAnnotations(BlockScope scope) {
if (!scope.validateNullAnnotation(this.binding.tagBits, this.type, this.annotations))
this.binding.tagBits &= ~TagBits.AnnotationNullMASK;
}
/*
* Checks the initializer for simple errors, and reports an error as needed. If error is found,
* returns a reasonable match for further type checking.
*/
private TypeBinding checkInferredLocalVariableInitializer(BlockScope scope) {
TypeBinding errorType = null;
if (this.initialization instanceof ArrayInitializer) {
scope.problemReporter().varLocalCannotBeArrayInitalizers(this);
errorType = scope.createArrayType(scope.getJavaLangObject(), 1); // Treat as array of anything
} else {
// Catch-22: isPolyExpression() is not reliable BEFORE resolveType, so we need to peek to suppress the errors
Expression polyExpression = findPolyExpression(this.initialization);
if (polyExpression instanceof ReferenceExpression) {
scope.problemReporter().varLocalCannotBeMethodReference(this);
errorType = TypeBinding.NULL;
} else if (polyExpression != null) { // Should be instanceof LambdaExpression, but this is safer
scope.problemReporter().varLocalCannotBeLambda(this);
errorType = TypeBinding.NULL;
}
}
if (this.type.dimensions() > 0 || this.type.extraDimensions() > 0) {
scope.problemReporter().varLocalCannotBeArray(this);
errorType = scope.createArrayType(scope.getJavaLangObject(), 1); // This is just to quell some warnings
}
if ((this.bits & ASTNode.IsAdditionalDeclarator) != 0) {
scope.problemReporter().varLocalMultipleDeclarators(this);
errorType = this.initialization.resolveType(scope);
}
return errorType;
}
@Override
public void traverse(ASTVisitor visitor, BlockScope scope) {
if (visitor.visit(this, scope)) {
if (this.annotations != null) {
int annotationsLength = this.annotations.length;
for (int i = 0; i < annotationsLength; i++)
this.annotations[i].traverse(visitor, scope);
}
this.type.traverse(visitor, scope);
if (this.initialization != null)
this.initialization.traverse(visitor, scope);
}
visitor.endVisit(this, scope);
}
private void traverseWithoutInitializer(ASTVisitor visitor, BlockScope scope) {
if (visitor.visit(this, scope)) {
if (this.annotations != null) {
int annotationsLength = this.annotations.length;
for (int i = 0; i < annotationsLength; i++)
this.annotations[i].traverse(visitor, scope);
}
this.type.traverse(visitor, scope);
}
visitor.endVisit(this, scope);
}
public boolean isRecoveredFromLoneIdentifier() { // recovered from lonely identifier or identifier cluster ?
return this.name == RecoveryScanner.FAKE_IDENTIFIER &&
(this.type instanceof SingleTypeReference || (this.type instanceof QualifiedTypeReference && !(this.type instanceof ArrayQualifiedTypeReference))) && this.initialization == null && !this.type.isBaseTypeReference();
}
public boolean isTypeNameVar(Scope scope) {
return this.type != null && this.type.isTypeNameVar(scope);
}
}