| /******************************************************************************* |
| * Copyright (c) 2000, 2018 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 185682 - Increment/decrement operators mark local variables as read |
| * bug 331649 - [compiler][null] consider null annotations for fields |
| * bug 383368 - [compiler][null] syntactic null analysis for field references |
| * Bug 412203 - [compiler] Internal compiler error: java.lang.IllegalArgumentException: info cannot be null |
| * Bug 400874 - [1.8][compiler] Inference infrastructure should evolve to meet JLS8 18.x (Part G of JSR335 spec) |
| * Bug 458396 - NPE in CodeStream.invoke() |
| * Jesper S Moller - Contributions for |
| * Bug 378674 - "The method can be declared as static" is wrong |
| * Robert Roth <robert.roth.off@gmail.com> - Contributions for |
| * Bug 361039 - NPE in FieldReference.optimizedBooleanConstant |
| *******************************************************************************/ |
| package org.eclipse.jdt.internal.compiler.ast; |
| |
| import org.eclipse.jdt.core.compiler.CharOperation; |
| import org.eclipse.jdt.internal.compiler.ASTVisitor; |
| import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; |
| import org.eclipse.jdt.internal.compiler.codegen.CodeStream; |
| import org.eclipse.jdt.internal.compiler.codegen.Opcodes; |
| import org.eclipse.jdt.internal.compiler.flow.FlowContext; |
| import org.eclipse.jdt.internal.compiler.flow.FlowInfo; |
| import org.eclipse.jdt.internal.compiler.impl.Constant; |
| import org.eclipse.jdt.internal.compiler.lookup.ArrayBinding; |
| import org.eclipse.jdt.internal.compiler.lookup.Binding; |
| import org.eclipse.jdt.internal.compiler.lookup.BlockScope; |
| import org.eclipse.jdt.internal.compiler.lookup.FieldBinding; |
| import org.eclipse.jdt.internal.compiler.lookup.InferenceContext18; |
| import org.eclipse.jdt.internal.compiler.lookup.InvocationSite; |
| import org.eclipse.jdt.internal.compiler.lookup.LocalVariableBinding; |
| import org.eclipse.jdt.internal.compiler.lookup.MethodBinding; |
| import org.eclipse.jdt.internal.compiler.lookup.MethodScope; |
| import org.eclipse.jdt.internal.compiler.lookup.MissingTypeBinding; |
| import org.eclipse.jdt.internal.compiler.lookup.ProblemFieldBinding; |
| import org.eclipse.jdt.internal.compiler.lookup.ProblemReasons; |
| import org.eclipse.jdt.internal.compiler.lookup.ProblemReferenceBinding; |
| import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding; |
| import org.eclipse.jdt.internal.compiler.lookup.Scope; |
| import org.eclipse.jdt.internal.compiler.lookup.SourceTypeBinding; |
| import org.eclipse.jdt.internal.compiler.lookup.TagBits; |
| import org.eclipse.jdt.internal.compiler.lookup.TypeBinding; |
| import org.eclipse.jdt.internal.compiler.lookup.TypeConstants; |
| import org.eclipse.jdt.internal.compiler.lookup.TypeIds; |
| import org.eclipse.jdt.internal.compiler.lookup.VariableBinding; |
| import org.eclipse.objectteams.otdt.internal.core.compiler.ast.CalloutMappingDeclaration; |
| import org.eclipse.objectteams.otdt.internal.core.compiler.ast.FieldAccessSpec; |
| import org.eclipse.objectteams.otdt.internal.core.compiler.lookup.RoleTypeBinding; |
| import org.eclipse.objectteams.otdt.internal.core.compiler.lookup.SyntheticOTTargetMethod; |
| import org.eclipse.objectteams.otdt.internal.core.compiler.mappings.CalloutImplementor; |
| import org.eclipse.objectteams.otdt.internal.core.compiler.model.FieldModel; |
| import org.eclipse.objectteams.otdt.internal.core.compiler.util.RoleTypeCreator; |
| |
| /** |
| * OTDT changes: |
| * |
| * What: Trigger CollectedReplacementsTransformer |
| * Why: Might need to insert a cast to the role types class version. |
| * |
| * What: Unwrap receiver type in resolve if it's a role type. |
| * Why: Role type interface does not have fields, only class has. |
| * |
| * What: wrap resolvedType if it's a role. |
| * |
| * What: Create synth. accessors if a role accesses a field of its enclosing team. |
| * Why: The code containing the access might be copy-inherited to a different |
| * package which would break access restriction at run-time. |
| * |
| * What: baseclass decapsulation for receiver. |
| * |
| * @version $Id: FieldReference.java 23405 2010-02-03 17:02:18Z stephan $ |
| */ |
| public class FieldReference extends Reference implements InvocationSite { |
| |
| |
| //{ObjectTeams: for baseclass decapsulation (implement interface from Expression): |
| private DecapsulationState baseclassDecapsulation = DecapsulationState.NONE; |
| public void setBaseclassDecapsulation(DecapsulationState state) { |
| this.baseclassDecapsulation = state; |
| } |
| @Override |
| public DecapsulationState getBaseclassDecapsulation() { |
| return this.baseclassDecapsulation; |
| } |
| @Override |
| public void tagReportedBaseclassDecapsulation() { |
| setBaseclassDecapsulation(DecapsulationState.REPORTED); |
| } |
| // SH} |
| |
| 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(); |
| |
| this.receiver.checkNPE(currentScope, flowContext, flowInfo); |
| |
| 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); |
| } |
| } else if (this.binding.isNonNull() || this.binding.type.isTypeVariable()) { |
| // in a context where it can be assigned? |
| if ( !isCompound |
| && this.receiver.isThis() |
| && !(this.receiver instanceof QualifiedThisReference) |
| && TypeBinding.equalsEquals(this.receiver.resolvedType, this.binding.declaringClass) // inherited fields are not tracked here |
| && ((this.receiver.bits & ASTNode.ParenthesizedMASK) == 0)) { // (this).x is forbidden |
| flowInfo.markAsDefinitelyAssigned(this.binding); |
| } |
| } |
| 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, 1); |
| } |
| |
| if (valueRequired || currentScope.compilerOptions().complianceLevel >= ClassFileConstants.JDK1_4) { |
| manageSyntheticAccessIfNecessary(currentScope, flowInfo, true /*read-access*/); |
| } |
| if (currentScope.compilerOptions().complianceLevel >= ClassFileConstants.JDK1_7) { |
| FieldBinding fieldBinding = this.binding; |
| if (this.receiver.isThis() && fieldBinding.isBlankFinal() && currentScope.needBlankFinalFieldInitializationCheck(fieldBinding)) { |
| FlowInfo fieldInits = flowContext.getInitsForFinalBlankInitializationCheck(fieldBinding.declaringClass.original(), flowInfo); |
| if (!fieldInits.isDefinitelyAssigned(fieldBinding)) { |
| currentScope.problemReporter().uninitializedBlankFinalField(fieldBinding, this); |
| } |
| } |
| } |
| return flowInfo; |
| } |
| |
| @Override |
| public boolean checkNPE(BlockScope scope, FlowContext flowContext, FlowInfo flowInfo, int ttlForFieldCheck) { |
| if (flowContext.isNullcheckedFieldAccess(this)) { |
| return true; // enough seen |
| } |
| return checkNullableFieldDereference(scope, this.binding, this.nameSourcePosition, flowContext, ttlForFieldCheck); |
| } |
| |
| /** |
| * @see org.eclipse.jdt.internal.compiler.ast.Expression#computeConversion(org.eclipse.jdt.internal.compiler.lookup.Scope, org.eclipse.jdt.internal.compiler.lookup.TypeBinding, org.eclipse.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, CodeStream codeStream, Assignment assignment, boolean valueRequired) { |
| int pc = codeStream.position; |
| FieldBinding codegenBinding = this.binding.original(); |
| //{ObjectTeams: inferred callout-to-static-field?? |
| if (!checkGeneratedSynthArgsForFieldAccess(this.syntheticAccessors, codeStream, currentScope)) |
| // if nothing generated, generate regular receiver code: |
| // SH} |
| this.receiver.generateCode(currentScope, codeStream, !codegenBinding.isStatic()); |
| codeStream.recordPositionsFrom(pc, this.sourceStart); |
| assignment.expression.generateCode(currentScope, codeStream, true); |
| fieldStore(currentScope, codeStream, codegenBinding, this.syntheticAccessors == null ? null : this.syntheticAccessors[FieldReference.WRITE], this.actualReceiverType, this.receiver.isImplicitThis(), valueRequired); |
| if (valueRequired) { |
| codeStream.generateImplicitConversion(assignment.implicitConversion); |
| } |
| // no need for generic cast as value got dupped |
| } |
| |
| /** |
| * Field reference code generation |
| * |
| * @param currentScope org.eclipse.jdt.internal.compiler.lookup.BlockScope |
| * @param codeStream org.eclipse.jdt.internal.compiler.codegen.CodeStream |
| * @param valueRequired boolean |
| */ |
| @Override |
| public void generateCode(BlockScope currentScope, CodeStream codeStream, boolean valueRequired) { |
| int pc = codeStream.position; |
| if (this.constant != Constant.NotAConstant) { |
| if (valueRequired) { |
| codeStream.generateConstant(this.constant, this.implicitConversion); |
| } |
| codeStream.recordPositionsFrom(pc, this.sourceStart); |
| return; |
| } |
| FieldBinding codegenBinding = this.binding.original(); |
| boolean isStatic = codegenBinding.isStatic(); |
| boolean isThisReceiver = this.receiver instanceof ThisReference; |
| Constant fieldConstant = codegenBinding.constant(); |
| if (fieldConstant != Constant.NotAConstant) { |
| if (!isThisReceiver) { |
| this.receiver.generateCode(currentScope, codeStream, !isStatic); |
| if (!isStatic){ |
| codeStream.invokeObjectGetClass(); |
| codeStream.pop(); |
| } |
| } |
| if (valueRequired) { |
| codeStream.generateConstant(fieldConstant, this.implicitConversion); |
| } |
| codeStream.recordPositionsFrom(pc, this.sourceStart); |
| return; |
| } |
| if (valueRequired |
| || (!isThisReceiver && currentScope.compilerOptions().complianceLevel >= ClassFileConstants.JDK1_4) |
| || ((this.implicitConversion & TypeIds.UNBOXING) != 0) |
| || (this.genericCast != null)) { |
| this.receiver.generateCode(currentScope, codeStream, !isStatic); |
| if ((this.bits & NeedReceiverGenericCast) != 0) { |
| codeStream.checkcast(this.actualReceiverType); |
| } |
| pc = codeStream.position; |
| if (codegenBinding.declaringClass == null) { // array length |
| codeStream.arraylength(); |
| if (valueRequired) { |
| codeStream.generateImplicitConversion(this.implicitConversion); |
| } else { |
| // could occur if !valueRequired but compliance >= 1.4 |
| codeStream.pop(); |
| } |
| } else { |
| if (this.syntheticAccessors == null || this.syntheticAccessors[FieldReference.READ] == null) { |
| TypeBinding constantPoolDeclaringClass = CodeStream.getConstantPoolDeclaringClass(currentScope, codegenBinding, this.actualReceiverType, this.receiver.isImplicitThis()); |
| if (isStatic) { |
| codeStream.fieldAccess(Opcodes.OPC_getstatic, codegenBinding, constantPoolDeclaringClass); |
| } else { |
| codeStream.fieldAccess(Opcodes.OPC_getfield, codegenBinding, constantPoolDeclaringClass); |
| } |
| } else { |
| codeStream.invoke(Opcodes.OPC_invokestatic, this.syntheticAccessors[FieldReference.READ], null /* default declaringClass */); |
| } |
| // required cast must occur even if no value is required |
| if (this.genericCast != null) codeStream.checkcast(this.genericCast); |
| if (valueRequired) { |
| codeStream.generateImplicitConversion(this.implicitConversion); |
| } else { |
| boolean isUnboxing = (this.implicitConversion & TypeIds.UNBOXING) != 0; |
| // conversion only generated if unboxing |
| if (isUnboxing) codeStream.generateImplicitConversion(this.implicitConversion); |
| switch (isUnboxing ? postConversionType(currentScope).id : codegenBinding.type.id) { |
| case T_long : |
| case T_double : |
| codeStream.pop2(); |
| break; |
| default : |
| codeStream.pop(); |
| } |
| } |
| } |
| } else { |
| if (isThisReceiver) { |
| if (isStatic){ |
| // if no valueRequired, still need possible side-effects of <clinit> invocation, if field belongs to different class |
| if (TypeBinding.notEquals(this.binding.original().declaringClass, this.actualReceiverType.erasure())) { |
| MethodBinding accessor = this.syntheticAccessors == null ? null : this.syntheticAccessors[FieldReference.READ]; |
| if (accessor == null) { |
| TypeBinding constantPoolDeclaringClass = CodeStream.getConstantPoolDeclaringClass(currentScope, codegenBinding, this.actualReceiverType, this.receiver.isImplicitThis()); |
| codeStream.fieldAccess(Opcodes.OPC_getstatic, codegenBinding, constantPoolDeclaringClass); |
| } else { |
| codeStream.invoke(Opcodes.OPC_invokestatic, accessor, null /* default declaringClass */); |
| } |
| switch (codegenBinding.type.id) { |
| case T_long : |
| case T_double : |
| codeStream.pop2(); |
| break; |
| default : |
| codeStream.pop(); |
| } |
| } |
| } |
| } else { |
| this.receiver.generateCode(currentScope, codeStream, !isStatic); |
| if (!isStatic){ |
| codeStream.invokeObjectGetClass(); // perform null check |
| codeStream.pop(); |
| } |
| } |
| } |
| codeStream.recordPositionsFrom(pc, this.sourceEnd); |
| } |
| |
| @Override |
| public void generateCompoundAssignment(BlockScope currentScope, CodeStream codeStream, Expression expression, int operator, int assignmentImplicitConversion, boolean valueRequired) { |
| boolean isStatic; |
| // 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, codeStream, !(isStatic = codegenBinding.isStatic())); |
| if (isStatic) { |
| if (this.syntheticAccessors == null || this.syntheticAccessors[FieldReference.READ] == null) { |
| TypeBinding constantPoolDeclaringClass = CodeStream.getConstantPoolDeclaringClass(currentScope, codegenBinding, this.actualReceiverType, this.receiver.isImplicitThis()); |
| codeStream.fieldAccess(Opcodes.OPC_getstatic, codegenBinding, constantPoolDeclaringClass); |
| } else { |
| codeStream.invoke(Opcodes.OPC_invokestatic, this.syntheticAccessors[FieldReference.READ], null /* default declaringClass */); |
| } |
| } else { |
| codeStream.dup(); |
| if (this.syntheticAccessors == null || this.syntheticAccessors[FieldReference.READ] == null) { |
| TypeBinding constantPoolDeclaringClass = CodeStream.getConstantPoolDeclaringClass(currentScope, codegenBinding, this.actualReceiverType, this.receiver.isImplicitThis()); |
| codeStream.fieldAccess(Opcodes.OPC_getfield, codegenBinding, constantPoolDeclaringClass); |
| } else { |
| codeStream.invoke(Opcodes.OPC_invokestatic, this.syntheticAccessors[FieldReference.READ], null /* default declaringClass */); |
| } |
| } |
| int operationTypeID; |
| switch(operationTypeID = (this.implicitConversion & TypeIds.IMPLICIT_CONVERSION_MASK) >> 4) { |
| case T_JavaLangString : |
| case T_JavaLangObject : |
| case T_undefined : |
| codeStream.generateStringConcatenationAppend(currentScope, null, expression); |
| break; |
| default : |
| if (this.genericCast != null) |
| codeStream.checkcast(this.genericCast); |
| // promote the array reference to the suitable operation type |
| codeStream.generateImplicitConversion(this.implicitConversion); |
| // generate the increment value (will by itself be promoted to the operation value) |
| if (expression == IntLiteral.One) { // prefix operation |
| codeStream.generateConstant(expression.constant, this.implicitConversion); |
| } else { |
| expression.generateCode(currentScope, codeStream, true); |
| } |
| // perform the operation |
| codeStream.sendOperator(operator, operationTypeID); |
| // cast the value back to the array reference type |
| codeStream.generateImplicitConversion(assignmentImplicitConversion); |
| } |
| fieldStore(currentScope, codeStream, 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, CodeStream codeStream, CompoundAssignment postIncrement, boolean valueRequired) { |
| boolean isStatic; |
| // 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, codeStream, !(isStatic = codegenBinding.isStatic())); |
| if (isStatic) { |
| if (this.syntheticAccessors == null || this.syntheticAccessors[FieldReference.READ] == null) { |
| TypeBinding constantPoolDeclaringClass = CodeStream.getConstantPoolDeclaringClass(currentScope, codegenBinding, this.actualReceiverType, this.receiver.isImplicitThis()); |
| codeStream.fieldAccess(Opcodes.OPC_getstatic, codegenBinding, constantPoolDeclaringClass); |
| } else { |
| codeStream.invoke(Opcodes.OPC_invokestatic, this.syntheticAccessors[FieldReference.READ], null /* default declaringClass */); |
| } |
| } else { |
| codeStream.dup(); |
| if (this.syntheticAccessors == null || this.syntheticAccessors[FieldReference.READ] == null) { |
| TypeBinding constantPoolDeclaringClass = CodeStream.getConstantPoolDeclaringClass(currentScope, codegenBinding, this.actualReceiverType, this.receiver.isImplicitThis()); |
| codeStream.fieldAccess(Opcodes.OPC_getfield, codegenBinding, constantPoolDeclaringClass); |
| } else { |
| codeStream.invoke(Opcodes.OPC_invokestatic, this.syntheticAccessors[FieldReference.READ], null /* default declaringClass */); |
| } |
| } |
| TypeBinding operandType; |
| if (this.genericCast != null) { |
| codeStream.checkcast(this.genericCast); |
| operandType = this.genericCast; |
| } else { |
| operandType = codegenBinding.type; |
| } |
| if (valueRequired) { |
| if (isStatic) { |
| switch (operandType.id) { |
| case TypeIds.T_long : |
| case TypeIds.T_double : |
| codeStream.dup2(); |
| break; |
| default : |
| codeStream.dup(); |
| break; |
| } |
| } else { // Stack: [owner][old field value] ---> [old field value][owner][old field value] |
| switch (operandType.id) { |
| case TypeIds.T_long : |
| case TypeIds.T_double : |
| codeStream.dup2_x1(); |
| break; |
| default : |
| codeStream.dup_x1(); |
| break; |
| } |
| } |
| } |
| codeStream.generateImplicitConversion(this.implicitConversion); |
| codeStream.generateConstant( |
| postIncrement.expression.constant, |
| this.implicitConversion); |
| codeStream.sendOperator(postIncrement.operator, this.implicitConversion & TypeIds.COMPILE_TYPE_MASK); |
| codeStream.generateImplicitConversion( |
| postIncrement.preAssignImplicitConversion); |
| fieldStore(currentScope, codeStream, codegenBinding, this.syntheticAccessors == null ? null : this.syntheticAccessors[FieldReference.WRITE], this.actualReceiverType, this.receiver.isImplicitThis(), false); |
| } |
| |
| /** |
| * @see org.eclipse.jdt.internal.compiler.lookup.InvocationSite#genericTypeArguments() |
| */ |
| @Override |
| public TypeBinding[] genericTypeArguments() { |
| return null; |
| } |
| |
| @Override |
| public InferenceContext18 freshInferenceContext(Scope scope) { |
| return null; |
| } |
| |
| @Override |
| public boolean isEquivalent(Reference reference) { |
| // only consider field references relative to "this": |
| if (this.receiver.isThis() && !(this.receiver instanceof QualifiedThisReference)) { |
| // current is a simple "this.f1" |
| char[] otherToken = null; |
| // matching 'reference' could be "f1" or "this.f1": |
| if (reference instanceof SingleNameReference) { |
| otherToken = ((SingleNameReference) reference).token; |
| } else if (reference instanceof FieldReference) { |
| FieldReference fr = (FieldReference) reference; |
| if (fr.receiver.isThis() && !(fr.receiver instanceof QualifiedThisReference)) { |
| otherToken = fr.token; |
| } |
| } |
| return otherToken != null && CharOperation.equals(this.token, otherToken); |
| } else { |
| // search deeper for "this" inside: |
| char[][] thisTokens = getThisFieldTokens(1); |
| if (thisTokens == null) { |
| return false; |
| } |
| // other can be "this.f1.f2", too, or "f1.f2": |
| char[][] otherTokens = null; |
| if (reference instanceof FieldReference) { |
| otherTokens = ((FieldReference) reference).getThisFieldTokens(1); |
| } else if (reference instanceof QualifiedNameReference) { |
| if (((QualifiedNameReference)reference).binding instanceof LocalVariableBinding) |
| return false; // initial variable mismatch: local (from f1.f2) vs. field (from this.f1.f2) |
| otherTokens = ((QualifiedNameReference) reference).tokens; |
| } |
| return CharOperation.equals(thisTokens, otherTokens); |
| } |
| } |
| |
| private char[][] getThisFieldTokens(int nestingCount) { |
| char[][] result = null; |
| if (this.receiver.isThis() && ! (this.receiver instanceof QualifiedThisReference)) { |
| // found an inner-most this-reference, start building the token array: |
| result = new char[nestingCount][]; |
| // fill it front to tail while traveling back out: |
| result[0] = this.token; |
| } else if (this.receiver instanceof FieldReference) { |
| result = ((FieldReference)this.receiver).getThisFieldTokens(nestingCount+1); |
| if (result != null) { |
| // front to tail: outermost is last: |
| result[result.length-nestingCount] = this.token; |
| } |
| } |
| return result; |
| } |
| |
| @Override |
| public boolean isSuperAccess() { |
| return this.receiver.isSuper(); |
| } |
| |
| @Override |
| public boolean isQualifiedSuper() { |
| return this.receiver.isQualifiedSuper(); |
| } |
| |
| @Override |
| public boolean isTypeAccess() { |
| return this.receiver != null && this.receiver.isTypeReference(); |
| } |
| |
| @Override |
| public FieldBinding lastFieldBinding() { |
| return this.binding; |
| } |
| |
| /* |
| * 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; |
| |
| //{ObjectTeams: don't create (more) synthetics for base field accessed via callout: |
| if ( FieldModel.isCalloutAccessed(this.binding) |
| && this.syntheticAccessors != null |
| && this.syntheticAccessors[isReadAccess ? FieldReference.READ : FieldReference.WRITE] != null) |
| { |
| return; // synthetic binding already created during resolveType; avoid CCE if declaringClass is binary. |
| } |
| // SH} |
| // if field from parameterized type got found, use the original field at codegen time |
| FieldBinding codegenBinding = this.binding.original(); |
| if (this.binding.isPrivate()) { |
| if ((TypeBinding.notEquals(currentScope.enclosingSourceType(), codegenBinding.declaringClass)) |
| && this.binding.constant(currentScope) == 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*/, |
| //{ObjectTeams: new arg: |
| RoleTypeBinding.isRoleWithExplicitAnchor(this.actualReceiverType)); |
| // SH} |
| 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(), |
| //{ObjectTeams: new arg: |
| RoleTypeBinding.isRoleWithExplicitAnchor(this.actualReceiverType)); |
| // SH} |
| currentScope.problemReporter().needToEmulateFieldAccess(codegenBinding, this, isReadAccess); |
| return; |
| |
| //{ObjectTeams: references to the enclosing team and role other than current need accessor, too: |
| /* orig: |
| } 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); |
| */ |
| } else if ( this.binding.isProtected() |
| || this.binding.isDefault() |
| || isRemoteRoleFieldAccess()) |
| { |
| SourceTypeBinding enclosingSourceType = currentScope.enclosingSourceType(); |
| int depth = getDepthForSynthFieldAccess(this.binding, enclosingSourceType); |
| if (depth > 0 || isRemoteRoleFieldAccess()) // TODO(SH): optimize: avoid accessor if role-to-role access? |
| { |
| SourceTypeBinding currentCompatibleType = |
| (SourceTypeBinding)enclosingSourceType.enclosingTypeAt(depth); |
| |
| // orig: |
| if (this.syntheticAccessors == null) |
| this.syntheticAccessors = new MethodBinding[2]; |
| this.syntheticAccessors[isReadAccess ? FieldReference.READ : FieldReference.WRITE] = currentCompatibleType.addSyntheticMethod(codegenBinding, isReadAccess, isSuperAccess(), |
| //{ObjectTeams: new arg: |
| RoleTypeBinding.isRoleWithExplicitAnchor(this.actualReceiverType)); |
| // SH} |
| currentScope.problemReporter().needToEmulateFieldAccess(codegenBinding, this, isReadAccess); |
| return; |
| } |
| // SH} |
| } |
| } |
| //{ObjectTeams: |
| private boolean isRemoteRoleFieldAccess() { |
| if (this.receiver.isThis() && !(this.receiver instanceof QualifiedThisReference)) |
| return false; |
| if (this.binding.isSynthetic()) |
| return false; |
| return this.binding.declaringClass != null |
| && this.binding.declaringClass.isRole(); |
| } |
| // SH} |
| |
| @Override |
| public Constant optimizedBooleanConstant() { |
| if (this.resolvedType == null) |
| return Constant.NotAConstant; |
| 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.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) { |
| //{ObjectTeams: remove unneeded dot in debug view |
| if (this.receiver.isImplicitThis()) |
| return output.append(this.token); |
| else |
| //MW} |
| 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; |
| } |
| //{ObjectTeams: unwrap type of this reference: |
| if (this.receiver instanceof ThisReference && this.actualReceiverType instanceof RoleTypeBinding) |
| this.actualReceiverType = ((RoleTypeBinding)this.actualReceiverType).getRealClass(); |
| // SH} |
| if (receiverCast) { |
| // due to change of declaring class with receiver type, only identity cast should be notified |
| if (TypeBinding.equalsEquals(((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); |
| //{ObjectTeams: baseclass decapsulation for receiver? |
| if (checkBaseclassDecapsulation(scope)) |
| fieldBinding = this.binding; // modified during check |
| // SH} |
| if (!fieldBinding.isValidBinding()) { |
| this.constant = Constant.NotAConstant; |
| //{ObjectTeams: one more chance: inferred use of callout-to-field: |
| if (fieldBinding.problemId() == ProblemReasons.NotFound) { |
| boolean isSetter = ((this.bits & IsStrictlyAssigned) != 0); |
| CalloutMappingDeclaration callout = CalloutImplementor.inferCalloutAccess(scope, this.receiver, this, this.token, isSetter, this.expectedType); |
| if (callout != null) { |
| if (this.syntheticAccessors == null) |
| this.syntheticAccessors = new MethodBinding[2]; |
| this.syntheticAccessors[isSetter ? FieldReference.WRITE : FieldReference.READ] = |
| new SyntheticOTTargetMethod.CalloutToField(callout.roleMethodSpec.resolvedMethod); |
| fieldBinding = ((FieldAccessSpec)callout.baseMethodSpec).resolvedField; |
| this.binding = fieldBinding; |
| setDepth(fieldBinding.isStatic() ? 1 : 0); // static c-t-f needs to pass the enclosing team |
| if (((FieldAccessSpec)callout.baseMethodSpec).isSetter()) |
| this.resolvedType = callout.roleMethodSpec.resolvedParameters()[0]; |
| else |
| this.resolvedType = callout.roleMethodSpec.resolvedType(); |
| } |
| } |
| if (!fieldBinding.isValidBinding()) { // might have been corrected by callout inference |
| // orig: |
| 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; |
| } |
| // :giro |
| } |
| // SH} |
| } |
| // 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 (TypeBinding.notEquals(this.actualReceiverType, oldReceiverType) && TypeBinding.notEquals(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(scope) : 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 |
| && TypeBinding.notEquals(declaringClass, this.actualReceiverType) |
| && declaringClass.canBeSeenBy(scope)) { |
| scope.problemReporter().indirectAccessToStaticField(this, fieldBinding); |
| } |
| // check if accessing enum static field in initializer |
| if (declaringClass.isEnum() && !scope.isModuleScope()) { |
| MethodScope methodScope = scope.methodScope(); |
| SourceTypeBinding sourceType = scope.enclosingSourceType(); |
| if (this.constant == Constant.NotAConstant |
| && !methodScope.isStatic |
| && (TypeBinding.equalsEquals(sourceType, declaringClass) || TypeBinding.equalsEquals(sourceType.superclass, declaringClass)) // enum constant body |
| && methodScope.isInsideInitializerOrConstructor()) { |
| scope.problemReporter().enumStaticFieldUsedDuringInitialization(this.binding, this); |
| } |
| } |
| } |
| TypeBinding fieldType = fieldBinding.type; |
| if (fieldType != null) { |
| //{ObjectTeams: use pre-computed type for inferred callout-to-field: |
| if (this.resolvedType != null) |
| fieldType = this.resolvedType; |
| // SH} |
| if ((this.bits & ASTNode.IsStrictlyAssigned) == 0) { |
| fieldType = fieldType.capture(scope, this.sourceStart, this.sourceEnd); // perform capture conversion if read access |
| } |
| //{ObjectTeams: also wrap resolvedType: |
| fieldType = RoleTypeCreator.maybeWrapQualifiedRoleType( |
| scope, this.receiver, fieldType, this); |
| // SH} |
| this.resolvedType = fieldType; |
| if ((fieldType.tagBits & TagBits.HasMissingType) != 0) { |
| scope.problemReporter().invalidType(this, fieldType); |
| return null; |
| } |
| } |
| return fieldType; |
| } |
| //{ObjectTeams: baseclass decapsulation for receiver? |
| /** Is baseclass decapsulation allowed for this node? |
| * PRE: this node is resolved, perhaps to a ProblemFieldBinding. |
| * POST: If decapsulation takes place, it has been reported. |
| * @param scope enclosing scope of this node. |
| * @return whether decapsulation actually takes place. |
| */ |
| @Override |
| protected boolean checkBaseclassDecapsulation(Scope scope) { |
| if ( this.binding.problemId() == ProblemReasons.ReceiverTypeNotVisible |
| && this.receiver.getBaseclassDecapsulation().isAllowed()) |
| { |
| if ( this.actualReceiverType != null |
| && !this.actualReceiverType.isCompatibleWith(scope.getJavaLangObject())) |
| return false; // the case of confined roles |
| // special field myArray.length: |
| if (this.actualReceiverType.isArrayType() && CharOperation.equals(this.binding.name, TypeConstants.LENGTH)) |
| this.binding = ArrayBinding.ArrayLength; |
| else |
| this.binding = ((ReferenceBinding)this.actualReceiverType).getField(this.token, true); |
| scope.problemReporter().decapsulation(this.receiver); |
| SourceTypeBinding sourceType = scope.enclosingSourceType(); |
| if (sourceType.isRole()) |
| sourceType.roleModel.markBaseClassDecapsulation((ReferenceBinding)this.resolvedType); |
| return true; |
| } |
| return false; |
| } |
| //SH} |
| @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); |
| } |
| |
| @Override |
| public VariableBinding nullAnnotatedVariableBinding(boolean supportTypeAnnotations) { |
| if (this.binding != null) { |
| if (supportTypeAnnotations |
| || ((this.binding.tagBits & TagBits.AnnotationNullMASK) != 0)) { |
| return this.binding; |
| } |
| } |
| return null; |
| } |
| } |