blob: be1b681b21498a2576bc58e560388d12d2b00cdf [file] [log] [blame]
/**********************************************************************
* This file is part of "Object Teams Development Tooling"-Software
*
* Copyright 2003, 2006 Fraunhofer Gesellschaft, Munich, Germany,
* for its Fraunhofer Institute for Computer Architecture and Software
* Technology (FIRST), Berlin, Germany and Technical University Berlin,
* Germany.
*
* 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
* $Id$
*
* Please visit http://www.eclipse.org/objectteams for updates and contact.
*
* Contributors:
* Fraunhofer FIRST - Initial API and implementation
* Technical University Berlin - Initial API and implementation
**********************************************************************/
package org.eclipse.objectteams.otdt.internal.core.compiler.lookup;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jdt.core.compiler.CharOperation;
import org.eclipse.jdt.internal.compiler.ast.Argument;
import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration;
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.lookup.BinaryTypeBinding;
import org.eclipse.jdt.internal.compiler.lookup.FieldBinding;
import org.eclipse.jdt.internal.compiler.lookup.LocalVariableBinding;
import org.eclipse.jdt.internal.compiler.lookup.LookupEnvironment;
import org.eclipse.jdt.internal.compiler.lookup.MethodBinding;
import org.eclipse.jdt.internal.compiler.lookup.ProblemFieldBinding;
import org.eclipse.jdt.internal.compiler.lookup.ProblemReasons;
import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding;
import org.eclipse.jdt.internal.compiler.lookup.SourceTypeBinding;
import org.eclipse.jdt.internal.compiler.lookup.SyntheticMethodBinding;
import org.eclipse.jdt.internal.compiler.lookup.TypeBinding;
import org.eclipse.jdt.internal.compiler.lookup.UnresolvedReferenceBinding;
import org.eclipse.objectteams.otdt.core.compiler.IOTConstants;
import org.eclipse.objectteams.otdt.core.exceptions.InternalCompilerError;
import org.eclipse.objectteams.otdt.internal.core.compiler.control.Config;
import org.eclipse.objectteams.otdt.internal.core.compiler.control.Config.NotConfiguredException;
import org.eclipse.objectteams.otdt.internal.core.compiler.model.TeamModel;
/**
* New for OTDT:
*
* This class manages the access to role fields.
* Mainly two issues are relevant here:
* 1. Role fields are copied and references must use an indirection via the team.
* 2. Role fields are in the class part, wheras usually the interface part is used.
*
* All issues are resolved by synthetic method bindings.
* They are similar to normal synthetic field accessors, but
* - they are not static to allow overriding to adjust to the suitable role type (impl.inh.)
* - they generate different code (including a cast to the class part)
*
* Synthetic role field accessors always reside in the enclosing team,
*
* This class does NOT handle the case of private fields since this class ONLY
* handles field access from outside the role object, and private fields are not
* visible for that kind of access.
*
* @author stephan
*/
public class SyntheticRoleFieldAccess extends SyntheticOTTargetMethod {
static final char[] FIELD_GET_NAME = "_fieldget_".toCharArray(); //$NON-NLS-1$
static final char[] FIELD_GET_PREFIX = CharOperation.concat(
IOTConstants.OT_DOLLAR_NAME,
FIELD_GET_NAME);
static final char[] FIELD_SET_NAME = "_fieldset_".toCharArray(); //$NON-NLS-1$
static final char[] FIELD_SET_PREFIX = CharOperation.concat(
IOTConstants.OT_DOLLAR_NAME,
FIELD_SET_NAME);
char[] encodedFieldName;
public SyntheticRoleFieldAccess(FieldBinding targetField, boolean isReadAccess, SourceTypeBinding declaringClass) {
super(targetField, isReadAccess, false, declaringClass);
if (targetField.declaringClass instanceof SourceTypeBinding && ((SourceTypeBinding)targetField.declaringClass).scope != null) {
FieldDeclaration sourceField = targetField.sourceField();
if (sourceField != null) {
this.sourceStart = sourceField.sourceStart;
retrieveLineNumber(declaringClass);
}
}
this.modifiers &= ~ClassFileConstants.AccStatic;
this.selector = CharOperation.concatWith( isReadAccess ? FIELD_GET_PREFIX : FIELD_SET_PREFIX,
new char[][] {targetField.declaringClass.sourceName(),
targetField.name},
'$');
}
/** Recover an accessor from binary. */
public SyntheticRoleFieldAccess(BinaryTypeBinding declaringClass,
int modifiers,
char[] selector,
TypeBinding[] parameters,
TypeBinding returnType)
{
super(declaringClass, modifiers, selector, parameters, returnType);
if (CharOperation.prefixEquals(FIELD_GET_PREFIX, selector))
this.purpose = FieldReadAccess;
else
this.purpose = FieldWriteAccess;
}
// hook for first ctor:
@Override
protected TypeBinding getReceiverParameterType(FieldBinding targetField, ReferenceBinding declaringSourceType)
{
if (!targetField.isStatic()) {
ReferenceBinding targetDeclaringClass = targetField.declaringClass;
if (targetDeclaringClass.isRole()) {
if (TeamModel.isTeamContainingRole(declaringSourceType, targetDeclaringClass))
{
targetDeclaringClass = (ReferenceBinding)TeamModel.strengthenRoleType(declaringSourceType, targetDeclaringClass);
} else {
throw new InternalCompilerError("synth accessor created in wrong scope"); //$NON-NLS-1$
}
return targetDeclaringClass.getRealType();
}
}
return declaringSourceType.getRealType();
}
/** For binary type: recover target field information from the encoded selector. */
public FieldBinding resolvedField() {
if (this.targetReadField != null)
return this.targetReadField;
if (this.targetWriteField != null)
return this.targetWriteField;
char[][] splitName = CharOperation.splitOn('$', this.selector); // "_OT fieldget/set Role field"
ReferenceBinding roleType = this.declaringClass.getMemberType(splitName[2]);
FieldBinding field = roleType.getRealClass().getField(splitName[3], true);
if (field == null) {
field = roleType.getRealType().getField(splitName[3], true);
if (field == null || !field.isStatic())
field = new ProblemFieldBinding(roleType, splitName[3], ProblemReasons.NotFound);
}
if (CharOperation.equals(FIELD_GET_NAME, splitName[1])) {
this.targetReadField = field;
this.purpose = FieldReadAccess;
} else {
this.targetWriteField = field;
this.purpose = FieldWriteAccess;
}
return field;
}
public void resolveTypes() {
LookupEnvironment env;
try {
env = Config.getLookupEnvironment();
} catch (NotConfiguredException e) {
e.logWarning("Cannot resolve types"); //$NON-NLS-1$
return;
}
for (int i=0; i<this.parameters.length; i++) {
TypeBinding param = this.parameters[i];
if (param instanceof UnresolvedReferenceBinding)
this.parameters[i] = ((UnresolvedReferenceBinding)param).resolve(env, false);
}
if (this.returnType instanceof UnresolvedReferenceBinding)
this.returnType = ((UnresolvedReferenceBinding)this.returnType).resolve(env, false);
}
public void generateBodyForRead(FieldBinding fieldBinding, CodeStream codeStream) {
if (fieldBinding.isStatic()) {
fieldBinding = checkAdjustRoleFieldAccess(fieldBinding, codeStream); // may insert cast, too.
codeStream.fieldAccess(Opcodes.OPC_getstatic, fieldBinding, fieldBinding.declaringClass);
// FIXME(SH): throw new InternalCompilerError("accessor for static field not applicable.");
} else {
// prepare "this" and role args:
LocalVariableBinding thisArg = createArgumentBinding(codeStream, "this".toCharArray(), fieldBinding.declaringClass.enclosingType(), 0); //$NON-NLS-1$
char[] argName = typeNameToLower(this.parameters[0].sourceName());
LocalVariableBinding arg1 = createArgumentBinding(codeStream, argName, this.parameters[0], 1);
// generate code:
codeStream.aload_1(); // not a static accessor, positions shifted by 1.
fieldBinding = checkAdjustRoleFieldAccess(fieldBinding, codeStream); // may insert cast, too.
codeStream.fieldAccess(Opcodes.OPC_getfield, fieldBinding, fieldBinding.declaringClass);
// finish args:
if ((codeStream.generateAttributes & (ClassFileConstants.ATTR_VARS
| ClassFileConstants.ATTR_STACK_MAP_TABLE
| ClassFileConstants.ATTR_STACK_MAP)) == 0)
return; // avoid NPE below
thisArg.recordInitializationEndPC(codeStream.position);
arg1.recordInitializationEndPC(codeStream.position);
}
}
private char[] typeNameToLower(char[] typeName) {
int len = typeName.length;
char[] newName = new char[len];
System.arraycopy(typeName, 0, newName, 0, len);
newName[0] = Character.toLowerCase(typeName[0]);
return newName;
}
private LocalVariableBinding createArgumentBinding(CodeStream codeStream, char[] argName, TypeBinding argType, int pos)
{
LocalVariableBinding argBinding = new LocalVariableBinding(argName, argType, 0, true);
argBinding.resolvedPosition = pos;
// declaration needed because otherwise completeCodeAttributeForSyntheticMethod
// would refuse to generate the local variable entry
argBinding.declaration = new Argument(argName, 0, null, 0);
codeStream.addVisibleLocalVariable(argBinding);
codeStream.record(argBinding);
argBinding.recordInitializationStartPC(0);
return argBinding;
}
public void generateBodyForWrite(FieldBinding fieldBinding, CodeStream codeStream) {
if (fieldBinding.isStatic()) {
fieldBinding = checkAdjustRoleFieldAccess(fieldBinding, codeStream);
codeStream.load(fieldBinding.type, 1);
codeStream.fieldAccess(Opcodes.OPC_putstatic, fieldBinding, fieldBinding.declaringClass);
} else {
// prepare "this" and role args:
LocalVariableBinding thisArg = createArgumentBinding(codeStream, "this".toCharArray(), fieldBinding.declaringClass.enclosingType(), 0); //$NON-NLS-1$
char[] argName = typeNameToLower(this.parameters[0].sourceName());
LocalVariableBinding arg1 = createArgumentBinding(codeStream, argName, this.parameters[0], 1);
LocalVariableBinding arg2 = createArgumentBinding(codeStream, "value".toCharArray(), this.parameters[1], 2); //$NON-NLS-1$
codeStream.aload_1(); // not a static accessor, positions shifted by 1.
fieldBinding = checkAdjustRoleFieldAccess(fieldBinding, codeStream);
codeStream.load(fieldBinding.type, 2);
codeStream.fieldAccess(Opcodes.OPC_putfield, fieldBinding, fieldBinding.declaringClass);
// finish args:
if ((codeStream.generateAttributes & (ClassFileConstants.ATTR_VARS
| ClassFileConstants.ATTR_STACK_MAP_TABLE
| ClassFileConstants.ATTR_STACK_MAP)) == 0)
return; // avoid NPE below
thisArg.recordInitializationEndPC(codeStream.position);
arg1.recordInitializationEndPC(codeStream.position);
arg2.recordInitializationEndPC(codeStream.position);
}
}
private FieldBinding checkAdjustRoleFieldAccess(FieldBinding fieldBinding,
CodeStream codeStream)
{
if (fieldBinding.declaringClass.isRole() && !fieldBinding.declaringClass.isInterface())
{
ReferenceBinding roleType = fieldBinding.declaringClass;
roleType = (ReferenceBinding)TeamModel.strengthenRoleType(this.declaringClass, roleType);
roleType = roleType.getRealClass();
if (!fieldBinding.isStatic())
codeStream.checkcast(roleType); // receiver cast
fieldBinding = new FieldBinding(fieldBinding, roleType);
}
return fieldBinding;
}
/**
* Generate the sequence for invoking this accessor.
*
* PRE: the role instance is on the stack, for write access also the new value
* POST: values from PRE have been consumed, for read access the value is on the stack
*
* @param codeStream
*/
@Override
public byte prepareOrGenerateInvocation(CodeStream codeStream, byte opcode) {
ReferenceBinding roleType = (ReferenceBinding)this.parameters[0];
if (roleType instanceof UnresolvedReferenceBinding) {
try {
roleType = ((UnresolvedReferenceBinding)roleType)
.resolve(Config.getLookupEnvironment(), false);
} catch (NotConfiguredException e) {
e.logWarning("Failed to generate accessor"); //$NON-NLS-1$
return opcode;
}
this.parameters[0] = roleType;
}
if (this.purpose == FieldReadAccess || this.purpose == SuperFieldReadAccess) {
insertOuterAccess(codeStream, roleType); // role -> role,team
codeStream.swap(); // role,team -> team,role
codeStream.invoke(Opcodes.OPC_invokevirtual,
this, // team,role -> result
this.declaringClass);
} else {
TypeBinding targetType = this.targetWriteField.type;
LocalVariableBinding arg = new LocalVariableBinding("<tmp>".toCharArray(), //$NON-NLS-1$
targetType,
0, false);
arg.resolvedPosition = codeStream.maxLocals;
arg.useFlag= LocalVariableBinding.USED;
codeStream.record(arg);
arg.recordInitializationStartPC(codeStream.position);
codeStream.store(arg, false/*valueRequired*/); // role, arg -> role
insertOuterAccess(codeStream, roleType); // role -> role,team
codeStream.swap(); // role,team -> team,role
codeStream.load(arg); // -> team,role,arg
codeStream.invoke(Opcodes.OPC_invokevirtual,
this, // -> <empty>
this.declaringClass);
if (arg.initializationPCs != null) // null checking is asymmetric in LocalVariableBinding.
arg.recordInitializationEndPC(codeStream.position);
}
return 0; // done
}
private void insertOuterAccess(CodeStream codeStream, ReferenceBinding roleType)
{
// external: use _OT$getTeam() method:
codeStream.dup(); // role,role
codeStream.invokeGetTeam(roleType); // role,team
codeStream.checkcast(this.declaringClass); // role,team
}
// =============== Static interface ===================
/**
* When reading a method from byte code: do modifiers and selector denote a role field accessor?
*/
public static boolean isRoleFieldAccess(int modifiers, char[] selector) {
if ((modifiers & ClassFileConstants.AccSynthetic) == 0)
return false;
if (CharOperation.prefixEquals(FIELD_GET_PREFIX, selector))
return true;
if (CharOperation.prefixEquals(FIELD_SET_PREFIX, selector))
return true;
return false;
}
/**
* Is given field a role field requiring a synthetic accessor?
* If so answer the enclosing team class.
*
* @param targetField the field
*
* @return the enclosing team type where the accessor should reside, or null.
*/
public static ReferenceBinding getTeamOfRoleField(FieldBinding targetField)
{
if (!targetField.declaringClass.isRole())
return null; // not a role
if (targetField.isSynthetic())
return null; // synthetics shouldn't be accessed across scopes
return targetField.declaringClass.enclosingType();
}
/**
* Get the access method for a given team and field when accessing from outside.
*
* @param enclosingTeam
* @param targetField
* @param isReadAccess
* @param externalizedReceiver whether the receiver of this field access is an externalized role
* @return a synthetic field accessor method or null if not found
*/
public static @Nullable SyntheticMethodBinding getAccessorFor(ReferenceBinding enclosingTeam,
FieldBinding targetField,
boolean isReadAccess,
boolean externalizedReceiver)
{
if (enclosingTeam.isBinaryBinding()) {
for (MethodBinding method : enclosingTeam.methods()) {
if (SyntheticRoleFieldAccess.isAccessFor(method, targetField, isReadAccess))
return (SyntheticMethodBinding)method;
}
return null;
} else {
SourceTypeBinding outerSrc = (SourceTypeBinding)enclosingTeam;
return outerSrc.addSyntheticMethod(targetField, isReadAccess, false/*isSuperAccess*/, externalizedReceiver);
}
}
/* when scanning binary access methods: check if method is indeed the required accessor for targetField. */
private static boolean isAccessFor(MethodBinding method, FieldBinding targetField, boolean isReadAccess)
{
if (!(method instanceof SyntheticRoleFieldAccess))
return false;
SyntheticRoleFieldAccess accessor = (SyntheticRoleFieldAccess)method;
if (accessor.resolvedField() != targetField)
return false;
return isReadAccess ? accessor.purpose == FieldReadAccess : accessor.purpose == FieldWriteAccess;
}
}