Commit 399e767e authored by Lubomir Bulej's avatar Lubomir Bulej

Add classes for handle-based analysis method invocation

parent 34640f59
package ch.usi.dag.shvm;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.nio.ByteBuffer;
import ch.usi.dag.dislreserver.shadow.ShadowObjectTable;
import ch.usi.dag.shvm.ArgumentUnmarshaller.UnmarshallerException;
/**
* Represents an analysis method. The analysis method can be either static, or
* bound to a {@ink RemoteAnalysis} instance. When creating an instance, the
* parameters of the analysis method are checked to conform to the Shadow VM
* requirements. This is done once per analysis method registration, not for
* every invocation. Also, a collection of unmarshallers corresponding to the
* method parameter types is create, which is then used to parse method
* invocation arguments from raw invocation data sent by the client.
*
* @author Lubomir Bulej <bulej@d3s.mff.cuni.cz>
*/
final class AnalysisMethod {
/**
* The reflective variant of the analysis method. The analysis method can be
* static.
*/
private final Method __method;
/**
* Method handle that allows invoking the analysis method in an efficient
* way. The method handle must be a <strong>spreader</strong> so that it can
* accept an array of objects as its arguments.
*/
private final MethodHandle __spreaderHandle;
/**
* Object responsible for unmarshalling analysis method invocation arguments
* from a byte buffer.
*/
private final ArgumentUnmarshaller __unmarshaller;
//
private AnalysisMethod (
final Method method, final MethodHandle spreaderHandle,
final ArgumentUnmarshaller unmarshaller
) {
__method = method;
__spreaderHandle = spreaderHandle;
__unmarshaller = unmarshaller;
}
public static AnalysisMethod create (
final Method method, final Object receiver,
final ArgumentUnmarshaller unmarshaller
) throws IllegalAccessException {
final MethodHandle spreader = __methodHandle (method, receiver).asSpreader (
Object [].class, method.getParameterCount ()
);
return new AnalysisMethod (method, spreader, unmarshaller);
}
private static MethodHandle __methodHandle (
final Method method, final Object receiver
) throws IllegalAccessException {
final MethodHandle result = MethodHandles.lookup ().unreflect (method);
if (Modifier.isStatic (method.getModifiers ())) {
return result;
} else {
return result.bindTo (receiver);
}
}
public Invocation unmarshalInvocation (
final ByteBuffer buffer, final ShadowObjectTable shadowObjects
) throws UnmarshallerException {
return new Invocation (
__unmarshaller.unmarshalArguments (buffer, shadowObjects)
);
}
//
@Override
public String toString () {
return String.format (
"%s.%s()", __method.getDeclaringClass ().getName (), __method.getName ()
);
}
//
/**
* Represents an invocation of an analysis method. Specifically, this class
* couples an analysis method with invocation arguments and allows it to be
* invoked.
*/
final class Invocation {
private final Object [] __args;
private Invocation (final Object [] args) {
__args = args;
}
/**
* Invokes the associated analysis method. Any exceptions that may occur
* during execution need to be caught upstream.
*/
public void invoke () throws Throwable {
__spreaderHandle.invokeExact (__args);
}
@Override
public String toString () {
return AnalysisMethod.this.toString ();
}
}
}
\ No newline at end of file
package ch.usi.dag.shvm;
import java.util.Arrays;
import ch.usi.dag.util.logging.Logger;
/**
* A registry of {@link AnalysisMethod} instances. Provides support for
* registration and lookup of analysis methods using a numeric identifier.
* <p>
* The registry is not thread safe, therefore it should only be used from a
* single thread (typically the server message processing thread).
*
* @author Lubomir Bulej <bulej@d3s.mff.cuni.cz>
*/
final class AnalysisMethodRegistry {
/**
* Analysis methods indexed by analysis method id.
*/
private AnalysisMethod [] __methods = new AnalysisMethod [8];
private final Logger __log;
//
AnalysisMethodRegistry (final SHVMContext context) {
__log = context.log ();
}
AnalysisMethod lookup (final short methodId) {
try {
return __methods [methodId];
} catch (final ArrayIndexOutOfBoundsException e) {
__log.error ("invalid analysis method id: %d", methodId);
}
return null;
}
private void __ensureCapacity (final int capacity) {
if (__methods.length < capacity) {
// Align capacity to multiples of 8
final int alignedCapacity = (capacity + 7) & (-1 << 3);
__methods = Arrays.copyOf (__methods, alignedCapacity);
}
}
public void register (
final short methodId, final AnalysisMethod method
) {
__ensureCapacity (methodId + 8);
final AnalysisMethod previousMethod = __methods [methodId];
if (previousMethod != null) {
__log.warn (
"analysis method %d already registered: %s",
methodId, previousMethod
);
}
__methods [methodId] = method;
}
}
package ch.usi.dag.shvm;
import static java.util.stream.Collectors.toList;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import ch.usi.dag.dislreserver.remoteanalysis.RemoteAnalysis;
import ch.usi.dag.dislreserver.shadow.ShadowObject;
import ch.usi.dag.shvm.ArgumentUnmarshaller.UnmarshallerException;
/**
* A registry of analysis class instances responsible for resolving analysis
* method names to {@link AnalysisMethod} instances. To this end, the class
* loads and instantiates analysis classes containing analysis methods and binds
* the analysis class instance as a receiver to non-static analysis methods.
* Only one analysis class instance is created.
* <p>
* The registry is not thread safe, therefore it should only be used from a
* single thread (typically the server message processing thread).
*
* @author Lubomir Bulej <bulej@d3s.mff.cuni.cz>
*/
final class AnalysisRegistry {
private static final String __DELIMITER__ = ".";
//
/**
* Analysis class instances corresponding to class names. Thread unsafe.
*/
private final Map <String, RemoteAnalysis> __analysesByClass = new HashMap <> ();
private final SHVMContext __context;
//
AnalysisRegistry (final SHVMContext context) {
__context = context;
}
/**
* Resolves a fully qualified analysis method name (without signature) into
* an instance of {@link AnalysisMethod} bound to the analysis instance (for
* non-static methods).
*
* @param methodQualifier
* Fully qualified name of the method (containing class name).
* @return An {@link AnalysisMethod} instance corresponding to the given
* name.
* @throws ResolveException
* If the method could not be resolved, which can happen if the
* qualifier is invalid, if there are no matching methods, or if
* there are multiple matching methods.
* @throws UnmarshallerException
* If an invocation argument unmarshaller cannot be created for the
* method.
* @throws IllegalAccessException
* If a method handle cannot be created for the method.
*/
public AnalysisMethod resolveMethod (
final String methodQualifier
) throws ResolveException, IllegalAccessException, UnmarshallerException {
// Split qualifier and check that we have both class and method names.
final int splitIndex = methodQualifier.lastIndexOf (__DELIMITER__);
final String className = methodQualifier.substring (0, Math.max (0, splitIndex));
final String methodName = methodQualifier.substring (splitIndex + 1);
if (className.isEmpty () || methodName.isEmpty ()) {
throw new ResolveException (
"invalid qualifier: %s", methodQualifier
);
}
// Get/create analysis. No mapping is created if instantiation fails.
final RemoteAnalysis analysis = __analysesByClass.computeIfAbsent (
className, this::__analysisForClassName
);
if (analysis == null) {
throw new ResolveException ("failed to instantiate %s", className);
}
// Find analysis method/complain if there is no (or ambiguous) match.
final List <Method> methods = __findMatchingMethods (methodName, analysis.getClass ());
if (methods.size () == 1) {
final Method analysisMethod = methods.get (0);
return AnalysisMethod.create (
analysisMethod, analysis,
ArgumentUnmarshaller.forMethod (analysisMethod)
);
} else if (methods.size () > 1) {
throw new ResolveException (
"multiple matching methods in %s", className
);
} else {
throw new ResolveException (
"no matching methods in %s", className
);
}
}
private RemoteAnalysis __analysisForClassName (final String className) {
try {
return Class.forName (className)
.asSubclass (RemoteAnalysis.class)
.newInstance ();
} catch (final ReflectiveOperationException e) {
__context.log ().error (
"failed to instantiate analysis %s: %s",
className, e.getMessage ()
);
return null;
}
}
/**
* Finds all methods matching the given name in the given class.
*/
private static List <Method> __findMatchingMethods (
final String methodName, final Class <?> targetClass
) {
return Arrays.stream (targetClass.getMethods ())
.filter (m -> m.getName ().equals (methodName))
.collect (toList ());
}
//
/**
* Notifies all analyzes that an object has been garbage collected.
*/
public void notifyAnalysesOnObjectFree (final ShadowObject shadowObject) {
for (final RemoteAnalysis analysis : __analysesByClass.values ()) {
__safeInvokeObjectFree (analysis, shadowObject);
}
}
private void __safeInvokeObjectFree (
final RemoteAnalysis analysis, final ShadowObject shadowObject
) {
try {
analysis.objectFree (shadowObject);
} catch (final Throwable e) {
// Log exceptions, but do not propagate them.
__context.log ().error (
e, "exception in %s.objectFree()",
analysis.getClass ().getName ()
);
}
}
//
/**
* Notifies all analyzes the application VM has terminated.
*/
public void notifyAnalysesOnVmExit () {
for (final RemoteAnalysis analysis : __analysesByClass.values ()) {
__safeInvokeAtExit (analysis);
}
}
private void __safeInvokeAtExit (final RemoteAnalysis analysis) {
try {
analysis.atExit ();
} catch (final Exception e) {
// Log exceptions, but do not propagate them.
__context.log ().error (
e, "exception in %s.atExit()",
analysis.getClass ().getName ()
);
}
}
//
@SuppressWarnings ("serial")
static final class ResolveException extends Exception {
public ResolveException (final String format, final Object... args) {
super (String.format (format, args));
}
public ResolveException (final Throwable cause, final String format,final Object... args) {
super (String.format (format, args), cause);
}
}
}
package ch.usi.dag.shvm;
import java.lang.reflect.Method;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import ch.usi.dag.dislreserver.shadow.ShadowObject;
import ch.usi.dag.dislreserver.shadow.ShadowObjectTable;
/**
* Class responsible for unmarshaling analysis method arguments from a
* {@link ByteBuffer}. Instances for a given reflective {@link Method} can be
* created using the {@link #forMethod(Method)} static factory method.
*
* @author Lubomir Bulej
*/
final class ArgumentUnmarshaller {
/** Umarshallers corresponding to parameter types. */
private final TypeUnmarshaller [] __typeUnmarshallers;
/** The length of the marshaled arguments in bytes. */
private final int __argumentByteCount;
//
private ArgumentUnmarshaller (
final TypeUnmarshaller [] typeUnmarshallers
) {
__typeUnmarshallers = typeUnmarshallers;
__argumentByteCount = __argumentByteCount (__typeUnmarshallers);
}
private static int __argumentByteCount (final TypeUnmarshaller [] unmarshallers) {
// We assume that all unmarshallers are non-null.
return Arrays.stream (unmarshallers).mapToInt (TypeUnmarshaller::size).sum ();
}
//
public Object [] unmarshalArguments (
final ByteBuffer buffer, final ShadowObjectTable shadowObjects
) throws UnmarshallerException {
//
// Read the length of the argument data and check that it matches the
// expected amount of data to be unmarshalled. Then unmarshal individual
// arguments using type-specific unmarshallers.
//
final short argsLength = buffer.getShort ();
if (argsLength != __argumentByteCount) {
throw new UnmarshallerException (
"expected %d but found %d bytes of argument data",
__argumentByteCount, argsLength
);
}
return __unmarshalArguments (buffer, shadowObjects, __typeUnmarshallers);
}
private Object [] __unmarshalArguments (
final ByteBuffer buffer,
final ShadowObjectTable shadowObjects,
final TypeUnmarshaller [] unmarshallers
) {
final int argCount = unmarshallers.length;
final Object [] result = new Object [argCount];
for (int argIndex = 0; argIndex < argCount; argIndex++) {
result [argIndex] = unmarshallers [argIndex].readFrom (
buffer, shadowObjects
);
}
return result;
}
//
public static ArgumentUnmarshaller forMethod (
final Method method
) throws UnmarshallerException {
return new ArgumentUnmarshaller (
__argumentUnmarshallers (method.getParameterTypes ())
);
}
/**
* Creates an array of {@link TypeUnmarshaller} instances corresponding to
* method parameter types. This array is then used to unmarshal method
* arguments from a data input stream.
*
* @throws UnmarshallerException
* If any of the parameter types is not supported by Shadow VM. This
* allows to make this check once during analysis method
* registration.
*/
private static TypeUnmarshaller [] __argumentUnmarshallers (
final Class <?> [] parameterTypes
) throws UnmarshallerException {
final int parameterCount = parameterTypes.length;
final TypeUnmarshaller [] result = new TypeUnmarshaller [parameterCount];
for (int i = 0; i < parameterCount; i++) {
final Class <?> type = parameterTypes [i];
final TypeUnmarshaller unmarshaller = TypeUnmarshaller.forType (type);
if (unmarshaller != null) {
result [i] = unmarshaller;
} else {
throw new UnmarshallerException ("unsupported data type: %s", type.getName ());
}
}
return result;
}
//
enum TypeUnmarshaller {
BOOLEAN (boolean.class, Byte.BYTES) {
@Override
Object readFrom (final ByteBuffer buffer, final ShadowObjectTable shadowObjects) {
return buffer.get () != 0;
}
},
CHAR (char.class, Character.BYTES) {
@Override
Object readFrom (final ByteBuffer buffer, final ShadowObjectTable shadowObjects) {
return buffer.getChar ();
}
},
BYTE (byte.class, Byte.BYTES) {
@Override
Object readFrom (final ByteBuffer buffer, final ShadowObjectTable shadowObjects) {
return buffer.get ();
}
},
SHORT (short.class, Short.BYTES) {
@Override
Object readFrom (final ByteBuffer buffer, final ShadowObjectTable shadowObjects) {
return buffer.getShort ();
}
},
INT (int.class, Integer.BYTES) {
@Override
Object readFrom (final ByteBuffer buffer, final ShadowObjectTable shadowObjects) {
return buffer.getInt ();
}
},
LONG (long.class, Long.BYTES) {
@Override
Object readFrom (final ByteBuffer buffer, final ShadowObjectTable shadowObjects) {
return buffer.getLong ();
}
},
FLOAT (float.class, Float.BYTES) {
@Override
Object readFrom (final ByteBuffer buffer, final ShadowObjectTable shadowObjects) {
return buffer.getFloat ();
}
},
DOUBLE (double.class, Double.BYTES) {
@Override
Object readFrom (final ByteBuffer buffer, final ShadowObjectTable shadowObjects) {
return buffer.getDouble ();
}
},
SHADOW (ShadowObject.class, Long.BYTES) {
@Override
Object readFrom (final ByteBuffer buffer, final ShadowObjectTable shadowObjects) {
// Convert object tags to ShadowObject instances.
return shadowObjects.get (buffer.getLong ());
}
};
//
private final Class <?> __type;
private final int __bytes;
TypeUnmarshaller (final Class <?> type, final int bytes) {
__type = type;
__bytes = bytes;
}
public int size () {
return __bytes;
}
boolean isPrimitive () {
return __type.isPrimitive ();
}
abstract Object readFrom (
ByteBuffer buffer, ShadowObjectTable shadowObjects
) throws BufferUnderflowException;
//
@SuppressWarnings ("serial")
static final Map <Class <?>, TypeUnmarshaller> __primitives = new HashMap <Class <?>, TypeUnmarshaller> () {{
for (final TypeUnmarshaller u : TypeUnmarshaller.values ()) {
if (u.isPrimitive ()) {
put (u.__type, u);
}
}
}};
static TypeUnmarshaller forType (final Class <?> type) {
if (type.isPrimitive ()) {
return Objects.requireNonNull (__primitives.get (type));
}
if (SHADOW.__type.isAssignableFrom (type)) {
return SHADOW;
}
return null;
}
}
//
@SuppressWarnings ("serial")
static final class UnmarshallerException extends Exception {
public UnmarshallerException (final String format, final Object... args) {
super (String.format (format, args));
}
public UnmarshallerException (final Throwable cause, final String format,final Object... args) {
super (String.format (format, args), cause);
}
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment