Commit 31b2cc8b authored by Vít Kabele's avatar Vít Kabele

Sessions supported.

Addressing #1 and #3
parent d08cbcd9
/**
* Load class from byte array.
*
* This file is part of disl project
*
* Author: Vit Kabele <vit@kabele.me>
* Created on the 26/10/2018
*/
package ch.usi.dag.disl;
/**
* Load classes from byte array.
*/
class ByteArrayClassLoader extends ClassLoader {
/**
* Create new instance of the {@link ByteArrayClassLoader} class.
*/
ByteArrayClassLoader(){
super();
}
/**
* Returns an class definition from given byte array.
* @param name Class name.
* @param classBytes Class bytes.
* @param offset Offset in array.
* @param length Length of class data.
* @return Appropriate instance of Class
*/
Class<?> createClass(String name, byte[] classBytes, int offset, int length){
return defineClass (name, classBytes, offset, length);
}
}
package ch.usi.dag.disl;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Collection;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Properties;
import java.util.Set;
import java.util.stream.Collectors;
import ch.usi.dag.disl.classparser.DislClassesImpl;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.MethodNode;
import ch.usi.dag.disl.classparser.DislClasses;
import ch.usi.dag.disl.classparser.DislClassesImpl;
import ch.usi.dag.disl.classparser.IsolatedDislClasses;
import ch.usi.dag.disl.exception.DiSLException;
import ch.usi.dag.disl.exception.DiSLInMethodException;
import ch.usi.dag.disl.localvar.SyntheticLocalVar;
......@@ -38,6 +19,16 @@ import ch.usi.dag.disl.util.Logging;
import ch.usi.dag.disl.weaver.Weaver;
import ch.usi.dag.util.asm.ClassNodeHelper;
import ch.usi.dag.util.logging.Logger;
import javafx.util.Pair;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.MethodNode;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;
/**
......@@ -157,6 +148,48 @@ public final class DiSL {
}
/**
* Initialize new instance of the {@link DiSL} class, using received instrumentaion jars.
* @param properties
* @param instrumentations
* @return
* @throws DiSLException
*/
public static DiSL init(
final Properties properties,
final List<byte[]> instrumentations
) throws DiSLException
{
final Set <CodeOption> codeOptions = __codeOptionsFrom (
Objects.requireNonNull (properties)
);
// Stream cannot be used here, since the constructor throws an exception
final List<InstrumentationJar> instrumentationJars = new ArrayList<> ();
for (byte[] instrumentation : instrumentations) {
InstrumentationJar jar = new InstrumentationJar (instrumentation);
instrumentationJars.add (jar);
}
final List<ClassNode> classNodes = instrumentationJars
.stream ()
.flatMap (i -> i.getDislClasses ().stream ())
.collect (Collectors.toList ());
final List<Pair<String, byte[]>> transformerList = instrumentationJars
.stream ()
.flatMap ( i -> i.getTransformers ().stream () )
.collect (Collectors.toList ());
final DislClasses dislClasses = new IsolatedDislClasses (codeOptions, classNodes);
final Transformers transformers = new IsolatedTransformers ( transformerList );
final Set<Scope> excludedScopes = ExclusionSetFactory.prepare ( instrumentationJars );
return new DiSL (codeOptions, transformers, excludedScopes, dislClasses);
}
/**
* Derives code options from global properties. This is a transitional
* compatibility method for the transition to per-request code options.
......
......@@ -46,5 +46,22 @@ final class DiSLManifest {
}
/**
* Transformer classes are loaded from both related attributes.
* @return
*/
List<String> getTransformersClasses(){
String transformersClasses = manifest.getMainAttributes ().getValue (DISL_TRANSFORMER);
transformersClasses += SEPARATOR;
transformersClasses += manifest.getMainAttributes ().getValue (DISL_TRANSFORMERS);
Pattern splitter = Pattern.compile (SEPARATOR);
return splitter.splitAsStream (transformersClasses)
.map (String::trim)
.filter (s -> !s.isEmpty ())
.collect(Collectors.toList());
}
}
/**
* This file is part of disl project
* Author: Vit Kabele <vit@kabele.me>
* Created on the 26/10/2018
*/
package ch.usi.dag.disl;
import ch.usi.dag.disl.scope.Scope;
import ch.usi.dag.disl.scope.ScopeMatcher;
import ch.usi.dag.disl.util.JavaNames;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.util.*;
import java.util.jar.JarEntry;
import java.util.stream.Collectors;
public abstract class ExclusionSetFactory {
public static Set <Scope> prepare (List<InstrumentationJar> instrumentationJars) {
try {
final Set <Scope> result = hardCodedExclusions ();
instrumentationJars
.stream ()
.map (ExclusionSetFactory::instrumentationExclusions)
.map (result::addAll);
result.addAll (userDefinedExclusions ());
return result;
} catch (final Exception e) {
e.printStackTrace ();
throw new InitializationException (
e, "failed to initialize exclusion list"
);
}
}
private static List<Scope> instrumentationExclusions(InstrumentationJar jar){
return jar
.stream()
.filter(s -> s != null && !s.isDirectory ())
.map (JarEntry::getName)
.filter (JavaNames::hasClassFileExtension)
.map (entryName -> {
final String className = JavaNames.internalToType (
JavaNames.stripClassFileExtension (entryName)
);
// exclude all methods of the class
return ScopeMatcher.forPattern (className + ".*");
})
.collect (Collectors.toList ());
}
private static final String __DISL_EXCLUSION_LIST__ = "disl.exclusionList";
private static Set <Scope> userDefinedExclusions () throws FileNotFoundException {
final String commentPrefix = "#";
final Set <Scope> result = new HashSet <> ();
//
// If a user specified a custom exclusion list, load it line by line,
// each line representing a single exclusion scope.
//
final String fileName = System.getProperty (__DISL_EXCLUSION_LIST__, "");
if (!fileName.isEmpty ()) {
final Scanner scanner = new Scanner (new FileInputStream (fileName));
while (scanner.hasNextLine ()) {
final String line = scanner.nextLine ().trim ();
if (!line.isEmpty () && !line.startsWith (commentPrefix)) {
result.add (ScopeMatcher.forPattern (line));
}
}
scanner.close ();
}
return result;
}
private static Set<Scope> hardCodedExclusions () {
final String [] exclusions = new String [] {
//
// Our classes.
//
"ch.usi.dag.dislagent.*.*" /* DiSL agent classes */,
"ch.usi.dag.disl.dynamicbypass.*.*" /* dynamic bypass classes */,
"ch.usi.dag.dislre.*.*" /* Shadow VM classes */,
//
// The following cause trouble when instrumented.
//
"sun.instrument.*.*" /* Sun instrumentation classes */,
"java.lang.Object.finalize" /* Object finalizer */
};
return Arrays.stream (exclusions)
.map (ScopeMatcher::forPattern)
.collect (Collectors.toCollection (HashSet::new));
}
}
......@@ -9,16 +9,21 @@
package ch.usi.dag.disl;
import ch.usi.dag.util.asm.ClassNodeHelper;
import javafx.util.Pair;
import org.objectweb.asm.tree.ClassNode;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;
import java.util.jar.Manifest;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
/**
......@@ -37,46 +42,161 @@ final class InstrumentationJar {
* @param JarBytes Byte array representing jar file.
* @throws IOException Error while reading, missing manifest or missing one of the mandatory fields.
*/
InstrumentationJar(byte[] JarBytes) throws IOException, ManifestException {
InstrumentationJar(byte[] JarBytes) throws ManifestException {
this.JarBytes = JarBytes;
JarInputStream jis = new JarInputStream ( new ByteArrayInputStream (JarBytes), false );
try(JarInputStream jis = new JarInputStream ( new ByteArrayInputStream (JarBytes), false ))
{
Manifest manifest = jis.getManifest ();
Manifest manifest = jis.getManifest ();
if (manifest == null)
throw new InitializationException ("Given Jar file has no manifest included.");
if (manifest == null)
throw new IOException ("Given Jar file has no manifest included.");
diSLManifest = new DiSLManifest (manifest);
diSLManifest = new DiSLManifest (manifest);
} catch (final IOException e){
throw new InitializationException (
e,
"Failed to load instrumentation jar"
);
}
}
/**
* Returns the Class no
* Returns the Class nodes of the DiSL classes.
* @return ClassNodes of the DiSL classes specified in manifest file
* @throws IOException On input error
* @throws InitializationException On input error
*/
List<ClassNode> getDislClasses() throws IOException {
List<ClassNode> getDislClasses() {
List<ClassNode> ClassNodes = new ArrayList<> ();
JarInputStream jis = new JarInputStream ( new ByteArrayInputStream (JarBytes));
List<String> dislClasses = diSLManifest.getDiSLClasses ().stream ().map( s -> s += ".class" ).collect(
Collectors.toList());
JarEntry je;
while((je = jis.getNextJarEntry ()) != null){
if(
!je.isDirectory () &&
dislClasses.contains (je.getName ())
){
ClassNodes.add ( ClassNodeHelper.SNIPPET.unmarshal (jis));
JarEntry je = null;
List<String> dislClasses = diSLManifest
.getDiSLClasses ()
.stream ()
.map( s -> s += ".class" )
.collect(Collectors.toList());
try(JarInputStream jis = new JarInputStream ( new ByteArrayInputStream (JarBytes) ))
{
while((je = jis.getNextJarEntry ()) != null){
if(
!je.isDirectory () &&
dislClasses.contains (je.getName ())
){
ClassNodes.add ( ClassNodeHelper.SNIPPET.unmarshal (jis));
}
}
} catch( final Exception e){
throw new InitializationException (
e,
"failed to load DiSL class %s",
(je == null) ? "" : je.getName ()
);
}
return ClassNodes;
}
/**
* Returns the transformer classes.
*
* @return The list of pairs [Class name, Classbytes]
*/
List<Pair<String,byte[]>> getTransformers(){
List<Pair<String,byte[]>> transformers = new ArrayList<> ();
JarEntry je = null;
List<String> t = diSLManifest
.getTransformersClasses ()
.stream ()
.map(s -> s += ".class")
.collect(Collectors.toList());
try(JarInputStream jis = new JarInputStream (new ByteArrayInputStream (JarBytes))){
while((je = jis.getNextJarEntry ()) != null){
byte[] classbytes = new byte[ (int)je.getSize () ];
if(!je.isDirectory () && t.contains (je.getName ())){
jis.read (classbytes, 0, (int)je.getSize ());
transformers.add (new Pair<> (je.getName (), classbytes));
}
}
} catch (final Exception e){
throw new InitializationException (
e,
"failed to load Transformer class %s",
(je == null) ? "" : je.getName ()
);
}
return transformers;
}
JarInputStream getJarInputStream(){
try {
return new JarInputStream (new ByteArrayInputStream (JarBytes));
} catch (Exception e){
// Exception cannot be raised here, since the JarBytes variable is checked in constructor.
}
return null;
}
Iterator iterator(){
try{
return new Iterator (
new JarInputStream (
new ByteArrayInputStream (
JarBytes
)
)
);
} catch (IOException e){}
return null;
}
Stream<JarEntry> stream(){
return StreamSupport.stream (
Spliterators.spliteratorUnknownSize (iterator (), Spliterator.ORDERED),
false
);
}
static class Iterator implements java.util.Iterator<JarEntry> {
private final JarInputStream jis;
Iterator(JarInputStream jis){
this.jis = jis;
}
@Override
public boolean hasNext () {
try {
return jis.available () > 0;
} catch (IOException e){}
return false;
}
@Override
public JarEntry next () {
try{
return jis.getNextJarEntry ();
} catch (IOException e){}
return null;
}
}
}
/**
* This file is part of disl project
* Author: Vit Kabele <vit@kabele.me>
* Created on the 26/10/2018
*/
package ch.usi.dag.disl;
import javafx.util.Pair;
import java.util.List;
import java.util.stream.Collectors;
public class IsolatedTransformers implements Transformers {
private List<Transformer> transformers;
IsolatedTransformers(List<Pair<String,byte[]>> transformersList){
transformers = transformersList
.stream ()
.map (this::createTransformer)
.collect(Collectors.toList());
}
@Override
public byte[] apply (
final byte[] originalBytes
) throws TransformerException {
byte[] result = originalBytes;
for (final Transformer transformer : transformers) {
try {
final byte[] bytes = transformer.transform (result);
if (bytes != null) {
result = bytes;
}
} catch (final Exception e) {
throw new TransformerException (
e, "transformation failed in %s", transformer
);
}
}
return result;
}
private Transformer createTransformer (Pair<String, byte[]> transformer) {
final Class<?> resolvedClass = resolveTransformer (transformer.getKey (), transformer.getValue ());
if (Transformer.class.isAssignableFrom (resolvedClass)) {
return instantiateTransformer (resolvedClass);
} else {
throw new InitializationException (
"%s does not implement %s",
transformer.getKey (), Transformer.class.getName ()
);
}
}
private Class<?> resolveTransformer (String name, byte[] classbytes){
ByteArrayClassLoader classLoader = new ByteArrayClassLoader ();
return classLoader.createClass(name, classbytes,0, classbytes.length);
}
private Transformer instantiateTransformer (Class<?> transformerClass) throws InitializationException{
try {
return (Transformer) transformerClass.newInstance ();
} catch (final Exception e) {
throw new InitializationException (
e, "failed to instantiate transformer %s",
transformerClass.getName ()
);
}
}
}
package ch.usi.dag.disl.classparser;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.AnnotationNode;
import org.objectweb.asm.tree.ClassNode;
import ch.usi.dag.disl.DiSL.CodeOption;
import ch.usi.dag.disl.InitializationException;
import ch.usi.dag.disl.annotation.ArgumentProcessor;
......@@ -21,6 +11,15 @@ import ch.usi.dag.disl.localvar.LocalVars;
import ch.usi.dag.disl.processor.ArgProcessor;
import ch.usi.dag.disl.snippet.Snippet;
import ch.usi.dag.util.asm.ClassNodeHelper;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.AnnotationNode;
import org.objectweb.asm.tree.ClassNode;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
......
/**
* This file is part of disl project
* Author: Vit Kabele <vit@kabele.me>
* Created on the 26/10/2018
*/
package ch.usi.dag.disl.classparser;
import ch.usi.dag.disl.DiSL;
import ch.usi.dag.disl.InitializationException;
import ch.usi.dag.disl.annotation.ArgumentProcessor;
import ch.usi.dag.disl.exception.ParserException;
import ch.usi.dag.disl.exception.ProcessorException;
import ch.usi.dag.disl.exception.ReflectionException;
import ch.usi.dag.disl.localvar.LocalVars;
import ch.usi.dag.disl.processor.ArgProcessor;
import ch.usi.dag.disl.snippet.Snippet;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.AnnotationNode;
import org.objectweb.asm.tree.ClassNode;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
public class IsolatedDislClasses implements DislClasses {
private final SnippetParser snippetParser;
public IsolatedDislClasses(final Set<DiSL.CodeOption> options, List<ClassNode> classNodes)
throws ParserException, ProcessorException, ReflectionException {
if (classNodes.isEmpty ()) {
throw new InitializationException (
"No DiSL classes found. They must be explicitly specified "+
"using the disl.classes system property or the DiSL-Classes "+
"attribute in the instrumentation JAR manifest."
);
}
//
final SnippetParser sp = new SnippetParser ();
final ArgProcessorParser app = new ArgProcessorParser ();
for (final ClassNode classNode : classNodes) {
if (isArgumentProcessor (classNode)) {
app.parse (classNode);
} else {
sp.parse (classNode);
}
}
//
// Collect all local variables and initialize the argument processor
// and snippets.
//
final LocalVars localVars = LocalVars.merge (
sp.getAllLocalVars (), app.getAllLocalVars ()
);
final Map<Type, ArgProcessor> processors = app.initProcessors (localVars);
// TODO LB: Consider whether we need to create the argument processor
// invocation map now -- we basically discard the argument processors
// and keep an invocation map keyed to instruction indices! :-(
// TODO LB: Move the loop to the SnippetParser class
for (final Snippet snippet : sp.getSnippets ()) {
snippet.init (localVars, processors, options);
}
snippetParser = sp;
}
@Override
public List<Snippet> getSnippets () {
return snippetParser.getSnippets ();
}
@Override
public List<Snippet> selectMatchingSnippets (
String className, String methodName, String methodDesc) {
return snippetParser.getSnippets ().stream ().unordered ()
.filter (s -> s.getScope ().matches (className, methodName, methodDesc))
.collect (Collectors.toList ());
}
private static boolean isArgumentProcessor (final ClassNode classNode) {
//
// An argument processor must have an @ArgumentProcessor annotation
// associated with the class. DiSL instrumentation classes may have
// an @Instrumentation annotation. DiSL classes without annotations
// are by default considered to be instrumentation classes.
//
if (classNode.invisibleAnnotations != null) {
final Type apType = Type.getType (ArgumentProcessor.class);
for (final AnnotationNode annotation : classNode.invisibleAnnotations) {
final Type annotationType = Type.getType (annotation.desc);
if (apType.equals (annotationType)) {
return true;
}
}
}
// default: not an argument processor
return false;
}
}
......@@ -182,17 +182,17 @@ final class RequestProcessor {
/**
* Creates new instance of RequestProcessor class.
*
* @param Intrumentations List of bytearrays representing instrumentation jars.
* @param Instrumentations List of bytearrays representing instrumentation jars.
* @return New instance of RequestProcessor
* @throws DiSLException
*/
public static RequestProcessor newInstance (List<byte[]> Intrumentations) throws DiSLException {
static RequestProcessor newInstance (List<byte[]> Instrumentations) throws DiSLException {
// TODO LB: Configure bypass on a per-request basis.
if (disableBypass) {
System.setProperty ("disl.disablebypass", "true");
}
final DiSL disl = DiSL.init ();