org.eclipse.jdt.internal.compiler.ast.LocalDeclaration Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of ecj Show documentation
Show all versions of ecj Show documentation
Eclipse Compiler for Java(TM)
/*******************************************************************************
* Copyright (c) 2000, 2019 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
* Stephan Herrmann - 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) [email protected] - 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 java.util.function.Consumer;
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;
@SuppressWarnings("rawtypes")
public class LocalDeclaration extends AbstractVariableDeclaration {
public LocalVariableBinding binding;
/**
* For pattern variable, resolve() may store here an obligation to be checked when we have
* a flow info that tells us whether a potential duplicates is in fact in scope.
*/
Consumer duplicateCheckObligation;
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
}
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
&& 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
}
if (this.duplicateCheckObligation != null) {
this.duplicateCheckObligation.accept(flowInfo);
}
return flowInfo;
}
public void checkModifiers() {
//only potential valid modifier is <>
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. 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;
}
this.initialization.generateCode(currentScope, codeStream, true);
// 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 allAnnotationContexts) {
AnnotationCollector collector = new AnnotationCollector(this, targetType, localVariable, allAnnotationContexts);
this.traverseWithoutInitializer(collector, (BlockScope) null);
}
// for arguments
public void getAllAnnotationContexts(int targetType, int parameterIndex, List 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 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);
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*/);
}
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 && (isPatternVariable
|| (((LocalVariableBinding) existingVariable).modifiers & ExtraCompilerModifiers.AccPatternVariable) != 0))
{
// Do this only if either one of them is a pattern variable.
this.duplicateCheckObligation = (flowInfo) -> {
if (flowInfo.isDefinitelyAssigned((LocalVariableBinding) existingVariable)) {
scope.problemReporter().redefineLocal(this);
}
};
} else {
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
if (variableType == null) {
if (this.initialization != null) {
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) {
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);
}
}
}
}
// 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);
}
// 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);
}
}