/******************************************************************************* * Copyright (c) 2000, 2011 IBM Corporation 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: * IBM Corporation - initial API and implementation * Stephan Herrmann <stephan@cs.tu-berlin.de> - Contribution for bug 185682 - Increment/decrement operators mark local variables as read *******************************************************************************/ package org.eclipse.che.ide.ext.java.jdt.internal.compiler.ast; import org.eclipse.che.ide.ext.java.jdt.core.compiler.CharOperation; import org.eclipse.che.ide.ext.java.jdt.internal.compiler.ASTVisitor; import org.eclipse.che.ide.ext.java.jdt.internal.compiler.ClassFileConstants; import org.eclipse.che.ide.ext.java.jdt.internal.compiler.flow.FlowContext; import org.eclipse.che.ide.ext.java.jdt.internal.compiler.flow.FlowInfo; import org.eclipse.che.ide.ext.java.jdt.internal.compiler.impl.Constant; import org.eclipse.che.ide.ext.java.jdt.internal.compiler.lookup.Binding; import org.eclipse.che.ide.ext.java.jdt.internal.compiler.lookup.BlockScope; import org.eclipse.che.ide.ext.java.jdt.internal.compiler.lookup.FieldBinding; import org.eclipse.che.ide.ext.java.jdt.internal.compiler.lookup.InvocationSite; import org.eclipse.che.ide.ext.java.jdt.internal.compiler.lookup.MethodBinding; import org.eclipse.che.ide.ext.java.jdt.internal.compiler.lookup.MethodScope; import org.eclipse.che.ide.ext.java.jdt.internal.compiler.lookup.MissingTypeBinding; import org.eclipse.che.ide.ext.java.jdt.internal.compiler.lookup.ProblemFieldBinding; import org.eclipse.che.ide.ext.java.jdt.internal.compiler.lookup.ProblemReasons; import org.eclipse.che.ide.ext.java.jdt.internal.compiler.lookup.ProblemReferenceBinding; import org.eclipse.che.ide.ext.java.jdt.internal.compiler.lookup.ReferenceBinding; import org.eclipse.che.ide.ext.java.jdt.internal.compiler.lookup.Scope; import org.eclipse.che.ide.ext.java.jdt.internal.compiler.lookup.SourceTypeBinding; import org.eclipse.che.ide.ext.java.jdt.internal.compiler.lookup.TagBits; import org.eclipse.che.ide.ext.java.jdt.internal.compiler.lookup.TypeBinding; import org.eclipse.che.ide.ext.java.jdt.internal.compiler.lookup.TypeIds; public class FieldReference extends Reference implements InvocationSite { public static final int READ = 0; public static final int WRITE = 1; public Expression receiver; public char[] token; public FieldBinding binding; // exact binding resulting from lookup public MethodBinding[] syntheticAccessors; // [0]=read accessor [1]=write accessor public long nameSourcePosition; //(start<<32)+end public TypeBinding actualReceiverType; public TypeBinding genericCast; public FieldReference(char[] source, long pos) { this.token = source; this.nameSourcePosition = pos; //by default the position are the one of the field (not true for super access) this.sourceStart = (int)(pos >>> 32); this.sourceEnd = (int)(pos & 0x00000000FFFFFFFFL); this.bits |= Binding.FIELD; } @Override public FlowInfo analyseAssignment(BlockScope currentScope, FlowContext flowContext, FlowInfo flowInfo, Assignment assignment, boolean isCompound) { // compound assignment extra work if (isCompound) { // check the variable part is initialized if blank final if (this.binding.isBlankFinal() && this.receiver.isThis() && currentScope.needBlankFinalFieldInitializationCheck(this.binding)) { FlowInfo fieldInits = flowContext.getInitsForFinalBlankInitializationCheck(this.binding.declaringClass.original(), flowInfo); if (!fieldInits.isDefinitelyAssigned(this.binding)) { currentScope.problemReporter().uninitializedBlankFinalField(this.binding, this); // we could improve error msg here telling "cannot use compound assignment on final blank field" } } manageSyntheticAccessIfNecessary(currentScope, flowInfo, true /*read-access*/); } flowInfo = this.receiver.analyseCode(currentScope, flowContext, flowInfo, !this.binding.isStatic()).unconditionalInits(); if (assignment.expression != null) { flowInfo = assignment.expression.analyseCode(currentScope, flowContext, flowInfo).unconditionalInits(); } manageSyntheticAccessIfNecessary(currentScope, flowInfo, false /*write-access*/); // check if assigning a final field if (this.binding.isFinal()) { // in a context where it can be assigned? if (this.binding.isBlankFinal() && !isCompound && this.receiver.isThis() && !(this.receiver instanceof QualifiedThisReference) && ((this.receiver.bits & ASTNode.ParenthesizedMASK) == 0) // (this).x is forbidden && currentScope.allowBlankFinalFieldAssignment(this.binding)) { if (flowInfo.isPotentiallyAssigned(this.binding)) { currentScope.problemReporter().duplicateInitializationOfBlankFinalField(this.binding, this); } else { flowContext.recordSettingFinal(this.binding, this, flowInfo); } flowInfo.markAsDefinitelyAssigned(this.binding); } else { // assigning a final field outside an initializer or constructor or wrong reference currentScope.problemReporter().cannotAssignToFinalField(this.binding, this); } } // https://bugs.eclipse.org/bugs/show_bug.cgi?id=318682 if (!this.binding.isStatic()) { if (this.receiver.isThis()) { currentScope.resetEnclosingMethodStaticFlag(); } } else if (this.receiver.isThis()) { if ((this.receiver.bits & ASTNode.IsImplicitThis) == 0) { // explicit this, not allowed in static context currentScope.resetEnclosingMethodStaticFlag(); } } return flowInfo; } @Override public FlowInfo analyseCode(BlockScope currentScope, FlowContext flowContext, FlowInfo flowInfo) { return analyseCode(currentScope, flowContext, flowInfo, true); } @Override public FlowInfo analyseCode(BlockScope currentScope, FlowContext flowContext, FlowInfo flowInfo, boolean valueRequired) { boolean nonStatic = !this.binding.isStatic(); this.receiver.analyseCode(currentScope, flowContext, flowInfo, nonStatic); if (nonStatic) { this.receiver.checkNPE(currentScope, flowContext, flowInfo); // https://bugs.eclipse.org/bugs/show_bug.cgi?id=318682 if (this.receiver.isThis()) { currentScope.resetEnclosingMethodStaticFlag(); } } else if (this.receiver.isThis()) { if ((this.receiver.bits & ASTNode.IsImplicitThis) == 0) { // explicit this receiver, not allowed in static context // https://bugs.eclipse.org/bugs/show_bug.cgi?id=318682 currentScope.resetEnclosingMethodStaticFlag(); } } if (valueRequired || currentScope.compilerOptions().complianceLevel >= ClassFileConstants.JDK1_4) { manageSyntheticAccessIfNecessary(currentScope, flowInfo, true /*read-access*/); } return flowInfo; } /** * @see org.eclipse.che.ide.ext.java.jdt.internal.compiler.ast.Expression#computeConversion(org.eclipse.che.ide.ext.java.jdt.internal.compiler.lookup.Scope, * org.eclipse.che.ide.ext.java.jdt.internal.compiler.lookup.TypeBinding, org.eclipse.che.ide.ext.java.jdt.internal.compiler.lookup * .TypeBinding) */ @Override public void computeConversion(Scope scope, TypeBinding runtimeTimeType, TypeBinding compileTimeType) { if (runtimeTimeType == null || compileTimeType == null) { return; } // set the generic cast after the fact, once the type expectation is fully known (no need for strict cast) if (this.binding != null && this.binding.isValidBinding()) { FieldBinding originalBinding = this.binding.original(); TypeBinding originalType = originalBinding.type; // extra cast needed if field type is type variable if (originalType.leafComponentType().isTypeVariable()) { TypeBinding targetType = (!compileTimeType.isBaseType() && runtimeTimeType.isBaseType()) ? compileTimeType // unboxing: checkcast before conversion : runtimeTimeType; this.genericCast = originalBinding.type.genericCast(targetType); if (this.genericCast instanceof ReferenceBinding) { ReferenceBinding referenceCast = (ReferenceBinding)this.genericCast; if (!referenceCast.canBeSeenBy(scope)) { scope.problemReporter().invalidType( this, new ProblemReferenceBinding(CharOperation.splitOn('.', referenceCast.shortReadableName()), referenceCast, ProblemReasons.NotVisible)); } } } } super.computeConversion(scope, runtimeTimeType, compileTimeType); } @Override public FieldBinding fieldBinding() { return this.binding; } @Override public void generateAssignment(BlockScope currentScope, Assignment assignment, boolean valueRequired) { FieldBinding codegenBinding = this.binding.original(); this.receiver.generateCode(currentScope, !codegenBinding.isStatic()); assignment.expression.generateCode(currentScope, true); fieldStore(currentScope, codegenBinding, this.syntheticAccessors == null ? null : this.syntheticAccessors[FieldReference.WRITE], this.actualReceiverType, this.receiver.isImplicitThis(), valueRequired); } /** * Field reference code generation * * @param currentScope * org.eclipse.che.ide.java.client.internal.compiler.lookup.BlockScope * @param codeStream * org.eclipse.che.ide.java.client.internal.compiler.codegen.CodeStream * @param valueRequired * boolean */ @Override public void generateCode(BlockScope currentScope, boolean valueRequired) { if (this.constant != Constant.NotAConstant) { return; } FieldBinding codegenBinding = this.binding.original(); boolean isStatic = codegenBinding.isStatic(); boolean isThisReceiver = this.receiver instanceof ThisReference; Constant fieldConstant = codegenBinding.constant(); if (fieldConstant != Constant.NotAConstant) { return; } if (valueRequired || (!isThisReceiver && currentScope.compilerOptions().complianceLevel >= ClassFileConstants.JDK1_4) || ((this.implicitConversion & TypeIds.UNBOXING) != 0) || (this.genericCast != null)) { this.receiver.generateCode(currentScope, !isStatic); } } @Override public void generateCompoundAssignment(BlockScope currentScope, Expression expression, int operator, int assignmentImplicitConversion, boolean valueRequired) { // check if compound assignment is the only usage of a private field reportOnlyUselesslyReadPrivateField(currentScope, this.binding, valueRequired); FieldBinding codegenBinding = this.binding.original(); this.receiver.generateCode(currentScope, !codegenBinding.isStatic()); switch (this.implicitConversion & TypeIds.IMPLICIT_CONVERSION_MASK >> 4) { case T_JavaLangString: case T_JavaLangObject: case T_undefined: break; default: // generate the increment value (will by itself be promoted to the operation value) if (expression != IntLiteral.One) { // prefix operation expression.generateCode(currentScope, true); } } fieldStore(currentScope, codegenBinding, this.syntheticAccessors == null ? null : this.syntheticAccessors[FieldReference.WRITE], this.actualReceiverType, this.receiver.isImplicitThis(), valueRequired); // no need for generic cast as value got dupped } @Override public void generatePostIncrement(BlockScope currentScope, CompoundAssignment postIncrement, boolean valueRequired) { // check if postIncrement is the only usage of a private field reportOnlyUselesslyReadPrivateField(currentScope, this.binding, valueRequired); FieldBinding codegenBinding = this.binding.original(); this.receiver.generateCode(currentScope, !codegenBinding.isStatic()); fieldStore(currentScope, codegenBinding, this.syntheticAccessors == null ? null : this.syntheticAccessors[FieldReference.WRITE], this.actualReceiverType, this.receiver.isImplicitThis(), false); } /** @see org.eclipse.che.ide.ext.java.jdt.internal.compiler.lookup.InvocationSite#genericTypeArguments() */ @Override public TypeBinding[] genericTypeArguments() { return null; } @Override public boolean isSuperAccess() { return this.receiver.isSuper(); } @Override public boolean isTypeAccess() { return this.receiver != null && this.receiver.isTypeReference(); } /* * No need to emulate access to protected fields since not implicitly accessed */ public void manageSyntheticAccessIfNecessary(BlockScope currentScope, FlowInfo flowInfo, boolean isReadAccess) { if ((flowInfo.tagBits & FlowInfo.UNREACHABLE_OR_DEAD) != 0) { return; } // if field from parameterized type got found, use the original field at codegen time FieldBinding codegenBinding = this.binding.original(); if (this.binding.isPrivate()) { if ((currentScope.enclosingSourceType() != codegenBinding.declaringClass) && this.binding.constant() == Constant.NotAConstant) { if (this.syntheticAccessors == null) { this.syntheticAccessors = new MethodBinding[2]; } this.syntheticAccessors[isReadAccess ? FieldReference.READ : FieldReference.WRITE] = ((SourceTypeBinding)codegenBinding.declaringClass).addSyntheticMethod(codegenBinding, isReadAccess, false /* not super ref in remote type*/); currentScope.problemReporter().needToEmulateFieldAccess(codegenBinding, this, isReadAccess); return; } } else if (this.receiver instanceof QualifiedSuperReference) { // qualified super // qualified super need emulation always SourceTypeBinding destinationType = (SourceTypeBinding)(((QualifiedSuperReference)this.receiver).currentCompatibleType); if (this.syntheticAccessors == null) { this.syntheticAccessors = new MethodBinding[2]; } this.syntheticAccessors[isReadAccess ? FieldReference.READ : FieldReference.WRITE] = destinationType.addSyntheticMethod(codegenBinding, isReadAccess, isSuperAccess()); currentScope.problemReporter().needToEmulateFieldAccess(codegenBinding, this, isReadAccess); return; } else if (this.binding.isProtected()) { SourceTypeBinding enclosingSourceType; if (((this.bits & ASTNode.DepthMASK) != 0) && this.binding.declaringClass.getPackage() != (enclosingSourceType = currentScope.enclosingSourceType()) .getPackage()) { SourceTypeBinding currentCompatibleType = (SourceTypeBinding)enclosingSourceType .enclosingTypeAt((this.bits & ASTNode.DepthMASK) >> ASTNode.DepthSHIFT); if (this.syntheticAccessors == null) { this.syntheticAccessors = new MethodBinding[2]; } this.syntheticAccessors[isReadAccess ? FieldReference.READ : FieldReference.WRITE] = currentCompatibleType.addSyntheticMethod(codegenBinding, isReadAccess, isSuperAccess()); currentScope.problemReporter().needToEmulateFieldAccess(codegenBinding, this, isReadAccess); return; } } } @Override public int nullStatus(FlowInfo flowInfo) { return FlowInfo.UNKNOWN; } @Override public Constant optimizedBooleanConstant() { switch (this.resolvedType.id) { case T_boolean: case T_JavaLangBoolean: return this.constant != Constant.NotAConstant ? this.constant : this.binding.constant(); default: return Constant.NotAConstant; } } /** @see org.eclipse.che.ide.ext.java.jdt.internal.compiler.ast.Expression#postConversionType(Scope) */ @Override public TypeBinding postConversionType(Scope scope) { TypeBinding convertedType = this.resolvedType; if (this.genericCast != null) { convertedType = this.genericCast; } int runtimeType = (this.implicitConversion & TypeIds.IMPLICIT_CONVERSION_MASK) >> 4; switch (runtimeType) { case T_boolean: convertedType = TypeBinding.BOOLEAN; break; case T_byte: convertedType = TypeBinding.BYTE; break; case T_short: convertedType = TypeBinding.SHORT; break; case T_char: convertedType = TypeBinding.CHAR; break; case T_int: convertedType = TypeBinding.INT; break; case T_float: convertedType = TypeBinding.FLOAT; break; case T_long: convertedType = TypeBinding.LONG; break; case T_double: convertedType = TypeBinding.DOUBLE; break; default: } if ((this.implicitConversion & TypeIds.BOXING) != 0) { convertedType = scope.environment().computeBoxingType(convertedType); } return convertedType; } @Override public StringBuffer printExpression(int indent, StringBuffer output) { return this.receiver.printExpression(0, output).append('.').append(this.token); } @Override public TypeBinding resolveType(BlockScope scope) { // Answer the signature type of the field. // constants are propaged when the field is final // and initialized with a (compile time) constant //always ignore receiver cast, since may affect constant pool reference boolean receiverCast = false; if (this.receiver instanceof CastExpression) { this.receiver.bits |= ASTNode.DisableUnnecessaryCastCheck; // will check later on receiverCast = true; } this.actualReceiverType = this.receiver.resolveType(scope); if (this.actualReceiverType == null) { this.constant = Constant.NotAConstant; return null; } if (receiverCast) { // due to change of declaring class with receiver type, only identity cast should be notified if (((CastExpression)this.receiver).expression.resolvedType == this.actualReceiverType) { scope.problemReporter().unnecessaryCast((CastExpression)this.receiver); } } // the case receiverType.isArrayType and token = 'length' is handled by the scope API FieldBinding fieldBinding = this.binding = scope.getField(this.actualReceiverType, this.token, this); if (!fieldBinding.isValidBinding()) { this.constant = Constant.NotAConstant; if (this.receiver.resolvedType instanceof ProblemReferenceBinding) { // problem already got signaled on receiver, do not report secondary problem return null; } // https://bugs.eclipse.org/bugs/show_bug.cgi?id=245007 avoid secondary errors in case of // missing super type for anonymous classes ... ReferenceBinding declaringClass = fieldBinding.declaringClass; boolean avoidSecondary = declaringClass != null && declaringClass.isAnonymousType() && declaringClass.superclass() instanceof MissingTypeBinding; if (!avoidSecondary) { scope.problemReporter().invalidField(this, this.actualReceiverType); } if (fieldBinding instanceof ProblemFieldBinding) { ProblemFieldBinding problemFieldBinding = (ProblemFieldBinding)fieldBinding; FieldBinding closestMatch = problemFieldBinding.closestMatch; switch (problemFieldBinding.problemId()) { case ProblemReasons.InheritedNameHidesEnclosingName: case ProblemReasons.NotVisible: case ProblemReasons.NonStaticReferenceInConstructorInvocation: case ProblemReasons.NonStaticReferenceInStaticContext: if (closestMatch != null) { fieldBinding = closestMatch; } } } if (!fieldBinding.isValidBinding()) { return null; } } // handle indirect inheritance thru variable secondary bound // receiver may receive generic cast, as part of implicit conversion TypeBinding oldReceiverType = this.actualReceiverType; this.actualReceiverType = this.actualReceiverType.getErasureCompatibleType(fieldBinding.declaringClass); this.receiver.computeConversion(scope, this.actualReceiverType, this.actualReceiverType); if (this.actualReceiverType != oldReceiverType && this.receiver.postConversionType(scope) != this.actualReceiverType) { // record need for explicit cast at codegen since receiver could not handle it this.bits |= NeedReceiverGenericCast; } if (isFieldUseDeprecated(fieldBinding, scope, this.bits)) { scope.problemReporter().deprecatedField(fieldBinding, this); } boolean isImplicitThisRcv = this.receiver.isImplicitThis(); this.constant = isImplicitThisRcv ? fieldBinding.constant() : Constant.NotAConstant; if (fieldBinding.isStatic()) { // static field accessed through receiver? legal but unoptimal (optional warning) if (!(isImplicitThisRcv || (this.receiver instanceof NameReference && (((NameReference)this.receiver).bits & Binding.TYPE) != 0))) { scope.problemReporter().nonStaticAccessToStaticField(this, fieldBinding); } ReferenceBinding declaringClass = this.binding.declaringClass; if (!isImplicitThisRcv && declaringClass != this.actualReceiverType && declaringClass.canBeSeenBy(scope)) { scope.problemReporter().indirectAccessToStaticField(this, fieldBinding); } // check if accessing enum static field in initializer if (declaringClass.isEnum()) { MethodScope methodScope = scope.methodScope(); SourceTypeBinding sourceType = scope.enclosingSourceType(); if (this.constant == Constant.NotAConstant && !methodScope.isStatic && (sourceType == declaringClass || sourceType.superclass == declaringClass) // enum constant body && methodScope.isInsideInitializerOrConstructor()) { scope.problemReporter().enumStaticFieldUsedDuringInitialization(this.binding, this); } } } TypeBinding fieldType = fieldBinding.type; if (fieldType != null) { if ((this.bits & ASTNode.IsStrictlyAssigned) == 0) { fieldType = fieldType.capture(scope, this.sourceEnd); // perform capture conversion if read access } this.resolvedType = fieldType; if ((fieldType.tagBits & TagBits.HasMissingType) != 0) { scope.problemReporter().invalidType(this, fieldType); return null; } } return fieldType; } @Override public void setActualReceiverType(ReferenceBinding receiverType) { this.actualReceiverType = receiverType; } @Override public void setDepth(int depth) { this.bits &= ~ASTNode.DepthMASK; // flush previous depth if any if (depth > 0) { this.bits |= (depth & 0xFF) << ASTNode.DepthSHIFT; // encoded on 8 bits } } @Override public void setFieldIndex(int index) { // ignored } @Override public void traverse(ASTVisitor visitor, BlockScope scope) { if (visitor.visit(this, scope)) { this.receiver.traverse(visitor, scope); } visitor.endVisit(this, scope); } }