blob: c968d4b8a0751f331bfcdd671c617f03dc8d818a [file] [log] [blame]
/**
* This file is part of "Object Teams Development Tooling"-Software
*
* Copyright 2009, 2019 Stephan Herrmann
*
* 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.bytecode;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.jdt.core.compiler.CharOperation;
import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration;
import org.eclipse.jdt.internal.compiler.classfmt.ClassFileStruct;
import org.eclipse.jdt.internal.compiler.impl.CompilerOptions.WeavingScheme;
import org.eclipse.jdt.internal.compiler.lookup.Binding;
import org.eclipse.jdt.internal.compiler.lookup.ExtraCompilerModifiers;
import org.eclipse.jdt.internal.compiler.lookup.LookupEnvironment;
import org.eclipse.jdt.internal.compiler.lookup.MethodBinding;
import org.eclipse.jdt.internal.compiler.lookup.ProblemMethodBinding;
import org.eclipse.jdt.internal.compiler.lookup.ProblemReasons;
import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding;
import org.eclipse.jdt.internal.compiler.parser.TerminalTokens;
import org.eclipse.jdt.internal.compiler.util.Util;
import org.eclipse.objectteams.otdt.core.compiler.IOTConstants;
import org.eclipse.objectteams.otdt.core.exceptions.InternalCompilerError;
import org.eclipse.objectteams.otdt.internal.core.compiler.ast.CallinMappingDeclaration;
import org.eclipse.objectteams.otdt.internal.core.compiler.ast.MethodSpec;
import org.eclipse.objectteams.otdt.internal.core.compiler.control.ITranslationStates;
import org.eclipse.objectteams.otdt.internal.core.compiler.lookup.CallinCalloutBinding;
import org.eclipse.objectteams.otdt.internal.core.compiler.model.MethodModel;
import org.eclipse.objectteams.otdt.internal.core.compiler.model.ModelElement;
import org.eclipse.objectteams.otdt.internal.core.compiler.model.TeamModel;
/**
* Encodes all callin bindings of a team class.
* Structure:
* <pre>
* CallinBindings_attribute {
* u2 attribute_name_index;
* u4 attribute_length;
* u2 bindings_count;
* {
* u2 role_class_name_index_table; // sipmle name of the role type (without __OT__ prefix)
* u2 callin_name_index_table;
* u2 role_selector_name_index_table;
* u2 role_signature_name_index_table;
* u1 callin_flags;
* u2 callin_modifier_name_index_table;
* u2 base_class_index_table; // internal name: java/util/Map$Entry
* u2 file_name_index_table; // for SMAP
* u2 line_number; // for SMAP
* u2 line_offset; // for SMAP
* u2 base_methods_count;
* {
* u2 base_method_name_index_table;
* u2 base_method_signature_index_table;
* u4 callin_id;
* u1 base_flags;
* u2 translation_flags;
* } base_methods[base_methods_count];
* } bindings[bindings_count];
* }
* </pre>
*
* This attribute is inherited / copied / merged into all sub and tsub teams, as to advise
* the OTDRE to generate dispatch code into the team.
*
* No team declares callinIds that would conflict with any of its supers/tsupers
* (see TeamModel.nextCallinID and the methods operating on it).
*
* @author stephan
* @since 1.3.0M3
*/
public class OTDynCallinBindingsAttribute extends ListValueAttribute {
public static final char[] ATTRIBUTE_NAME = "OTDynCallinBindings".toCharArray(); //$NON-NLS-1$
// bits in 'flags'
public static final short STATIC_ROLE_METHOD = 1;
public static final short INHERITED = 4;
public static final short COVARIANT_BASE_RETURN = 8;
public static final short BASE_SUPER_CALL = 16;
private class Mapping {
char[] roleClassName, declaringRoleName, callinName, roleSelector, roleSignature, callinModifier, baseClassName, fileName;
int flags; // STATIC_ROLE_METHOD, INHERITED, COVARIANT_BASE_RETURN
int lineNumber, lineOffset;
BaseMethod[] baseMethods;
MethodBinding roleMethod;
Mapping(char[] roleClassName, char[] declaringRoleName, char[] callinName, char[] roleSelector, char[] roleSignature, char[] callinModifer, int flags, char[] baseClassName, int baseMethodCount, MethodBinding roleMethod)
{
this.roleClassName = roleClassName;
this.declaringRoleName = declaringRoleName;
this.callinName = callinName;
this.roleSelector = roleSelector;
this.roleSignature = roleSignature;
this.callinModifier = callinModifer;
this.flags = flags;
this.baseClassName = baseClassName;
this.baseMethods = new BaseMethod[baseMethodCount];
this.roleMethod = roleMethod;
}
void addBaseMethod(int i, char[] baseMethodName, char[] baseMethodSignature, char[] declaringBaseClassName, int callinID, int baseFlags, int translationFlags) {
this.baseMethods[i] = new BaseMethod(baseMethodName, baseMethodSignature, declaringBaseClassName, callinID, baseFlags, translationFlags);
}
public BaseMethod[] getBaseMethods() {
return this.baseMethods;
}
public int getAttributeSize() {
int s = 21; // 7 names (roleClassName callinName roleSelector roleSignature callinModifier baseClassName fileName)
// + 1 byte (flags) 3 shorts (lineNumber lineOffset baseMethodCount)
for (int i = 0; i < this.baseMethods.length; i++)
s += 13; // 3 names, 1 int (callinID) 1 byte (baseFlags) 1 short (translationFlags)
return s;
}
/**
* Compute from 'decl' and store info as needed for SMAP (JSR-045).
*/
void setSMAPinfo(CallinMappingDeclaration decl) {
this.fileName = getFileName(decl);
int[] lineEnds = decl.scope.referenceCompilationUnit().compilationResult().getLineSeparatorPositions();
this.lineNumber =
(short)Util.getLineNumber(decl.sourceStart, lineEnds, 0, lineEnds.length-1);
short lineEnd =
(short)Util.getLineNumber(decl.declarationSourceEnd, lineEnds, 0, lineEnds.length-1);
this.lineOffset = (short)(lineEnd - this.lineNumber);
}
/**
* Compute the name of the file containing the given callin mapping.
* Do consider packages but no projects or source folders.
*/
private char[] getFileName(CallinMappingDeclaration decl) {
CompilationUnitDeclaration compilationUnit = decl.scope.referenceCompilationUnit();
char[] fullName = compilationUnit.getFileName();
char[][] packageName = null;
if ( compilationUnit.currentPackage == null
|| compilationUnit.currentPackage.tokens.length == 0)
{ // default package, use last path component only
int pos = CharOperation.lastIndexOf('/', fullName);
if (pos == -1) // no '/'
return fullName;
return CharOperation.subarray(fullName, pos+1, -1);
}
packageName = compilationUnit.currentPackage.tokens;
char[][] components = CharOperation.splitOn('/', fullName);
// sometimes fullname (ie., compilationUnit.getFileName()) does not contain any path, just sourceunitname,
// which is due to the many different contexts calling new CompilationResult(fileName..)
int pos = CharOperation.lastIndexOf('/', fullName);
if (pos == -1)
{
return CharOperation.concatWith (packageName, fullName, '/');
}
//check whether components contains packageName:
if (components.length <= packageName.length)
throw new InternalCompilerError("too few path elements"); //$NON-NLS-1$
int start = components.length - (packageName.length + 1);
int end = components.length;
if (!CharOperation.equals(packageName,
CharOperation.subarray(components, start, end - 1)))
decl.scope.problemReporter().packageIsNotExpectedPackage(compilationUnit);
return CharOperation.concatWith(CharOperation.subarray(components, start, end), '/');
}
public void setSMAPInfo(char[] fileName, int lineNumber, int lineOffset) {
this.fileName = fileName;
this.lineNumber = lineNumber;
this.lineOffset = lineOffset;
}
@Override
public String toString() {
StringBuilder buf = new StringBuilder("Callin binding for "); //$NON-NLS-1$
buf.append(this.declaringRoleName);
buf.append('.').append(this.roleSelector).append(this.roleSignature);
return buf.toString();
}
}
/* Encodes the binding to one individual base method. */
private class BaseMethod {
static final int CALLIN = 1;
static final int STATIC = 2;
static final int FINAL = 4;
static final int PRIVATE = 8;
static final int PUBLIC = 16;
char[] baseMethodName, baseMethodSignature, declaringBaseClassName;
int callinID, baseFlags, translationFlags;
BaseMethod(char[] baseMethodName, char[] baseMethodSignature, char[] declaringBaseClassName, int callinID, int baseFlags, int translationFlags) {
this.baseMethodName = baseMethodName;
this.baseMethodSignature = baseMethodSignature;
this.declaringBaseClassName = declaringBaseClassName;
this.callinID = callinID;
this.baseFlags = baseFlags;
this.translationFlags = translationFlags;
}
}
private List<Mapping> mappings;
private TeamModel theTeam;
OTDynCallinBindingsAttribute(TeamModel theTeam) {
super(ATTRIBUTE_NAME, -1/*size pending*/, -1/*variable entry size*/);
this.theTeam = theTeam;
this.theTeam.addAttribute(this);
this.mappings = new ArrayList<Mapping>();
}
private OTDynCallinBindingsAttribute(TeamModel theTeam, List<Mapping> mappings) {
super(ATTRIBUTE_NAME, mappings.size(), -1/*variable entry size*/);
this.theTeam = theTeam;
this.mappings = mappings;
}
/**
* Read the attribute from byte code.
*
* @param reader
* @param readOffset
* @param constantPoolOffsets
*/
public OTDynCallinBindingsAttribute(ClassFileStruct reader, int readOffset, int[] constantPoolOffsets) {
super(ATTRIBUTE_NAME, -1/*size pending*/, -1/*variable entry size*/);
this._reader = reader;
this._readOffset = readOffset;
this._constantPoolOffsets = constantPoolOffsets;
this._count = consumeShort();
this.mappings = new ArrayList<Mapping>();
for (int i=0; i<this._count; i++)
this.mappings.add(readMapping());
}
@Override
protected int getAttributeSize() {
int s = 2; // entry count
for (Mapping mapping : this.mappings)
s += mapping.getAttributeSize();
return s;
}
void addMappings(char[] baseClassName, CallinMappingDeclaration callinDecl) {
MethodSpec roleSpec = callinDecl.roleMethodSpec;
int flags = 0;
if (callinDecl.roleMethodSpec.resolvedMethod.isStatic())
flags |= STATIC_ROLE_METHOD;
if (callinDecl.hasCovariantReturn())
flags |= COVARIANT_BASE_RETURN;
MethodSpec[] baseMethodSpecs = callinDecl.getBaseMethodSpecs();
Mapping mapping = new Mapping(callinDecl.scope.enclosingSourceType().sourceName(), // indeed: simple name
callinDecl.declaringRoleName(), callinDecl.name, roleSpec.selector, roleSpec.signature(WeavingScheme.OTDRE),
callinDecl.getCallinModifier(), flags,
baseClassName, baseMethodSpecs.length,
roleSpec.resolvedMethod);
for (int i=0; i<baseMethodSpecs.length; i++) {
MethodSpec baseSpec = baseMethodSpecs[i];
int baseFlags = 0;
if (baseSpec.isCallin())
baseFlags |= BaseMethod.CALLIN;
if (baseSpec.isStatic())
baseFlags |= BaseMethod.STATIC;
if (baseSpec.isFinal())
baseFlags |= BaseMethod.FINAL;
if (baseSpec.isPrivate())
baseFlags |= BaseMethod.PRIVATE;
if (baseSpec.isPublic())
baseFlags |= BaseMethod.PUBLIC;
mapping.addBaseMethod(i, baseSpec.codegenSeletor(), baseSpec.signature(WeavingScheme.OTDRE),
baseSpec.resolvedMethod.declaringClass.constantPoolName(),
baseSpec.getCallinId(this.theTeam, callinDecl.explicitName()),
baseFlags, baseSpec.getTranslationFlags());
}
mapping.setSMAPinfo(callinDecl);
this.mappings.add(mapping);
this._count = this.mappings.size();
}
public static void createOrMerge(TeamModel theTeam, char[] baseClassName, CallinMappingDeclaration[] mappingDecls) {
AbstractAttribute existingAttr = theTeam.getAttribute(ATTRIBUTE_NAME);
OTDynCallinBindingsAttribute theAttr = existingAttr != null
? (OTDynCallinBindingsAttribute)existingAttr
: new OTDynCallinBindingsAttribute(theTeam);
for (CallinMappingDeclaration callinDecl : mappingDecls)
theAttr.addMappings(baseClassName, callinDecl);
}
@Override
String toString(int i) {
Mapping mapping = this.mappings.get(i);
StringBuffer buf = new StringBuffer();
buf.append('\t');
buf.append(String.valueOf(mapping.declaringRoleName));
buf.append('.');
buf.append(String.valueOf(mapping.callinName));
buf.append(": "); //$NON-NLS-1$
buf.append(String.valueOf(mapping.roleSelector));
buf.append(String.valueOf(mapping.roleSignature));
buf.append(" <- "); //$NON-NLS-1$
buf.append(String.valueOf(mapping.callinModifier));
buf.append(' ');
buf.append(String.valueOf(mapping.baseClassName));
BaseMethod[] baseMethods = mapping.getBaseMethods();
for (int j = 0; j < baseMethods.length; j++) {
buf.append("\n\t\t"); //$NON-NLS-1$
buf.append(String.valueOf(baseMethods[j].declaringBaseClassName));
buf.append('.');
buf.append(String.valueOf(baseMethods[j].baseMethodName));
buf.append(String.valueOf(baseMethods[j].baseMethodSignature));
buf.append('{');
buf.append(baseMethods[j].callinID);
buf.append('}');
}
buf.append('\n');
return buf.toString();
}
@Override
void writeElementValue(int i) {
Mapping mapping = this.mappings.get(i);
writeName(mapping.roleClassName);
writeName(CharOperation.concat(mapping.declaringRoleName, mapping.callinName, '.'));
writeName(mapping.roleSelector);
writeName(mapping.roleSignature);
writeName(mapping.callinModifier);
int flags = mapping.flags;
// if mapping is from binary we already have the base-super flag in flags, for source we had to wait till after analyseCode():
if (mapping.roleMethod != null && MethodModel.hasCallinFlag(mapping.roleMethod, IOTConstants.CALLIN_FLAG_BASE_SUPER_CALL))
flags |= BASE_SUPER_CALL;
writeByte((byte) flags);
writeName(mapping.baseClassName);
writeName(mapping.fileName);
writeUnsignedShort(mapping.lineNumber);
writeUnsignedShort(mapping.lineOffset);
BaseMethod[] baseMethods = mapping.getBaseMethods();
writeUnsignedShort(baseMethods.length);
for (int j = 0; j < baseMethods.length; j++) {
writeName(baseMethods[j].baseMethodName);
writeName(baseMethods[j].baseMethodSignature);
writeName(baseMethods[j].declaringBaseClassName);
writeInt(baseMethods[j].callinID);
writeByte((byte)baseMethods[j].baseFlags);
writeUnsignedShort(baseMethods[j].translationFlags);
}
}
Mapping readMapping() {
char[] roleClassName = consumeName();
char[] callinName = consumeName();
char[] roleSelector = consumeName();
char[] roleSignature = consumeName();
char[] callinModifer = consumeName();
int flags = consumeByte();
char[] baseClassName = consumeName();
char[] fileName = consumeName();
int lineNumber = consumeShort();
int lineOffset = consumeShort();
int baseMethodCount = consumeShort();
int pos = CharOperation.lastIndexOf('.', callinName);
char[] declaringRoleName = CharOperation.subarray(callinName, 0, pos);
callinName = CharOperation.subarray(callinName, pos+1, -1);
Mapping result = new Mapping(roleClassName, declaringRoleName, callinName, roleSelector, roleSignature, callinModifer, flags, baseClassName, baseMethodCount, null);
result.setSMAPInfo(fileName, lineNumber, lineOffset);
for (int i=0; i<baseMethodCount; i++) {
char[] baseMethodName = consumeName();
char[] baseMethodSignature = consumeName();
char[] declaringBaseClass = consumeName();
int callinID = consumeInt();
int baseFlags = consumeByte();
int translationFlags = consumeShort();
result.addBaseMethod(i, baseMethodName, baseMethodSignature, declaringBaseClass, callinID, baseFlags, translationFlags);
}
return result;
}
/* (non-Javadoc)
* @see org.eclipse.objectteams.otdt.internal.core.compiler.bytecode.AbstractAttribute#evaluate(org.eclipse.jdt.internal.compiler.lookup.Binding, org.eclipse.jdt.internal.compiler.lookup.LookupEnvironment)
*/
@Override
public void evaluate(Binding binding, LookupEnvironment environment, char[][][] missingTypeNames) {
checkBindingMismatch(binding, ExtraCompilerModifiers.AccTeam);
if (((ReferenceBinding)binding).isTeam())
((ReferenceBinding)binding).getTeamModel().addAttribute(this);
}
// Evaluate CallinMethodMappingAttribute late, because we need our methods to be in place.
@Override
public void evaluateLateAttribute(ReferenceBinding teamBinding, int state)
{
if (state != ITranslationStates.STATE_FAULT_IN_TYPES)
return;
for (int i = 0; i < this.mappings.size(); i++)
createBinding(teamBinding, this.mappings.get(i), true);
}
public void createBindings(TeamModel teamModel, boolean recordLabels)
{
ReferenceBinding teamBinding = teamModel.getBinding();
for (int i = 0; i < this.mappings.size(); i++)
createBinding(teamBinding, this.mappings.get(i), recordLabels);
}
/**
* @param teamBinding where to add created bindings
* @param mapping the mapping to reconstruct
* @param recordLabels whether labelled callins should be recording in the team
*/
private static void createBinding(ReferenceBinding teamBinding, Mapping mapping, boolean recordLabels)
{
ReferenceBinding roleBinding = teamBinding.getMemberType(mapping.roleClassName).getRealClass();
CallinCalloutBinding result = null;
CallinCalloutBinding[] callinCallouts = roleBinding.callinCallouts;
if (callinCallouts != null) {
for (int i = 0; i < callinCallouts.length; i++) {
if (CharOperation.equals(mapping.callinName, callinCallouts[i].name))
{
// fill in details to existing binding:
result = callinCallouts[i];
result.callinModifier = encodeCallinModifier(mapping.callinModifier);
break;
}
}
}
if (result == null)
result = new CallinCalloutBinding(roleBinding,
mapping.callinName,
encodeCallinModifier(mapping.callinModifier));
BaseMethod[] mappingBaseMethods = mapping.baseMethods;
MethodBinding[] baseMethods = new MethodBinding[mappingBaseMethods.length];
ReferenceBinding currentType = roleBinding;
char[] roleSignature = mapping.roleSignature;
if (result.callinModifier == TerminalTokens.TokenNamereplace) {
// ignore generalized return by truncating the signature:
int closePos = CharOperation.indexOf(')', roleSignature);
if (closePos > -1)
roleSignature = CharOperation.subarray(roleSignature, 0, closePos+1);
}
roleMethod:
while (currentType != null) {
MethodBinding[] methods = currentType.getMethods(mapping.roleSelector);
for (int j = 0; j < methods.length; j++) {
if (CharOperation.prefixEquals(roleSignature, MethodSpec.signature(methods[j], WeavingScheme.OTDRE)))
{
result._roleMethodBinding = methods[j];
break roleMethod;
}
}
currentType = currentType.superclass();
}
if (result._roleMethodBinding == null)
throw new InternalCompilerError("role method specified in callin mapping does not exist "+mapping); //$NON-NLS-1$
int callinIdMax = 0;
mappingBaseMethods:
for (int i = 0; i < mappingBaseMethods.length; i++) {
BaseMethod bm = mappingBaseMethods[i];
callinIdMax = Math.max(callinIdMax, bm.callinID);
currentType = roleBinding.baseclass();
while (currentType != null) {
MethodBinding[] methods = currentType.getMethods(bm.baseMethodName);
for (int j = 0; j < methods.length; j++) {
if (CharOperation.equals(bm.baseMethodSignature, methods[j].signature())) // TODO(SH): enhancing? / _isCallin?
{
baseMethods[i] = methods[j];
if (recordLabels && mapping.callinName != null && mapping.callinName[0] != '<') {
teamBinding._teamModel.recordLabelledCallin(new String(mapping.callinName), methods[j], bm.callinID);
}
continue mappingBaseMethods;
}
}
currentType = currentType.superclass();
}
baseMethods[i]= new ProblemMethodBinding(bm.baseMethodName, null, roleBinding.baseclass(), ProblemReasons.NotFound);
}
result._baseMethods = baseMethods;
result.callinIdMax = callinIdMax;
teamBinding._teamModel.recordCallinId(callinIdMax);
result.copyInheritanceSrc = findTSuperBinding(mapping.callinName, roleBinding);
roleBinding.addCallinCallouts(new CallinCalloutBinding[]{result});
}
private static CallinCalloutBinding findTSuperBinding(char[] name, ReferenceBinding roleType) {
ReferenceBinding[] tsuperRoles = roleType.roleModel.getTSuperRoleBindings();
for (int i = tsuperRoles.length-1; i >= 0; i--) { // check highest prio first (which comes last in the array)
if (tsuperRoles[i].callinCallouts != null)
for (CallinCalloutBinding mapping : tsuperRoles[i].callinCallouts)
if (CharOperation.equals(mapping.name, name))
return mapping.copyInheritanceSrc != null ?
mapping.copyInheritanceSrc :
mapping;
}
return null;
}
private static int encodeCallinModifier(char[] modifierName) {
if (CharOperation.equals(modifierName, IOTConstants.NAME_REPLACE))
return TerminalTokens.TokenNamereplace;
if (CharOperation.equals(modifierName, IOTConstants.NAME_AFTER))
return TerminalTokens.TokenNameafter;
if (CharOperation.equals(modifierName, IOTConstants.NAME_BEFORE))
return TerminalTokens.TokenNamebefore;
throw new InternalCompilerError("invalid callin modifier in byte code"); //$NON-NLS-1$
}
public int getCallinIdMax() {
int max = -1;
for (Mapping mapping : this.mappings)
for (BaseMethod baseMethod : mapping.baseMethods)
max = Math.max(max, baseMethod.callinID);
return max;
}
/**
* Get a copy of this attribute with those bindings filtered out,
* that are overridden in the current team.
* @param teamBinding filter the attribute for this (sub)team
* @return a filtered copy of this attribute or null, if no mappings remain after filtering
*/
public OTDynCallinBindingsAttribute filteredCopy(ReferenceBinding teamBinding) {
List<Mapping> filteredMappings = new ArrayList<Mapping>();
nextMapping: for (Mapping mapping : this.mappings) {
if (mapping.callinName[0] != '<') { // only named callins can be overridden
ReferenceBinding roleBinding = teamBinding.getMemberType(mapping.roleClassName).getRealClass();
for (CallinCalloutBinding callinCallout : roleBinding.callinCallouts)
if (CharOperation.equals(callinCallout.name, mapping.callinName))
continue nextMapping;
}
// not found means not overridden
filteredMappings.add(mapping);
}
if (filteredMappings.size() == 0)
return null;
return new OTDynCallinBindingsAttribute(teamBinding._teamModel, filteredMappings);
}
@Override
public void merge(ModelElement model, AbstractAttribute other) {
List<Mapping> newMappings = ((OTDynCallinBindingsAttribute)other).mappings;
for (Mapping mapping : newMappings) {
if (!hasMapping(mapping)) {
this.mappings.add(mapping);
this._count ++;
}
}
}
private boolean hasMapping(Mapping mapping) {
for (Mapping current : this.mappings) {
if (current.callinName.equals(mapping.callinName)) // including generated callin names
return true;
}
return false;
}
}