blob: f9c3f048461212f0115ccad89d1029af3a073b9f [file] [log] [blame]
/**********************************************************************
* This file is part of "Object Teams Development Tooling"-Software
*
* Copyright 2014 GK Software AG
*
* 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
*
* Please visit http://www.eclipse.org/objectteams for updates and contact.
*
* Contributors:
* Stephan Herrmann - Initial API and implementation
**********************************************************************/
package org.eclipse.objectteams.otdt.internal.core.compiler.lookup;
import org.eclipse.jdt.internal.compiler.ast.ASTNode;
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.BlockScope;
import org.eclipse.jdt.internal.compiler.lookup.FieldBinding;
import org.eclipse.jdt.internal.compiler.lookup.MethodBinding;
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.TypeIds;
import org.eclipse.objectteams.otdt.internal.core.compiler.model.TeamModel;
/**
* A synthetic method binding, which used as an invocation target needs to
* influence code generation.
*
* @since 2.3.1
*/
public abstract class SyntheticOTTargetMethod extends SyntheticMethodBinding {
protected SyntheticOTTargetMethod(MethodBinding targetMethod, int purpose) {
super(targetMethod, purpose);
}
protected SyntheticOTTargetMethod(FieldBinding targetField, boolean isReadAccess, boolean isSuperAccess, SourceTypeBinding declaringClass) {
super(targetField, isReadAccess, isSuperAccess, declaringClass);
}
protected SyntheticOTTargetMethod(BinaryTypeBinding declaringClass, int modifiers, char[] selector,
TypeBinding[] parameters, TypeBinding returnType) {
super(declaringClass, modifiers, selector, parameters, returnType);
}
/**
* We assume: all arguments of an invocation have already been pushed.
* At this point we may insert additional byte codes, or generate the actual invocation.
* @return 0 means: no further action needed, else we return the opcode for which the actual
* invoke still needs to be generated.
*/
public abstract byte prepareOrGenerateInvocation(CodeStream codeStream, byte opcode);
/**
* Represents an inferred callout-to-field.
* May need to tweak invokestatic (for regular access$n methods) into invokevirtual (for the generated c-t-f method).
*/
public static class CalloutToField extends SyntheticOTTargetMethod {
public CalloutToField(MethodBinding targetMethod) {
super(targetMethod, InferredCalloutToField);
}
@Override
public byte prepareOrGenerateInvocation(CodeStream codeStream, byte opcode) {
if (!isStatic() && this.purpose == SyntheticMethodBinding.InferredCalloutToField)
return Opcodes.OPC_invokevirtual;
return opcode;
}
}
/**
* Represents a decapsulating field access while targeting OTDRE.
* We need to generate a special sequence to call _OT$access(int,int,Object[],ITeam)
* Currently only supports read access.
* Typical use is when a base predicate refers to a private base field.
*/
public static class OTDREFieldDecapsulation extends SyntheticOTTargetMethod {
private int accessId;
private int opKind;
private ReferenceBinding enclosingTeam;
private TypeBinding originalType;
private ASTNode site;
private BlockScope scope;
/**
* Create a binding for a field access using decapsulation.
* @param fakedMethod this method is member of the base class, representing the otdre-generated access method.
* @param originalType return type of the original feature (field)
* @param accessId ID by which the base field is identified inside the access method
* @param opKind 0 = get, 1 = set
* @param scope where this access has been seen
* @param site the exact node where decapsulation happened
*/
public OTDREFieldDecapsulation(MethodBinding fakedMethod, TypeBinding originalType, int accessId, int opKind, BlockScope scope, ASTNode site) {
super(fakedMethod, SyntheticMethodBinding.InferredCalloutToField);
this.accessId = accessId;
this.opKind = opKind;
ReferenceBinding enclosingRole = scope.enclosingReceiverType();
this.enclosingTeam = enclosingRole.enclosingType();
this.originalType = originalType;
this.site = site;
this.scope = scope;
}
@Override
public byte prepareOrGenerateInvocation(CodeStream codeStream, byte opcode) {
// accessId
codeStream.bipush((byte) this.accessId);
// 0 = get, 1 = set
if (this.opKind == 0)
codeStream.iconst_0();
else
codeStream.iconst_1();
// no args to pack for read access:
codeStream.aconst_null();
// enclosing team instance:
Object[] emulationPath = this.scope.getEmulationPath(this.enclosingTeam, true /*only exact match*/, false/*consider enclosing arg*/);
codeStream.generateOuterAccess(emulationPath, this.site, this.enclosingTeam, this.scope);
// invoke it:
byte invoke = this.targetMethod.isStatic() ? Opcodes.OPC_invokestatic : Opcodes.OPC_invokevirtual;
codeStream.invoke(invoke, this.targetMethod, this.targetMethod.declaringClass);
// convert result?:
if (this.originalType != TypeBinding.VOID) {
if (this.originalType.isBaseType()) {
codeStream.checkcast(this.scope.environment().computeBoxingType(this.originalType));
codeStream.generateUnboxingConversion(this.originalType.id);
} else {
codeStream.checkcast(this.originalType);
}
} else {
// what? - not hit for field read :)
}
return 0; // signal we're done
}
}
/**
* Represents a decapsulating method access while targeting OTDRE (not an explicit callout).
* We need to generate a special sequence to call _OT$access(int,int,Object[],ITeam)
*/
public static class OTDREMethodDecapsulation extends SyntheticOTTargetMethod implements TeamModel.UpdatableAccessId {
private int accessId;
private ReferenceBinding enclosingTeam;
private TypeBinding[] originalParameters;
private TypeBinding originalReturnType;
private ASTNode site;
private BlockScope scope;
/**
* Create a binding for a method access using decapsulation.
* @param targetMethod this method is member of the base class, representing the otdre-generated access method.
* @param originalParameters parameters of the original target method (before replacing with _OT$access)
* @param originalReturn return type of the original target method (before replacing with _OT$access)
* @param accessId ID by which the base field is identified inside the access method
* @param scope where this access has been seen
* @param site the exact node where decapsulation happened
*/
public OTDREMethodDecapsulation(MethodBinding targetMethod, TypeBinding[] originalParameters, TypeBinding originalReturn, int accessId, BlockScope scope, ASTNode site) {
super(targetMethod, SyntheticMethodBinding.MethodDecapsulation);
this.accessId = accessId;
ReferenceBinding enclosingRole = scope.enclosingReceiverType();
this.enclosingTeam = enclosingRole.enclosingType();
this.originalParameters = originalParameters;
this.originalReturnType = originalReturn;
this.site = site;
this.scope = scope;
}
@Override
public byte prepareOrGenerateInvocation(CodeStream codeStream, byte opcode) {
TypeBinding[] tgtParams = this.originalParameters;
byte len = (byte) tgtParams.length;
// argument array:
codeStream.bipush(len);
codeStream.anewarray(this.scope.getJavaLangObject());
// fold array store into arguments on stack:
for (byte i = (byte) (len-1); i >= 0; i--) {
// argi, array
if (size(tgtParams[i]) == 1) {
codeStream.dup_x1();
// array, argi, array
codeStream.swap();
} else {
codeStream.dup_x2();
// array, argi, array
codeStream.dup_x2();
// array, array, argi, array
codeStream.pop();
}
// array, array, argi
if (tgtParams[i].isPrimitiveType())
codeStream.generateBoxingConversion(tgtParams[i].id); // no longer need to handle 2-byte values
codeStream.bipush(i);
// array, array, argi, i
codeStream.swap();
// array, array, i, argi
codeStream.aastore();
// array
}
// array (containing all arguments)
// accessId:
codeStream.bipush((byte) this.accessId);
codeStream.swap();
// accessId, array
// opKind (ignored):
codeStream.iconst_0();
codeStream.swap();
// accessId, opKind, array
// enclosing team instance:
Object[] emulationPath = this.scope.getEmulationPath(this.enclosingTeam, true /*only exact match*/, false/*consider enclosing arg*/);
codeStream.generateOuterAccess(emulationPath, this.site, this.enclosingTeam, this.scope);
// invoke it:
byte invoke = this.targetMethod.isStatic() ? Opcodes.OPC_invokestatic : Opcodes.OPC_invokevirtual;
codeStream.invoke(invoke, this, this.targetMethod.declaringClass);
// convert result?:
if (this.originalReturnType != TypeBinding.VOID) {
if (this.originalReturnType.isBaseType()) {
codeStream.checkcast(this.scope.environment().computeBoxingType(this.originalReturnType));
codeStream.generateUnboxingConversion(this.originalReturnType.id);
} else {
codeStream.checkcast(this.originalReturnType);
}
} else {
codeStream.pop();
}
return 0; // signal we're done
}
@Override
public void update(int offset) {
this.accessId += offset;
}
}
static int size(TypeBinding type) {
switch (type.id) {
case TypeIds.T_double :
case TypeIds.T_long :
return 2;
case TypeIds.T_void :
return 0;
default :
return 1;
}
}
}