blob: 54d377c3c37424aea08948a1308ec27fc35b2150 [file] [log] [blame]
/**********************************************************************
* This file is part of "Object Teams Development Tooling"-Software
*
* Copyright 2004, 2016 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
*
* 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.ast;
import org.eclipse.jdt.core.compiler.CharOperation;
import org.eclipse.jdt.internal.compiler.ASTVisitor;
import org.eclipse.jdt.internal.compiler.ast.ASTNode;
import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration;
import org.eclipse.jdt.internal.compiler.ast.Argument;
import org.eclipse.jdt.internal.compiler.ast.Expression;
import org.eclipse.jdt.internal.compiler.ast.IntLiteral;
import org.eclipse.jdt.internal.compiler.ast.LambdaExpression;
import org.eclipse.jdt.internal.compiler.ast.MessageSend;
import org.eclipse.jdt.internal.compiler.ast.MethodDeclaration;
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.impl.CompilerOptions.WeavingScheme;
import org.eclipse.jdt.internal.compiler.lookup.BaseTypeBinding;
import org.eclipse.jdt.internal.compiler.lookup.BlockScope;
import org.eclipse.jdt.internal.compiler.lookup.ExtraCompilerModifiers;
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.ReferenceBinding;
import org.eclipse.jdt.internal.compiler.lookup.Scope;
import org.eclipse.jdt.internal.compiler.lookup.SourceTypeBinding;
import org.eclipse.jdt.internal.compiler.lookup.SyntheticArgumentBinding;
import org.eclipse.jdt.internal.compiler.lookup.TypeBinding;
import org.eclipse.jdt.internal.compiler.lookup.TypeConstants;
import org.eclipse.jdt.internal.compiler.problem.ProblemReporter;
import org.eclipse.objectteams.otdt.core.compiler.IOTConstants;
import org.eclipse.objectteams.otdt.core.exceptions.InternalCompilerError;
import org.eclipse.objectteams.otdt.internal.core.compiler.lifting.Lowering;
import org.eclipse.objectteams.otdt.internal.core.compiler.lookup.SyntheticBaseCallSurrogate;
import org.eclipse.objectteams.otdt.internal.core.compiler.mappings.CallinImplementorDyn;
import org.eclipse.objectteams.otdt.internal.core.compiler.model.MethodModel;
import org.eclipse.objectteams.otdt.internal.core.compiler.problem.BaseCallProblemReporterWrapper;
import org.eclipse.objectteams.otdt.internal.core.compiler.problem.BlockScopeWrapper;
import org.eclipse.objectteams.otdt.internal.core.compiler.problem.ProblemReporterWrapper;
import org.eclipse.objectteams.otdt.internal.core.compiler.statemachine.transformer.MethodSignatureEnhancer;
import org.eclipse.objectteams.otdt.internal.core.compiler.util.AstGenerator;
/**
* NEW for OTDT:
*
* This class encodes a base call in a callin method.
*
* What: Intercept error ParameterMismatch
* Why: @see BaseCallProblemReporterWrapper
*
* What: Help to analyse number of base calls
* How: see analyseCode and MethodDeclaration.baseCallTrackingVariable
*
* What: re-wire the message send to the base call surrogate
* How: resolveType adapts receiver, selector and return type,
* may even insert an assignment to _OT$result for base return tunneling.
* TransformStatementsVisitor will take care of argument enhancing.
*
* What: create a method binding faking the base call surrogate to be created by the OTRE
*
* @author gis
*/
public class BaseCallMessageSend extends AbstractExpressionWrapper
{
public char[] sourceSelector;
public Expression[] sourceArgs;
// keep a reference to the original send in case the _wrappee is replaced by some transformation
// (e.g., CollectedReplacementsTransformer wrapping _wrappee with a cast).
protected MessageSend _sendOrig;
BaseReference _receiver; // avoid casts like (BaseReference)wrappee.receiver
public boolean isSuperAccess; // flag for base.super.m() calls
private WeavingScheme _weavingScheme = WeavingScheme.OTRE; // avoid null
private MethodDeclaration enclosingCallinMethod;
public BaseCallMessageSend(MessageSend wrappee, int baseEndPos)
{
super(wrappee, wrappee.sourceStart, wrappee.sourceEnd);
wrappee.receiver =
this._receiver = new BaseReference(wrappee.sourceStart, baseEndPos);
this._sendOrig = wrappee;
this.sourceSelector = wrappee.selector;
this.sourceArgs = wrappee.arguments;
}
/** Signal whether this base call is actually a base.super-call. */
public void setSuperAccess(boolean isSuperAccess, ProblemReporter problemReporter) {
this.isSuperAccess = isSuperAccess;
if (isSuperAccess)
problemReporter.baseSuperCallDecapsulation(this);
}
public void prepareSuperAccess(WeavingScheme weavingScheme, MethodDeclaration enclosingMethod, BlockScope scope) {
// add a further boolean argument to pass this flag to the runtime.
// insert it at front of regular arguments to it will end up between normal enhancement args and regular args.
// (note that this arg may be removed again if current callin method is static,
// see TransformStatementsVisitor#visit(MessageSend,BlockScope))
Expression[] args = this._sendOrig.arguments;
this.sourceArgs = args;
int len = 0;
int extra = weavingScheme == WeavingScheme.OTRE ? 1 : 0;
if (args == null) {
args = new Expression[extra];
} else {
len = args.length;
System.arraycopy(args, 0, args=new Expression[len+extra], extra, len);
}
if (weavingScheme == WeavingScheme.OTRE) {
// insert before regular args:
args[0] = new AstGenerator(this).booleanLiteral(this.isSuperAccess);
} else {
// translate & pack arguments and append baseCallFlags:
AstGenerator gen = new AstGenerator(this);
IntLiteral baseCallFlags = gen.intLiteral(this.isSuperAccess ? 2 : 1);
if (args.length == 0) {
args = new Expression[] { gen.nullLiteral(), baseCallFlags };
} else {
int enhLen = MethodSignatureEnhancer.getEnhancingArgLen(weavingScheme);
if (enclosingMethod.arguments.length - enhLen != args.length) {
scope.problemReporter().baseCallDoesntMatchRoleMethodSignature(this);
return;
}
Expression[] boxedArgs = new Expression[args.length];
for (int i = 0; i < args.length; i++) {
Argument argument = enclosingMethod.arguments[i+enhLen];
TypeBinding argTypeBinding = argument.binding.type;
if (argTypeBinding.isBaseType()) {
boxedArgs[i] = gen.createBoxing(args[i], (BaseTypeBinding) argTypeBinding);
continue;
} else if (argument.type.isDeclaredLifting()) {
LiftingTypeReference ltr = (LiftingTypeReference)argument.type;
if (ltr.roleReference.resolvedType != null) {
Expression teamThis = gen.qualifiedThisReference(enclosingMethod.binding.declaringClass.enclosingType());
boxedArgs[i] = new Lowering().lowerExpression(scope, args[i],
ltr.roleReference.resolvedType, ltr.resolvedType, teamThis, true, true);
continue;
}
// fall through
}
boxedArgs[i] = args[i];
}
args = new Expression[] {
gen.arrayAllocation(gen.qualifiedTypeReference(TypeConstants.JAVA_LANG_OBJECT), 1, boxedArgs),
baseCallFlags
};
}
}
this._sendOrig.arguments = args;
this._weavingScheme = weavingScheme;
}
@Override
public FlowInfo analyseCode(BlockScope currentScope, FlowContext flowContext, FlowInfo flowInfo)
{
flowInfo = super.analyseCode(currentScope, flowContext, flowInfo);
MethodDeclaration callinMethod = getEnclosingCallinMethod(currentScope);
LocalVariableBinding trackingVariable = callinMethod.baseCallTrackingVariable.binding;
if (flowInfo.isDefinitelyAssigned(callinMethod.baseCallTrackingVariable))
currentScope.problemReporter().definitelyDuplicateBasecall(this._wrappee);
else if (flowInfo.isPotentiallyAssigned(trackingVariable))
currentScope.problemReporter().potentiallyDuplicateBasecall(this._wrappee);
else
flowInfo.markAsDefinitelyAssigned(trackingVariable);
// check exceptions thrown by any bound base method:
MethodScope methodScope = currentScope.methodScope();
if (methodScope != null) {
MethodModel methodModel = callinMethod.binding.model;
if ( methodModel != null
&& methodModel._baseExceptions != null)
{
for (ReferenceBinding exceptionType : methodModel._baseExceptions)
flowContext.checkExceptionHandlers(exceptionType, this, flowInfo, currentScope);
}
}
if (this.isSuperAccess)
// signal that the base call surrogate needs to handle super-access:
MethodModel.addCallinFlag(currentScope.methodScope().referenceMethod(), IOTConstants.CALLIN_FLAG_BASE_SUPER_CALL);
return flowInfo;
}
@Override
public int nullStatus(FlowInfo flowInfo, FlowContext flowContext) {
return FlowInfo.UNKNOWN;
}
@Override
public TypeBinding resolveType(BlockScope scope)
{
WeavingScheme weavingScheme = scope.compilerOptions().weavingScheme;
AstGenerator gen = new AstGenerator(this._wrappee.sourceStart, this._wrappee.sourceEnd);
MessageSend wrappedSend = this._sendOrig;
MethodDeclaration callinMethodDecl = getEnclosingCallinMethod(scope.methodScope());
if (callinMethodDecl == null)
return null; // no hope
MethodBinding callinMethodBinding = callinMethodDecl.binding;
if (callinMethodBinding == null)
return null; // no hope
boolean isStatic = scope.methodScope().isStatic;
// === re-wire the message send to the base call surrogate
// receiver:
ReferenceBinding roleType = scope.enclosingSourceType();
this._receiver.adjustReceiver(roleType, isStatic, callinMethodDecl, gen, weavingScheme);
// empty base call surrogate is handled using a synthetic base call surrogate:
boolean isCallinBound = SyntheticBaseCallSurrogate.isCallinMethodBoundIn(callinMethodBinding, callinMethodBinding.declaringClass);
// who should work, compiler or OTRE?
if (!isCallinBound && weavingScheme == WeavingScheme.OTRE) {
resolveSyntheticBaseCallSurrogate(callinMethodDecl, scope, weavingScheme);
return this.resolvedType;
}
// selector:
if (weavingScheme == WeavingScheme.OTDRE) {
wrappedSend.selector = CallinImplementorDyn.OT_CALL_NEXT;
} else {
wrappedSend.selector = SyntheticBaseCallSurrogate.genSurrogateName(wrappedSend.selector, roleType.sourceName(), isStatic);
}
// arguments are enhanced by the TransformStatementsVisitor(OTRE) or prepareSuperAccess() (OTDRE)
// return type:
TypeBinding returnType = MethodModel.getReturnType(callinMethodBinding);
boolean resultTunneling = false;
if (returnType != null) {
if (returnType.isPrimitiveType()) {
this._wrappee = gen.createUnboxing(this._wrappee, (BaseTypeBinding)returnType);
} else if (TypeBinding.equalsEquals(returnType, TypeBinding.VOID) && callinMethodDecl == scope.methodScope().referenceMethod()) {
// store value which is not used locally but has to be chained to the caller.
// (cannot set result in outer callin method!)
this._wrappee = gen.assignment(gen.singleNameReference(IOTConstants.OT_RESULT), this._wrappee);
resultTunneling = true;
}
}
BlockScopeWrapper baseCallScope = new BlockScopeWrapper(scope, this);
super.resolveType(baseCallScope);
if (weavingScheme == WeavingScheme.OTDRE) {
// convert Object result from callNext
if (returnType != null && !returnType.isPrimitiveType() && !resultTunneling) {
this.resolvedType = returnType;
if (TypeBinding.notEquals(returnType, TypeBinding.VOID))
this._sendOrig.valueCast = returnType;
}
// manually check arguments only in OTDRE:
Expression[] args = this.sourceArgs;
callinMethodBinding.switchToSourceParamters();
try {
TypeBinding[] parameters = callinMethodBinding.parameters;
if (args != null && args.length == parameters.length) {
TypeBinding[] argumentTypes = new TypeBinding[args.length];
boolean hasArgumentProblem = false;
for (int i = 0; i < args.length; i++) {
argumentTypes[i] = args[i].resolvedType;
if (argumentTypes[i] == null) {
return this.resolvedType;
}
if (!hasArgumentProblem && !argumentTypes[i].isBoxingCompatibleWith(parameters[i], scope))
hasArgumentProblem = true;
}
if (hasArgumentProblem)
scope.problemReporter().baseCallArgumentMismatch(callinMethodBinding, argumentTypes, this._sendOrig);
checkInvocationArguments(scope, this._receiver, roleType, callinMethodBinding, args, argumentTypes, false, this._sendOrig);
}
} finally {
callinMethodBinding.resetParameters();
}
}
return this.resolvedType;
}
/* manual resolve for an empty base call surrogate, which is indeed generated by the compiler, not the OTRE. */
private void resolveSyntheticBaseCallSurrogate(MethodDeclaration callinMethodDecl, BlockScope scope, WeavingScheme weavingScheme)
{
// find the method:
MethodBinding callinMethod = callinMethodDecl.binding;
if (callinMethod == null) {
if (callinMethodDecl.ignoreFurtherInvestigation)
return;
throw new InternalCompilerError("Unresolved method without an error"); //$NON-NLS-1$
}
// check name match:
if (!CharOperation.equals(this._sendOrig.selector, callinMethod.selector))
scope.problemReporter().baseCallNotSameMethod(callinMethodDecl, this._sendOrig);
// find the receiver type:
this._receiver.resolve(scope);
int depth = 0;
while (this._receiver.resolvedType.isLocalType()) {
this._receiver.resolvedType = this._receiver.resolvedType.enclosingType();
depth++;
}
this._receiver.bits |= depth << DepthSHIFT;
if (this._receiver.resolvedType instanceof ReferenceBinding) {
ReferenceBinding receiverType = (ReferenceBinding) this._receiver.resolvedType;
this._receiver.resolvedType = receiverType.getRealClass();
}
// resolve arguments:
TypeBinding[] sendparams = new TypeBinding[this._sendOrig.arguments.length];
for (int i=0; i<sendparams.length; i++)
sendparams[i] = this._sendOrig.arguments[i].resolveType(scope);
// check arguments:
int sourceArgsLen = 0;
if (this.sourceArgs != null)
sourceArgsLen = this.sourceArgs.length;
TypeBinding[] methodParams = callinMethod.getSourceParameters();
if (sourceArgsLen != methodParams.length) {
scope.problemReporter().baseCallDoesntMatchRoleMethodSignature(this);
} else {
for (int i=0; i<sourceArgsLen; i++) {
TypeBinding srcArgType = this.sourceArgs[i].resolvedType;
if (srcArgType == null) {
if (!callinMethodDecl.hasErrors())
throw new InternalCompilerError("Unexpected: srcArgType should only ever be missing in declarations with reported errors"); //$NON-NLS-1$
continue;
}
if (!srcArgType.isCompatibleWith(methodParams[i])) {
if (isBoxingCompatible(srcArgType, methodParams[i], this.sourceArgs[i], scope)) {
int enhancedArgIdx = i+MethodSignatureEnhancer.getEnhancingArgLen(weavingScheme)+1; // normal enhancement plus isSuperAccess flag
this._sendOrig.arguments[enhancedArgIdx].computeConversion(scope, methodParams[i], srcArgType);
} else {
scope.problemReporter().baseCallDoesntMatchRoleMethodSignature(this);
break;
}
}
}
}
// create and link the synthetic method binding:
MethodBinding surrogate = null;
MethodModel model = callinMethod.model;
if (model != null)
surrogate = model.getBaseCallSurrogate();
if (surrogate == null) {
SourceTypeBinding receiverClass = ((SourceTypeBinding)((ReferenceBinding)this._receiver.resolvedType).getRealClass());
if (SyntheticBaseCallSurrogate.isBindingForCallinMethodInherited(callinMethod)) {
ReferenceBinding currentRole = callinMethod.declaringClass;
while (surrogate == null && ((currentRole = currentRole.superclass()) != null)) {
surrogate = receiverClass.getExactMethod(SyntheticBaseCallSurrogate.genSurrogateName(this.sourceSelector, currentRole.sourceName(), callinMethod.isStatic()), sendparams, null);
}
} else {
surrogate = receiverClass.addSyntheticBaseCallSurrogate(callinMethod);
}
}
this._sendOrig.binding = surrogate;
this._sendOrig.actualReceiverType = this._receiver.resolvedType;
this._sendOrig.constant = Constant.NotAConstant;
this.resolvedType = this._sendOrig.resolvedType = MethodModel.getReturnType(this._sendOrig.binding);
}
@Override
public void computeConversion(Scope scope, TypeBinding runtimeTimeType, TypeBinding compileTimeType) {
if ( this._sendOrig.binding instanceof SyntheticBaseCallSurrogate
&& this._sendOrig == this._wrappee // otherwise wrappee contains conversion
&& this.resolvedType.isBaseType()
&& this.resolvedType != TypeBinding.VOID)
{
ReferenceBinding boxType = (ReferenceBinding) scope.getType(AstGenerator.boxTypeName((BaseTypeBinding) this.resolvedType), 3);
this._sendOrig.valueCast = boxType; // triggers insertion of checkcast to boxType
compileTimeType = boxType; // triggers unboxing conversion
}
super.computeConversion(scope, runtimeTimeType, compileTimeType);
}
private MethodDeclaration getEnclosingCallinMethod(Scope scope) {
if (this.enclosingCallinMethod == null)
this.enclosingCallinMethod = findEnclosingCallinMethod(scope, this);
return this.enclosingCallinMethod;
}
public static MethodDeclaration findEnclosingCallinMethod(Scope scope, ASTNode errorLocation) { // errorLocation == null => don't report
AbstractMethodDeclaration methodDecl = null;
MethodScope methodScope = scope.methodScope();
while (methodScope != null) {
if (methodScope.referenceContext() instanceof AbstractMethodDeclaration) {
methodDecl = (AbstractMethodDeclaration) methodScope.referenceContext();
if ((methodDecl.modifiers & ExtraCompilerModifiers.AccCallin) != 0)
return (MethodDeclaration) methodDecl;
}
methodScope = methodScope.parent.methodScope();
}
if (errorLocation != null) {
if (methodDecl == null) {
scope.problemReporter().baseCallOutsideMethod(errorLocation);
} else {
scope.problemReporter().basecallInRegularMethod(errorLocation, methodDecl);
}
}
return null;
}
@Override
public void traverse(ASTVisitor visitor, BlockScope scope)
{
if(visitor.visit(this, scope))
{
this._wrappee.traverse(visitor, scope);
}
visitor.endVisit(this, scope);
}
public MessageSend getMessageSend()
{
return this._sendOrig;
}
@Override
public ProblemReporterWrapper create(ProblemReporter wrappee)
{
return new BaseCallProblemReporterWrapper(wrappee, this);
}
@Override
public StringBuffer printExpression(int indent, StringBuffer output) {
char[] selectorSave = "<missing>".toCharArray(); //$NON-NLS-1$
Expression[] argsSave= null;
try {
if (this._sendOrig != null) {
selectorSave = this._sendOrig.selector;
this._sendOrig.selector = this.sourceSelector;
argsSave = this._sendOrig.arguments;
if (hasBeenTransformed(this)) // signals presence of superAccess flag
this._sendOrig.arguments = MethodSignatureEnhancer.retrenchBasecallArguments(argsSave, hasBeenTransformed(this._sendOrig), this._weavingScheme);
return this._sendOrig.printExpression(indent, output);
}
} finally {
if (selectorSave != null)
this._sendOrig.selector = selectorSave;
if (argsSave != null)
this._sendOrig.arguments = argsSave;
}
return super.printExpression(indent, output);
}
private static boolean hasBeenTransformed(Expression expr) {
return (expr.bits & ASTNode.HasBeenTransformed) != 0;
}
@Override
public boolean statementExpression() {
return ((this.bits & ASTNode.ParenthesizedMASK) == 0);
}
/**
* Under OTDRE detect this structure:
* <ul>
* <li>static callin method
* <li>containing a lambda
* <li>containing a base call
* </ul>
* In this structure the base call (_OT$callNext()) needs to tunnel the call target
* (team instance) through yet another synthetic argument.
*/
public static void lambdaCapture(LambdaExpression lambda, BlockScope currentScope) {
if (currentScope.compilerOptions().weavingScheme != WeavingScheme.OTDRE)
return ;
MethodScope methodScope = currentScope.methodScope();
if (methodScope == null || methodScope.extraSyntheticArguments == null)
return;
SyntheticArgumentBinding[] extraSyntheticArguments = methodScope.extraSyntheticArguments;
ASTVisitor findBaseCalls = new ASTVisitor() {
@Override
public boolean visit(BaseCallMessageSend messageSend, BlockScope scope) {
TypeBinding receiverType = messageSend._receiver.resolvedType;
if (receiverType != null && receiverType.isValidBinding()) {
for (SyntheticArgumentBinding synthArg : extraSyntheticArguments)
if (TypeBinding.equalsEquals(synthArg.type, receiverType))
lambda.addSyntheticArgument(synthArg);
}
return super.visit(messageSend, scope);
}
};
lambda.body.traverse(findBaseCalls, currentScope);
}
}