Commit 11234525 authored by Lubomir Bulej's avatar Lubomir Bulej

- Reimplemented the scope matcher. The scope now accepts multiple parameter...

- Reimplemented the scope matcher. The scope now accepts multiple parameter wild cards, similar to normal textual wild cards.
- Added tests for the simpler matchers that comprise a scope matcher, and fixed, extended, and cleaned up the existing scope tests.
- Removed ScopeParserException, and replaced it with package-private MalformedScopeException.
- Introduced GeneralException class to hold common runtime exception code.
- Introduced JavaNames utility class to hold common Java name handling code.
- Modified SnippetParser, ExclusionSet to use the new ScopeMatcher.
parent 9493149b
......@@ -559,6 +559,7 @@
</then><else>
<echo>Running all tests.</echo>
<fileset id="test.batch" dir="${src.test}">
<include name="**/scope/*Test.java" />
<include name="**/junit/*Test.java" />
</fileset>
</else>
......
package ch.usi.dag.disl;
/**
* Represents a general DiSL exception, which is a superclass for all
* DiSL exception classes.
*
* @author Lubomir Bulej
*/
@SuppressWarnings ("serial")
public abstract class GeneralException extends RuntimeException {
protected GeneralException (final String message) {
super (message);
}
protected GeneralException (final Throwable cause) {
super (cause);
}
protected GeneralException (
final String format, final Object ... args
) {
super (String.format (format, args));
}
protected GeneralException (
final Throwable cause, final String format, final Object ... args
) {
super (String.format (format, args), cause);
}
}
......@@ -27,7 +27,7 @@ import ch.usi.dag.disl.guard.GuardHelper;
import ch.usi.dag.disl.marker.Marker;
import ch.usi.dag.disl.marker.Parameter;
import ch.usi.dag.disl.scope.Scope;
import ch.usi.dag.disl.scope.ScopeImpl;
import ch.usi.dag.disl.scope.ScopeMatcher;
import ch.usi.dag.disl.snippet.Snippet;
import ch.usi.dag.disl.snippet.SnippetUnprocessedCode;
import ch.usi.dag.disl.util.AsmHelper;
......@@ -106,7 +106,7 @@ class SnippetParser extends AbstractParser {
//
final Marker marker = getMarker (data.marker, data.args);
final Scope scope = new ScopeImpl (data.scope);
final Scope scope = ScopeMatcher.forPattern (data.scope);
final Method guard = GuardHelper.findAndValidateGuardMethod (
AbstractParser.getGuard (data.guard), GuardHelper.snippetContextSet ()
);
......
package ch.usi.dag.disl.exception;
public class ScopeParserException extends ParserException {
private static final long serialVersionUID = -8728473299351707468L;
public ScopeParserException() {
super();
}
public ScopeParserException(String message, Throwable cause) {
super(message, cause);
}
public ScopeParserException(String message) {
super(message);
}
public ScopeParserException(Throwable cause) {
super(cause);
}
}
package ch.usi.dag.disl.exclusion;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.URL;
import java.util.Enumeration;
......@@ -15,9 +14,8 @@ import ch.usi.dag.disl.cbloader.ManifestHelper;
import ch.usi.dag.disl.cbloader.ManifestHelper.ManifestInfo;
import ch.usi.dag.disl.exception.ExclusionPrepareException;
import ch.usi.dag.disl.exception.ManifestInfoException;
import ch.usi.dag.disl.exception.ScopeParserException;
import ch.usi.dag.disl.scope.Scope;
import ch.usi.dag.disl.scope.ScopeImpl;
import ch.usi.dag.disl.scope.ScopeMatcher;
import ch.usi.dag.disl.util.Constants;
......@@ -37,17 +35,20 @@ public abstract class ExclusionSet {
private static final String ALL_METHODS = ".*";
public static Set <Scope> prepare ()
throws ScopeParserException, ManifestInfoException,
ExclusionPrepareException {
final Set <Scope> exclSet = defaultExcludes ();
exclSet.addAll (instrumentationJar ());
exclSet.addAll (readExlusionList ());
return exclSet;
public static Set <Scope> prepare () throws ExclusionPrepareException {
try {
final Set <Scope> exclSet = defaultExcludes ();
exclSet.addAll (instrumentationJar ());
exclSet.addAll (readExlusionList ());
return exclSet;
} catch (final Exception e) {
throw new ExclusionPrepareException ("failed to prepare exclusion list", e);
}
}
private static Set <Scope> defaultExcludes () throws ScopeParserException {
private static Set <Scope> defaultExcludes () {
final String [] excludedScopes = new String [] {
//
// Our classes.
......@@ -65,7 +66,7 @@ public abstract class ExclusionSet {
final Set <Scope> exclSet = new HashSet <Scope> ();
for (final String excludedScope : excludedScopes) {
exclSet.add (new ScopeImpl (excludedScope));
exclSet.add (ScopeMatcher.forPattern (excludedScope));
}
return exclSet;
......@@ -73,91 +74,79 @@ public abstract class ExclusionSet {
private static Set <Scope> instrumentationJar ()
throws ManifestInfoException, ExclusionPrepareException,
ScopeParserException {
try {
// add classes from instrumentation jar
final Set <Scope> exclSet = new HashSet <Scope> ();
throws ManifestInfoException, IOException {
// add classes from instrumentation jar
final Set <Scope> exclSet = new HashSet <Scope> ();
// get DiSL manifest info
final ManifestInfo mi = ManifestHelper.getDiSLManifestInfo ();
// get DiSL manifest info
final ManifestInfo mi = ManifestHelper.getDiSLManifestInfo ();
// no manifest found
if (mi == null) {
return exclSet;
}
// no manifest found
if (mi == null) {
return exclSet;
}
// get URL of the instrumentation jar manifest
final URL manifestURL = ManifestHelper.getDiSLManifestInfo ().getResource ();
// get URL of the instrumentation jar manifest
final URL manifestURL = ManifestHelper.getDiSLManifestInfo ().getResource ();
// manifest path contains "file:" + jar name + "!" + manifest path
final String manifestPath = manifestURL.getPath ();
// manifest path contains "file:" + jar name + "!" + manifest path
final String manifestPath = manifestURL.getPath ();
// extract jar path
final int jarPathBegin = manifestPath.indexOf (JAR_PATH_BEGIN);
final int jarPathEnd = manifestPath.indexOf (JAR_PATH_END);
final String jarPath = manifestPath.substring (jarPathBegin, jarPathEnd);
// extract jar path
final int jarPathBegin = manifestPath.indexOf (JAR_PATH_BEGIN);
final int jarPathEnd = manifestPath.indexOf (JAR_PATH_END);
final String jarPath = manifestPath.substring (jarPathBegin, jarPathEnd);
// open jar...
final JarFile jarFile = new JarFile (jarPath);
// open jar...
final JarFile jarFile = new JarFile (jarPath);
// ... and iterate over items
final Enumeration <JarEntry> entries = jarFile.entries ();
while (entries.hasMoreElements ()) {
final JarEntry entry = entries.nextElement ();
// ... and iterate over items
final Enumeration <JarEntry> entries = jarFile.entries ();
while (entries.hasMoreElements ()) {
final JarEntry entry = entries.nextElement ();
// get entry name
final String entryName = entry.getName ();
// get entry name
final String entryName = entry.getName ();
// add all classes to the exclusion list
if (entryName.endsWith (Constants.CLASS_EXT)) {
String className = entryName.replace (JAR_ENTRY_DELIM, CLASS_DELIM);
// add all classes to the exclusion list
if (entryName.endsWith (Constants.CLASS_EXT)) {
String className = entryName.replace (JAR_ENTRY_DELIM, CLASS_DELIM);
// remove class ext
final int classNameEnd = className.lastIndexOf (Constants.CLASS_EXT);
className = className.substring (0, classNameEnd);
// remove class ext
final int classNameEnd = className.lastIndexOf (Constants.CLASS_EXT);
className = className.substring (0, classNameEnd);
// add exclusion for all methods
final String classExcl = className + ALL_METHODS;
// add exclusion for all methods
final String classExcl = className + ALL_METHODS;
exclSet.add (new ScopeImpl (classExcl));
}
exclSet.add (ScopeMatcher.forPattern (classExcl));
}
jarFile.close ();
return exclSet;
} catch (final IOException e) {
throw new ExclusionPrepareException (e);
}
jarFile.close ();
return exclSet;
}
private static Set <Scope> readExlusionList ()
throws ExclusionPrepareException, ScopeParserException {
private static Set <Scope> readExlusionList () throws IOException {
final String COMMENT_START = "#";
try {
final Set <Scope> exclSet = new HashSet <Scope> ();
// if exclusion list path exits
if (excListPath != null) {
// read exclusion list line by line
final Scanner scanner = new Scanner (new FileInputStream (excListPath));
while (scanner.hasNextLine ()) {
final String line = scanner.nextLine ().trim ();
if (!line.isEmpty () && !line.startsWith (COMMENT_START)) {
exclSet.add (new ScopeImpl (line));
}
}
final Set <Scope> exclSet = new HashSet <Scope> ();
scanner.close ();
// if exclusion list path exits
if (excListPath != null) {
// read exclusion list line by line
final Scanner scanner = new Scanner (new FileInputStream (excListPath));
while (scanner.hasNextLine ()) {
final String line = scanner.nextLine ().trim ();
if (!line.isEmpty () && !line.startsWith (COMMENT_START)) {
exclSet.add (ScopeMatcher.forPattern (line));
}
}
return exclSet;
} catch (final FileNotFoundException e) {
throw new ExclusionPrepareException (e);
scanner.close ();
}
return exclSet;
}
}
package ch.usi.dag.disl.scope;
import ch.usi.dag.disl.GeneralException;
@SuppressWarnings ("serial")
final class MalformedScopeException extends GeneralException {
protected MalformedScopeException (final String message) {
super (message);
}
protected MalformedScopeException (
final String format, final Object ... args
) {
super (format, args);
}
}
package ch.usi.dag.disl.scope;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.regex.Pattern;
import org.objectweb.asm.Type;
import ch.usi.dag.disl.util.JavaNames;
/**
* Method parameter matcher. Allows checking a method descriptor against a
* parameter pattern. The pattern list can contain multiple wild card (..)
* elements which represent zero or more parameters of any type. Conceptually,
* this is a variant of the {@link WildCardMatcher} applied to sequences of
* method parameters instead of characters.
*
* @author Lubomir Bulej
*/
abstract class ParameterMatcher {
public static final String WILDCARD = "..";
//
private static final ParameterMatcher __matchAnyParams__ = new ParameterMatcher (WILDCARD) {
@Override
public boolean match (final String methodDesc) {
return true;
};
};
private static final ParameterMatcher __matchZeroParams__ = new ParameterMatcher ("") {
private static final String __NO_PARAMETERS__ = "()";
@Override
public boolean match (final String methodDesc) {
return methodDesc.startsWith (__NO_PARAMETERS__);
};
};
//
private static final class Generic extends ParameterMatcher {
private final TypeMatcher [][] __cards;
private final TypeMatcher [] __prefixCard;
private final TypeMatcher [] __suffixCard;
private Generic (
final String pattern, final List <TypeMatcher []> cards
) {
super (pattern);
__prefixCard = cards.get (0);
__suffixCard = cards.get (cards.size () - 1);
__cards = cards.stream ()
.filter (a -> a.length > 0)
.toArray (size -> new TypeMatcher [size][]);
}
//
@Override
public boolean match (final String methodDesc) {
final Type [] params = Type.getArgumentTypes (methodDesc);
int paramsFromInclusive = 0;
int paramsToExclusive = params.length;
int cardsFromInclusive = 0;
if (__prefixCard.length > 0) {
// The first card should match at the beginning.
if (!__matchCard (__prefixCard, params, 0, paramsToExclusive)) {
return false;
}
paramsFromInclusive += __prefixCard.length;
cardsFromInclusive++;
}
int cardsToExclusive = __cards.length;
if (__suffixCard.length > 0) {
// The last card should match at the end
final int tailFromInclusive = Math.max (0, paramsToExclusive - __suffixCard.length);
if (!__matchCard (__suffixCard, params, tailFromInclusive, paramsToExclusive)) {
return false;
}
paramsToExclusive -= __suffixCard.length;
cardsToExclusive--;
}
//
// If the first and last cards are the same, they must overlap
// on all parameters.
//
if (cardsFromInclusive > cardsToExclusive) {
return (paramsToExclusive - paramsFromInclusive) == -params.length;
}
//
// Otherwise try to match the pattern to the input. All parameter
// cards must be found in the method parameters, in the correct
// order, and without overlaps.
//
while (cardsFromInclusive < cardsToExclusive) {
final TypeMatcher [] card = __cards [cardsFromInclusive];
final int cardMatchIndex = __indexOf (card, params, paramsFromInclusive, paramsToExclusive);
if (cardMatchIndex < 0) {
return false;
}
paramsFromInclusive = cardMatchIndex + card.length;
cardsFromInclusive++;
}
return true;
}
private boolean __matchCard (
final TypeMatcher [] card, final Type [] params,
final int fromInclusive, final int toExclusive
) {
if (card.length > (toExclusive - fromInclusive)) {
// not enough parameters to match the card
return false;
}
int paramIndex = fromInclusive;
for (final TypeMatcher matcher : card) {
final Type param = params [paramIndex++];
if (!matcher.match (param.getDescriptor ())) {
return false;
}
}
return true;
}
private int __indexOf (
final TypeMatcher [] card, final Type [] params,
final int fromInclusive, final int toExclusive
) {
for (int index = fromInclusive; index < toExclusive; index++) {
if (__matchCard (card, params, index, toExclusive)) {
return index;
}
}
return -1;
}
}
//
private final String __pattern;
private ParameterMatcher (final String pattern) {
__pattern = pattern;
}
/**
* @return The parameter pattern associated with this matcher.
*/
public String pattern () {
return __pattern;
}
@Override
public String toString () {
return String.format ("%s[(%s)]", JavaNames.simpleClassName (this), pattern ());
}
/**
* @param methodDesc
* the method descriptor to test for match, may not be {@code null}.
* @return {@code true} if the given method descriptor matches the pattern
* represented by this matcher, {@code false} otherwise.
*/
public abstract boolean match (final String methodDesc);
//
/**
* Creates a matcher for the given parameter pattern. The pattern can
* contain multiple wild card (..) elements representing a sequence of zero
* or more parameters of any type. An empty pattern will only match
* descriptors for methods with no parameters.
*
* @param pattern
* the pattern to create the matcher for, may not be {@code null}.
* @return a new {@link ParameterMatcher} for the given pattern.
*/
public static ParameterMatcher forPattern (final String pattern) {
final String trimmed = Objects.requireNonNull (pattern).trim ();
//
if (trimmed.isEmpty ()) {
return __matchZeroParams__;
} else if (trimmed.equals (WILDCARD)) {
return __matchAnyParams__;
} else {
return new Generic (trimmed, __split (trimmed));
}
}
private static final Pattern __splitter__ = Pattern.compile ("\\s*,\\s*");
private static List <TypeMatcher []> __split (final String pattern) {
assert !pattern.isEmpty ();
final List <TypeMatcher []> result = new ArrayList <> ();
final List <TypeMatcher> card = new ArrayList <> ();
__splitter__.splitAsStream (pattern)
.map (String::trim)
.map (s -> s.isEmpty () ? WildCardMatcher.WILDCARD : s)
.forEachOrdered (s -> {
if (!s.equals (WILDCARD)) {
card.add (TypeMatcher.forPattern (s));
} else {
// flush current card
result.add (card.toArray (new TypeMatcher [card.size ()]));
card.clear ();
}
});
// flush last card
result.add (card.toArray (new TypeMatcher [card.size ()]));
return result;
}
}
\ No newline at end of file
......@@ -10,8 +10,8 @@ public interface Scope {
* Determines whether this scope matches the given class name (including
* package name), method name, and method type descriptor.
*
* @param className
* standard or internal name of the class to match
* @param classInternalName
* <b>internal name</b> (i.e., delimited using slashes) of the class to match
* @param methodName
* name of the method to match
* @param methodDesc
......@@ -19,6 +19,6 @@ public interface Scope {
* @return {@code true} if the scope matches the given class name, method
* name, and method type descriptor, {@code false} otherwise.
*/
boolean matches (String className, String methodName, String methodDesc);
boolean matches (String classInternalName, String methodName, String methodDesc);
}
This diff is collapsed.
This diff is collapsed.
package ch.usi.dag.disl.scope;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import org.objectweb.asm.Type;
import ch.usi.dag.disl.util.JavaNames;
abstract class TypeMatcher {
public abstract boolean match (final String typeDesc);
public abstract String pattern ();
@Override
public String toString () {
return String.format ("%s[%s]", JavaNames.simpleClassName (this), pattern ());
}
//
private static final TypeMatcher __matchAny__ = new TypeMatcher () {
@Override
public boolean match (final String typeDesc) {
return true;
};
@Override
public String pattern () {
return "<any>";
};
};
private static final TypeMatcher __matchNone__ = new TypeMatcher () {
@Override
public boolean match (final String typeDesc) {
return false;
};
@Override
public String pattern() {
return "<none>";
};
};
//
private static final class Generic extends TypeMatcher {
private final WildCardMatcher __matcher;
private Generic (final WildCardMatcher matcher) {
__matcher = matcher;
}
//
@Override
public boolean match (final String typeDesc) {
//
// Object type descriptors without package specification need
// special handling. To match patterns with package wild card,
// we need to add package separator to such descriptors so that
// the default package is an empty package.
//
if (__isObjectType (typeDesc) && !JavaNames.hasInternalPackageName (typeDesc)) {
return __matcher.match (__fixupDefaultPackage (typeDesc));
} else {
return __matcher.match (typeDesc);
}
}
private static final char __OBJECT_TYPE_CHAR__ = 'L';
private boolean __isObjectType (final String descriptor) {
return descriptor.length () > 0 && descriptor.charAt (0) == __OBJECT_TYPE_CHAR__;
}
private static final char __PKG_SEPARATOR_CHAR__ = '/';
private static String __fixupDefaultPackage (final String descriptor) {
final StringBuilder result = new StringBuilder (descriptor.length () + 1);
result.append (descriptor.charAt (0));
result.append (__PKG_SEPARATOR_CHAR__);
result.append (descriptor.substring (1));
return result.toString ();
}
//
@Override
public String pattern() {
return __matcher.pattern ();
}
}
//
static TypeMatcher forPattern (final String typePattern) {
final String trimmed = Objects.requireNonNull (typePattern).trim ();
//
if (trimmed.isEmpty ()) {
return __matchNone__;
} else if (trimmed.equals (WildCardMatcher.WILDCARD)) {
return __matchAny__;
} else {
final String descPattern = __getDescriptorPattern (trimmed);
final WildCardMatcher matcher = WildCardMatcher.forPattern (
JavaNames.canonicalToInternal (descPattern)
);
return new Generic (matcher);
}
}
//
private static final String __ARRAY_BRACKETS__ = "[]";
private static final int __ARRAY_BRACKETS_LENGTH__ = __ARRAY_BRACKETS__.length ();
private static String __getDescriptorPattern (final String input) {
final int firstPosition = input.indexOf (__ARRAY_BRACKETS__);
if (firstPosition < 0) {
return __getTypeDescriptor (input);
} else {
final StringBuilder result = new StringBuilder ();
final int dimensions =