blob: 4cd80830f277f9450ed81f25189298cb34009b9e [file] [log] [blame]
/*******************************************************************************
* 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.jdt.internal.eval;
import org.eclipse.jdt.internal.compiler.ast.Assignment;
import org.eclipse.jdt.internal.compiler.ast.CompoundAssignment;
import org.eclipse.jdt.internal.compiler.ast.Expression;
import org.eclipse.jdt.internal.compiler.ast.FieldReference;
import org.eclipse.jdt.internal.compiler.ast.IntLiteral;
import org.eclipse.jdt.internal.compiler.codegen.CodeStream;
import org.eclipse.jdt.internal.compiler.codegen.Opcodes;
import org.eclipse.jdt.internal.compiler.flow.FlowInfo;
import org.eclipse.jdt.internal.compiler.impl.Constant;
import org.eclipse.jdt.internal.compiler.lookup.BlockScope;
import org.eclipse.jdt.internal.compiler.lookup.FieldBinding;
import org.eclipse.jdt.internal.compiler.lookup.ProblemFieldBinding;
import org.eclipse.jdt.internal.compiler.lookup.ProblemReasons;
import org.eclipse.jdt.internal.compiler.lookup.TypeBinding;
import org.eclipse.jdt.internal.compiler.lookup.TypeIds;
public class CodeSnippetFieldReference extends FieldReference implements ProblemReasons, EvaluationConstants {
EvaluationContext evaluationContext;
FieldBinding delegateThis;
/**
* CodeSnippetFieldReference constructor comment.
* @param source char[]
* @param pos long
*/
public CodeSnippetFieldReference(char[] source, long pos, EvaluationContext evaluationContext) {
super(source, pos);
this.evaluationContext = evaluationContext;
}
public void generateAssignment(BlockScope currentScope, CodeStream codeStream, Assignment assignment, boolean valueRequired) {
FieldBinding codegenBinding = this.binding.original();
if (codegenBinding.canBeSeenBy(this.actualReceiverType, this, currentScope)) {
this.receiver.generateCode(currentScope, codeStream, !codegenBinding.isStatic());
assignment.expression.generateCode(currentScope, codeStream, true);
fieldStore(currentScope, codeStream, codegenBinding, null, this.actualReceiverType, this.receiver.isImplicitThis(), valueRequired);
} else {
codeStream.generateEmulationForField(codegenBinding);
this.receiver.generateCode(currentScope, codeStream, !codegenBinding.isStatic());
if (codegenBinding.isStatic()) { // need a receiver?
codeStream.aconst_null();
}
assignment.expression.generateCode(currentScope, codeStream, true);
if (valueRequired) {
switch (codegenBinding.type.id) {
case TypeIds.T_long :
case TypeIds.T_double :
codeStream.dup2_x2();
break;
default :
codeStream.dup_x2();
break;
}
}
codeStream.generateEmulatedWriteAccessForField(codegenBinding);
}
if (valueRequired){
codeStream.generateImplicitConversion(assignment.implicitConversion);
}
}
/**
* 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
*/
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);
}
} else {
FieldBinding codegenBinding = this.binding.original();
boolean isStatic = codegenBinding.isStatic();
this.receiver.generateCode(currentScope, codeStream, !isStatic);
if (valueRequired) {
Constant fieldConstant = codegenBinding.constant();
if (fieldConstant == Constant.NotAConstant) {
if (codegenBinding.declaringClass == null) { // array length
codeStream.arraylength();
} else {
if (codegenBinding.canBeSeenBy(this.actualReceiverType, this, currentScope)) {
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 {
if (isStatic) {
// we need a null on the stack to use the reflect emulation
codeStream.aconst_null();
}
codeStream.generateEmulatedReadAccessForField(codegenBinding);
}
}
codeStream.generateImplicitConversion(this.implicitConversion);
} else {
if (!isStatic) {
codeStream.invokeObjectGetClass(); // perform null check
codeStream.pop();
}
codeStream.generateConstant(fieldConstant, this.implicitConversion);
}
} else {
if (!isStatic){
codeStream.invokeObjectGetClass(); // perform null check
codeStream.pop();
}
}
}
codeStream.recordPositionsFrom(pc, this.sourceStart);
}
public void generateCompoundAssignment(BlockScope currentScope, CodeStream codeStream, Expression expression, int operator, int assignmentImplicitConversion, boolean valueRequired) {
boolean isStatic;
FieldBinding codegenBinding = this.binding.original();
if (codegenBinding.canBeSeenBy(this.actualReceiverType, this, currentScope)) {
this.receiver.generateCode(currentScope, codeStream, !(isStatic = codegenBinding.isStatic()));
TypeBinding constantPoolDeclaringClass = CodeStream.getConstantPoolDeclaringClass(currentScope, codegenBinding, this.actualReceiverType, this.receiver.isImplicitThis());
if (isStatic) {
codeStream.fieldAccess(Opcodes.OPC_getstatic, codegenBinding, constantPoolDeclaringClass);
} else {
codeStream.dup();
codeStream.fieldAccess(Opcodes.OPC_getfield, codegenBinding, constantPoolDeclaringClass);
}
int operationTypeID;
switch(operationTypeID = (this.implicitConversion & IMPLICIT_CONVERSION_MASK) >> 4) {
case T_JavaLangString :
case T_JavaLangObject :
case T_undefined :
codeStream.generateStringConcatenationAppend(currentScope, null, expression);
break;
default :
// 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, null, this.actualReceiverType, this.receiver.isImplicitThis(), valueRequired);
} else {
this.receiver.generateCode(currentScope, codeStream, !(isStatic = codegenBinding.isStatic()));
if (isStatic) {
// used to store the value
codeStream.generateEmulationForField(codegenBinding);
codeStream.aconst_null();
// used to retrieve the actual value
codeStream.aconst_null();
codeStream.generateEmulatedReadAccessForField(codegenBinding);
} else {
// used to store the value
codeStream.generateEmulationForField(this.binding);
this.receiver.generateCode(currentScope, codeStream, !isStatic);
// used to retrieve the actual value
codeStream.dup();
codeStream.generateEmulatedReadAccessForField(codegenBinding);
}
int operationTypeID;
if ((operationTypeID = (this.implicitConversion & IMPLICIT_CONVERSION_MASK) >> 4) == T_JavaLangString) {
codeStream.generateStringConcatenationAppend(currentScope, null, expression);
} else {
// 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);
}
// current stack is:
// field receiver value
if (valueRequired) {
if ((codegenBinding.type == TypeBinding.LONG) || (codegenBinding.type == TypeBinding.DOUBLE)) {
codeStream.dup2_x2();
} else {
codeStream.dup_x2();
}
}
// current stack is:
// value field receiver value
codeStream.generateEmulatedWriteAccessForField(codegenBinding);
}
}
public void generatePostIncrement(BlockScope currentScope, CodeStream codeStream, CompoundAssignment postIncrement, boolean valueRequired) {
boolean isStatic;
FieldBinding codegenBinding = this.binding.original();
if (codegenBinding.canBeSeenBy(this.actualReceiverType, this, currentScope)) {
super.generatePostIncrement(currentScope, codeStream, postIncrement, valueRequired);
} else {
this.receiver.generateCode(currentScope, codeStream, !(isStatic = codegenBinding.isStatic()));
if (isStatic) {
codeStream.aconst_null();
}
// the actual stack is: receiver
codeStream.dup();
// the actual stack is: receiver receiver
codeStream.generateEmulatedReadAccessForField(codegenBinding);
// the actual stack is: receiver value
// receiver value
// value receiver value dup_x1 or dup2_x1 if value required
// value value receiver value dup_x1 or dup2_x1
// value value receiver pop or pop2
// value value receiver field generateEmulationForField
// value value field receiver swap
// value field receiver value field receiver dup2_x1 or dup2_x2
// value field receiver value pop2
// value field receiver newvalue generate constant + op
// value store
int typeID;
switch (typeID = codegenBinding.type.id) {
case TypeIds.T_long :
case TypeIds.T_double :
if (valueRequired) {
codeStream.dup2_x1();
}
codeStream.dup2_x1();
codeStream.pop2();
break;
default :
if (valueRequired) {
codeStream.dup_x1();
}
codeStream.dup_x1();
codeStream.pop();
break;
}
codeStream.generateEmulationForField(codegenBinding);
codeStream.swap();
switch (typeID) {
case TypeIds.T_long :
case TypeIds.T_double :
codeStream.dup2_x2();
break;
default :
codeStream.dup2_x1();
break;
}
codeStream.pop2();
codeStream.generateConstant(postIncrement.expression.constant, this.implicitConversion);
codeStream.sendOperator(postIncrement.operator, codegenBinding.type.id);
codeStream.generateImplicitConversion(postIncrement.preAssignImplicitConversion);
codeStream.generateEmulatedWriteAccessForField(codegenBinding);
}
}
/*
* No need to emulate access to protected fields since not implicitly accessed
*/
public void manageSyntheticAccessIfNecessary(BlockScope currentScope, FlowInfo flowInfo, boolean isReadAccess){
// The private access will be managed through the code generation
if ((flowInfo.tagBits & FlowInfo.UNREACHABLE_OR_DEAD) != 0) return;
}
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
// regular receiver reference
this.actualReceiverType = this.receiver.resolveType(scope);
if (this.actualReceiverType == null){
this.constant = Constant.NotAConstant;
return null;
}
// the case receiverType.isArrayType and token = 'length' is handled by the scope API
this.binding = scope.getField(this.actualReceiverType, this.token, this);
FieldBinding firstAttempt = this.binding;
boolean isNotVisible = false;
if (!this.binding.isValidBinding()) {
if (this.binding instanceof ProblemFieldBinding
&& ((ProblemFieldBinding) this.binding).problemId() == NotVisible) {
isNotVisible = true;
if (this.evaluationContext.declaringTypeName != null) {
this.delegateThis = scope.getField(scope.enclosingSourceType(), DELEGATE_THIS, this);
if (this.delegateThis == null){ // if not found then internal error, field should have been found
this.constant = Constant.NotAConstant;
scope.problemReporter().invalidField(this, this.actualReceiverType);
return null;
}
this.actualReceiverType = this.delegateThis.type;
} else {
this.constant = Constant.NotAConstant;
scope.problemReporter().invalidField(this, this.actualReceiverType);
return null;
}
CodeSnippetScope localScope = new CodeSnippetScope(scope);
this.binding = localScope.getFieldForCodeSnippet(this.delegateThis.type, this.token, this);
}
}
if (!this.binding.isValidBinding()) {
this.constant = Constant.NotAConstant;
if (isNotVisible) {
this.binding = firstAttempt;
}
scope.problemReporter().invalidField(this, this.actualReceiverType);
return null;
}
if (isFieldUseDeprecated(this.binding, scope, this.bits)) {
scope.problemReporter().deprecatedField(this.binding, this);
}
// check for this.x in static is done in the resolution of the receiver
this.constant = this.receiver.isImplicitThis() ? this.binding.constant() : Constant.NotAConstant;
if (!this.receiver.isThis()) { // TODO need to check if shouldn't be isImplicitThis check (and then removed)
this.constant = Constant.NotAConstant;
}
return this.resolvedType = this.binding.type;
}
}