| /********************************************************************** |
| * 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; |
| } |
| } |