| /********************************************************************** |
| * This file is part of "Object Teams Dynamic Runtime Environment" |
| * |
| * Copyright 2009, 2014 Oliver Frank and others. |
| * |
| * 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: |
| * 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"; |
| public 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)); |
| } |
| } |
| } |
| } |