blob: e10550935596f39aa1e59d22ccae26d06bb6d363 [file] [log] [blame]
/**********************************************************************
* This file is part of "Object Teams Dynamic Runtime Environment"
*
* Copyright 2009, 2014 Oliver Frank and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Please visit http://www.eclipse.org/objectteams for updates and contact.
*
* Contributors:
* Oliver Frank - Initial API and implementation
* Stephan Herrmann - Initial API and implementation
**********************************************************************/
package org.eclipse.objectteams.otredyn.bytecode.asm;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.objectteams.otredyn.bytecode.AbstractBoundClass;
import org.eclipse.objectteams.otredyn.bytecode.Binding;
import org.eclipse.objectteams.otredyn.bytecode.ClassRepository;
import org.eclipse.objectteams.otredyn.runtime.ClassIdentifierProviderFactory;
import org.eclipse.objectteams.otredyn.runtime.IBinding;
import org.eclipse.objectteams.otredyn.runtime.IClassIdentifierProvider;
import org.objectweb.asm.Attribute;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.Label;
/**
* This class contains all classes representing OT/J class file attributes
* @author Oliver Frank
*/
public abstract class Attributes {
protected final static String ATTRIBUTE_OT_DYN_CALLIN_BINDINGS="OTDynCallinBindings";
protected final static String ATTRIBUTE_ROLE_BASE_BINDINGS = "CallinRoleBaseBindings";
protected final static String ATTRIBUTE_CALLIN_PRECEDENCE = "CallinPrecedence";
protected final static String ATTRIBUTE_OT_CLASS_FLAGS = "OTClassFlags";
protected final static String ATTRIBUTE_OT_SPECIAL_ACCESS = "OTSpecialAccess";
public final static String ATTRIBUTE_OT_COMPILER_VERSION = "OTCompilerVersion";
public static final int OTDRE_FLAG = 0x8000; // high bit in OTCompilerVersion
protected final static Attribute[] attributes = {
new CallinBindingsAttribute(0),
new RoleBaseBindingsAttribute(0),
new CallinPrecedenceAttribute(0),
new OTClassFlagsAttribute(0),
new OTSpecialAccessAttribute(),
new OTCompilerVersion(0)
};
protected static class OTCompilerVersion extends Attribute {
private int version;
protected OTCompilerVersion(int version) {
super(ATTRIBUTE_OT_COMPILER_VERSION);
this.version = version;
}
@Override
protected Attribute read(ClassReader cr, int off, int len, char[] buf, int codeOff, Label[] labels) {
int encodedVersion = cr.readUnsignedShort(off);
if ((encodedVersion & OTDRE_FLAG) == 0)
throw new UnsupportedClassVersionError("OTDRE: Class "+cr.getClassName()+" was compiled for incompatible weaving target OTRE");
return new OTCompilerVersion(encodedVersion);
}
@Override public String toString() {
return this.type+' '+this.version;
}
}
protected static class CallinBindingsAttribute extends Attribute {
static final short COVARIANT_BASE_RETURN = 8;
static final short BASE_SUPER_CALL = 16;
/** Represents all base method bindings of one callin binding. */
protected static class MultiBinding {
private String roleClassName;
private String callinLabel;
private String baseClassName;
private String[] baseMethodNames;
private String[] baseMethodSignatures;
private String[] declaringBaseClassNames;
private int callinModifier;
private int[] callinIds;
private int[] baseFlags;
private boolean isHandleCovariantReturn;
private boolean requireBaseSuperCall;
MultiBinding(String roleName, String callinLabel,
String baseClassName,
String[] baseMethodNames, String[] baseMethodSignatures, String[] declaringBaseClassNames,
int callinModifier, int[] callinIds, int[] baseFlags, int flags)
{
this.roleClassName = roleName;
this.callinLabel = callinLabel;
this.baseClassName = baseClassName;
this.baseMethodNames = baseMethodNames;
this.baseMethodSignatures = baseMethodSignatures;
this.declaringBaseClassNames = declaringBaseClassNames;
this.callinModifier = callinModifier;
this.callinIds = callinIds;
this.baseFlags = baseFlags;
this.isHandleCovariantReturn = (flags & COVARIANT_BASE_RETURN) != 0;
this.requireBaseSuperCall = (flags & BASE_SUPER_CALL) != 0;
}
protected String getRoleClassName() {
return roleClassName;
}
protected String getBaseClassName() {
return baseClassName;
}
protected String[] getBaseMethodNames() {
return baseMethodNames;
}
protected String[] getBaseMethodSignatures() {
return baseMethodSignatures;
}
protected int getCallinModifier() {
return this.callinModifier;
}
protected int[] getCallinIds() {
return callinIds;
}
public int[] getBaseFlags() {
return baseFlags;
}
protected String getCallinLabel() {
return callinLabel;
}
public boolean isHandleCovariantReturn() {
return this. isHandleCovariantReturn;
}
public boolean requiresBaseSuperCall() {
return this.requireBaseSuperCall;
}
public String[] getDeclaringBaseClassName() {
return this.declaringBaseClassNames;
}
}
private MultiBinding[] bindings;
public CallinBindingsAttribute(int bindingsCount) {
super(ATTRIBUTE_OT_DYN_CALLIN_BINDINGS);
this.bindings = new MultiBinding[bindingsCount];
}
private void addBinding(int i, String roleName, String callinLabel,
String baseClassName,
String[] baseMethodNames, String[] baseMethodSignatures, String[] declaringBaseClassNames,
String callinModifierName, int[] callinIds, int[] baseFlags, int flags) {
int callinModifier = 0;
if ("before".equals(callinModifierName))
callinModifier = Binding.BEFORE;
else if ("after".equals(callinModifierName))
callinModifier = Binding.AFTER;
else
callinModifier = Binding.REPLACE;
this.bindings[i] = new MultiBinding(roleName, callinLabel,
baseClassName,
baseMethodNames, baseMethodSignatures, declaringBaseClassNames,
callinModifier, callinIds, baseFlags, flags);
}
@Override
protected Attribute read(ClassReader cr, int off, int len,
char[] buf, int codeOff, Label[] labels)
{
int bindingsCount = cr.readShort(off); off += 2;
CallinBindingsAttribute attr = new CallinBindingsAttribute(bindingsCount);
for (int i = 0; i < bindingsCount; i++) {
String roleName = cr.readUTF8(off, buf); off += 2;
String callinLabel = cr.readUTF8(off, buf); off += 2;
/* skip roleSelector, roleSignature */ off += 4;
String callinModifier = cr.readUTF8(off, buf); off += 2;
int flags = cr.readByte(off); off += 1;
String baseClassName = cr.readUTF8(off, buf); off += 2;
/* skip filename & lineNumber & lineOffset */ off += 6;
int baseMethodsCount = cr.readShort(off); off += 2;
String[] baseMethodNames = new String[baseMethodsCount];
String[] baseMethodSignatures = new String[baseMethodsCount];
String[] declaringBaseClassNames = new String[baseMethodsCount];
int[] callinIds = new int[baseMethodsCount];
int[] baseFlags = new int[baseMethodsCount];
for (int m = 0; m < baseMethodsCount; m++) {
baseMethodNames[m] = cr.readUTF8(off, buf); off += 2;
baseMethodSignatures[m] = cr.readUTF8(off, buf); off += 2;
declaringBaseClassNames[m] = cr.readUTF8(off, buf); off += 2;
callinIds[m] = cr.readInt(off); off += 4;
baseFlags[m] = cr.readByte(off); off++;
/* skip translationFlags */ off += 2;
}
attr.addBinding(i, roleName, callinLabel,
baseClassName,
baseMethodNames, baseMethodSignatures, declaringBaseClassNames,
callinModifier, callinIds, baseFlags, flags);
}
return attr;
}
public MultiBinding[] getBindings() {
return this.bindings;
}
@Override
public String toString() {
StringBuffer buf = new StringBuffer();
for (MultiBinding binding : this.bindings) {
buf.append(binding.getBaseClassName());
int[] callinIds = binding.getCallinIds();
String[] baseMethodNames = binding.getBaseMethodNames();
String[] baseMethodSignatures = binding.getBaseMethodSignatures();
for (int i=0; i<callinIds.length; i++) {
buf.append("\n\t{");
buf.append(callinIds[i]);
buf.append("} ");
buf.append(baseMethodNames[i]);
buf.append(baseMethodSignatures[i]);
}
}
return buf.toString();
}
}
protected static class RoleBaseBindingsAttribute extends Attribute {
String[] roles;
String[] bases;
protected RoleBaseBindingsAttribute(int elementCount) {
super(ATTRIBUTE_ROLE_BASE_BINDINGS);
roles = new String[elementCount];
bases = new String[elementCount];
}
@Override
protected Attribute read(ClassReader cr, int off, int len,
char[] buf, int codeOff, Label[] labels)
{
int elementCount = cr.readShort(off); off += 2;
RoleBaseBindingsAttribute attr = new RoleBaseBindingsAttribute(elementCount);
for (int i = 0; i < elementCount; i++) {
attr.roles[i] = cr.readUTF8(off, buf); off += 2;
attr.bases[i] = cr.readUTF8(off, buf); off += 2;
}
return attr;
}
@Override
public String toString() {
StringBuilder buf = new StringBuilder(this.type).append('\n');
for (int i = 0; i < roles.length; i++) {
buf.append('\t').append(roles[i]).append("->").append(bases[i]).append('\n');
}
return buf.toString();
}
}
protected static class CallinPrecedenceAttribute extends Attribute {
String[] labels;
public CallinPrecedenceAttribute(int elementCount) {
super(ATTRIBUTE_CALLIN_PRECEDENCE);
this.labels = new String[elementCount];
}
@Override
protected Attribute read(ClassReader cr, int off, int len,
char[] buf, int codeOff, Label[] labels)
{
int elementCount = cr.readShort(off); off += 2;
CallinPrecedenceAttribute attr = new CallinPrecedenceAttribute(elementCount);
for (int i = 0; i < elementCount; i++) {
attr.labels[i] = cr.readUTF8(off, buf); off += 2;
}
return attr;
}
}
protected static class OTClassFlagsAttribute extends Attribute {
int flags;
protected OTClassFlagsAttribute(int flags) {
super(ATTRIBUTE_OT_CLASS_FLAGS);
this.flags = flags;
}
@Override
protected Attribute read(ClassReader cr, int off, int len, char[] buf,
int codeOff, Label[] labels)
{
return new OTClassFlagsAttribute(cr.readUnsignedShort(off));
}
}
protected static class OTSpecialAccessAttribute extends Attribute {
class DecapsField {
String accessMode;
boolean isStatic;
@NonNull String baseclass, name, desc;
public int perTeamAccessId;
public DecapsField(@NonNull String baseclass, @NonNull String name, @NonNull String desc, int accessId, String accessMode, boolean isStatic) {
this.baseclass = baseclass;
this.name = name;
this.desc = desc;
this.perTeamAccessId = accessId;
this.accessMode = accessMode;
this.isStatic = isStatic;
}
}
class DecapsMethod {
@NonNull String[] weaveIntoClasses;
@NonNull String declaringClass, name, desc;
int perTeamAccessId;
boolean isStatic;
DecapsMethod(@NonNull String weaveIntoClasses, @NonNull String declaringClass, @NonNull String name, @NonNull String desc, int id, boolean isStatic) {
this.weaveIntoClasses = weaveIntoClasses.split(":");
this.declaringClass = declaringClass;
this.name = name;
this.desc = desc;
this.perTeamAccessId = id;
this.isStatic = isStatic;
}
}
private static final int DECAPSULATION_METHOD_ACCESS= 4; // kinds are disjoint from those used by the old OTRE
private static final int CALLOUT_FIELD_ACCESS = 5;
List<DecapsMethod> methods = new ArrayList<DecapsMethod>();
List<DecapsField> fields = new ArrayList<DecapsField>();
List<String> decapsulatedBaseClasses = new ArrayList<String>(); // not currently used, see AddInterfaceAdapter for brute force solution
protected OTSpecialAccessAttribute() {
super(ATTRIBUTE_OT_SPECIAL_ACCESS);
}
@Override
protected Attribute read(ClassReader cr, int off, int len, char[] buf,
int codeOff, Label[] labels)
{
OTSpecialAccessAttribute attr = new OTSpecialAccessAttribute();
int size = cr.readUnsignedShort(off); off+=2;
for (int i=0; i<size; i++) {
int kind = cr.readByte(off++);
switch (kind) {
case DECAPSULATION_METHOD_ACCESS:
attr.readMethodAccess(cr, off, buf); off+=8;
break;
case CALLOUT_FIELD_ACCESS:
attr.readFieldAccess(cr, off, buf); off+=9;
break;
default:
throw new IllegalStateException("Unexpected kind in OTSpecialAccess attribute: "+kind);
}
}
size = cr.readUnsignedShort(off); off+=2;
for (int i = 0; i < size; i++) {
String baseClass = cr.readUTF8(off, buf); off+=2;
int flag = cr.readByte(off++);
if (flag == 1)
decapsulatedBaseClasses.add(baseClass);
}
return attr;
}
private void readMethodAccess(ClassReader cr, int off, char[] buf) {
String className = cr.readUTF8(off, buf);
String encodedName = cr.readUTF8(off+2, buf);
String methodDesc = cr.readUTF8(off+4, buf);
int accessId = cr.readUnsignedShort(off+6);
boolean isStatic = false;
String declaringClass;
String methodName;
if (encodedName.charAt(0) == '<') {
// constructor
declaringClass = className;
methodName = encodedName;
isStatic = true; // use static accessor
} else {
int pos = encodedName.indexOf('?');
if (pos == -1) {
pos = encodedName.indexOf('!');
isStatic = true;
}
declaringClass = encodedName.substring(0, pos);
methodName = encodedName.substring(pos+1);
}
if (className != null && declaringClass != null && methodName != null && methodDesc != null) {
this.methods.add(new DecapsMethod(className, declaringClass, methodName, methodDesc, accessId, isStatic));
} else {
System.err.println("Class attribute has unexpected null value: "+className+":"+declaringClass+":"+methodName+":"+methodDesc);
}
}
private void readFieldAccess(ClassReader cr, int off, char[] buf) {
int accessId = cr.readUnsignedShort(off);
int flags = cr.readByte(off+2);
String className = cr.readUTF8(off+3, buf);
String fieldName = cr.readUTF8(off+5, buf);
String fieldDesc = cr.readUTF8(off+7, buf);
boolean isStatic = (flags & 2) != 0;
String accessMode = (flags & 1) == 1 ? "set" : "get";
if (className != null && fieldName != null && fieldDesc != null) {
this.fields.add(new DecapsField(className, fieldName, fieldDesc, accessId, accessMode, isStatic));
} else {
System.err.println("Class attribute has unexpected null value: "+className+":"+fieldName+":"+fieldDesc);
}
}
public void registerAt(AsmBoundClass clazz) {
ClassRepository repo = ClassRepository.getInstance();
IClassIdentifierProvider provider = ClassIdentifierProviderFactory.getClassIdentifierProvider();
for (DecapsMethod dMethod : this.methods) {
// FIXME(SH): the following may need adaptation for OT/Equinox or other multi-classloader settings:
// bypassing the identifier provider (we don't have a Class<?> yet):
// String boundClassIdentifier = provider.getBoundClassIdentifier(clazz, dMethod.baseclass);
AbstractBoundClass baseclass = repo.getBoundClass(dMethod.declaringClass, dMethod.declaringClass.replace('.', '/'), clazz.getClassLoader());
// register the target method:
baseclass.getMethod(dMethod.name, dMethod.desc, false/*covariantReturn*/, dMethod.isStatic);
clazz.recordAccessId(dMethod.perTeamAccessId);
clazz.addBinding(new Binding(clazz, dMethod.declaringClass, dMethod.name, dMethod.desc, dMethod.perTeamAccessId, IBinding.BindingType.METHOD_ACCESS));
}
for (DecapsField dField: this.fields) {
// FIXME(SH): the following may need adaptation for OT/Equinox or other multi-classloader settings:
// bypassing the identifier provider (we don't have a Class<?> yet):
// String boundClassIdentifier = provider.getBoundClassIdentifier(clazz, dMethod.baseclass);
AbstractBoundClass baseclass = repo.getBoundClass(dField.baseclass, dField.baseclass.replace('.', '/'), clazz.getClassLoader());
// register the target field:
baseclass.getField(dField.name, dField.desc);
clazz.recordAccessId(dField.perTeamAccessId);
clazz.addBinding(new Binding(clazz, dField.baseclass, dField.name, dField.desc, dField.perTeamAccessId, IBinding.BindingType.FIELD_ACCESS));
}
}
}
}