Java tutorial
/******************************************************************************* * 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 <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; @SuppressWarnings("rawtypes") public class LocalDeclaration extends AbstractVariableDeclaration { public LocalVariableBinding binding; 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 } 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. 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<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) { // 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 && (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); } }