diff --git a/src/main/docs/signatures-syntax.html b/src/main/docs/signatures-syntax.html index a9120e1..7f1d944 100644 --- a/src/main/docs/signatures-syntax.html +++ b/src/main/docs/signatures-syntax.html @@ -41,9 +41,10 @@

Syntax of Custom Signatures Files

  • A field of a class: package.Class#fieldName
  • A method signature: It consists of a binary class name, followed by # and a method name including method parameters: java.lang.String#concat(java.lang.String) - – All method parameters need to use fully qualified class names! - To refer to instance constructors, use the method name <init>, - e.g. java.lang.Integer#<init>(int).
  • + – All method parameters need to use fully qualified class names! Instead of + method parameters, the special wildcard string ** may be used to add all variants + of a method, regardless of their parameter types. To refer to instance constructors, use the + method name <init>, e.g. java.lang.Integer#<init>(int).

    The error message displayed when the signature matches can be given at the end of each diff --git a/src/main/java/de/thetaphi/forbiddenapis/Signatures.java b/src/main/java/de/thetaphi/forbiddenapis/Signatures.java index a6147f8..a98a8c1 100644 --- a/src/main/java/de/thetaphi/forbiddenapis/Signatures.java +++ b/src/main/java/de/thetaphi/forbiddenapis/Signatures.java @@ -34,6 +34,7 @@ import java.util.Set; import java.util.TreeSet; import java.util.regex.Matcher; +import java.util.regex.Pattern; import org.objectweb.asm.Type; import org.objectweb.asm.commons.Method; @@ -48,6 +49,9 @@ public final class Signatures implements Constants { private static final String DEFAULT_MESSAGE_PREFIX = "@defaultMessage "; private static final String IGNORE_UNRESOLVABLE_LINE = "@ignoreUnresolvable"; private static final String IGNORE_MISSING_CLASSES_LINE = "@ignoreMissingClasses"; + private static final String WILDCARD_ARGS = "**"; + private static final Pattern PATTERN_WILDCARD_ARGS = Pattern.compile(String.format(Locale.ROOT, "%s\\s*%s\\s*%s", + Pattern.quote("("), Pattern.quote(WILDCARD_ARGS), Pattern.quote(")"))); private static enum UnresolvableReporting { FAIL(true) { @@ -139,21 +143,26 @@ private void addSignature(final String line, final String defaultMessage, final p = signature.indexOf('#'); if (p >= 0) { clazz = signature.substring(0, p); - final String s = signature.substring(p + 1); - p = s.indexOf('('); + final String methodOrField = signature.substring(p + 1); + p = methodOrField.indexOf('('); if (p >= 0) { if (p == 0) { throw new ParseException("Invalid method signature (method name missing): " + signature); } - // we ignore the return type, its just to match easier (so return type is void): - try { - method = Method.getMethod("void " + s, true); - } catch (IllegalArgumentException iae) { - throw new ParseException("Invalid method signature: " + signature); + if (PATTERN_WILDCARD_ARGS.matcher(methodOrField.substring(p)).matches()) { + // we create a method instance with the special descriptor string "**", which gets detected later: + method = new Method(methodOrField.substring(0, p).trim(), WILDCARD_ARGS); + } else { + // we ignore the return type, it just allows the parser to succeed (so return type is void): + try { + method = Method.getMethod("void ".concat(methodOrField), true); + } catch (IllegalArgumentException iae) { + throw new ParseException("Invalid method signature: " + signature); + } } field = null; } else { - field = s; + field = methodOrField; method = null; } } else { @@ -192,7 +201,8 @@ private void addSignature(final String line, final String defaultMessage, final // list all methods with this signature: boolean found = false; for (final Method m : c.methods) { - if (m.getName().equals(method.getName()) && Arrays.equals(m.getArgumentTypes(), method.getArgumentTypes())) { + if (m.getName().equals(method.getName()) && + (WILDCARD_ARGS.equals(method.getDescriptor()) || Arrays.equals(m.getArgumentTypes(), method.getArgumentTypes()))) { found = true; signatures.put(getKey(c.className, m), printout); // don't break when found, as there may be more covariant overrides! diff --git a/src/test/antunit/TestInlineSignatures.xml b/src/test/antunit/TestInlineSignatures.xml index cd8bc70..84ff597 100644 --- a/src/test/antunit/TestInlineSignatures.xml +++ b/src/test/antunit/TestInlineSignatures.xml @@ -67,6 +67,16 @@ + + + + + java.lang.String#substring(**) @ You are crazy that you disallow all substrings + + + + + diff --git a/src/test/java/de/thetaphi/forbiddenapis/CheckerSetupTest.java b/src/test/java/de/thetaphi/forbiddenapis/CheckerSetupTest.java index 8719e81..670082e 100644 --- a/src/test/java/de/thetaphi/forbiddenapis/CheckerSetupTest.java +++ b/src/test/java/de/thetaphi/forbiddenapis/CheckerSetupTest.java @@ -88,6 +88,62 @@ public void testMethodSignature() throws Exception { assertFalse(checker.noSignaturesFilesParsed()); } + @Test + public void testMethodSignatureWS() throws Exception { + checker.parseSignaturesString("java.lang.Object# toString\t ( ) @ Foobar"); + assertEquals(Collections.singletonMap(Signatures.getKey("java/lang/Object", new Method("toString", "()Ljava/lang/String;")), "java.lang.Object# toString\t ( ) [Foobar]"), + forbiddenSignatures.signatures); + assertEquals(Collections.emptySet(), forbiddenSignatures.classPatterns); + assertFalse(checker.hasNoSignatures()); + assertFalse(checker.noSignaturesFilesParsed()); + } + + @Test + public void testWildcardMethodSignature() throws Exception { + checker.parseSignaturesString("java.lang.String#copyValueOf(**) @ Foobar"); + + // For Java 7 it should at least contain those 2 signatures: + assertTrue(forbiddenSignatures.signatures.containsKey(Signatures.getKey("java/lang/String", new Method("copyValueOf", "([C)Ljava/lang/String;")))); + assertTrue(forbiddenSignatures.signatures.containsKey(Signatures.getKey("java/lang/String", new Method("copyValueOf", "([CII)Ljava/lang/String;")))); + + assertEquals(Collections.emptySet(), forbiddenSignatures.classPatterns); + assertFalse(checker.hasNoSignatures()); + assertFalse(checker.noSignaturesFilesParsed()); + } + + @Test + public void testWildcardMethodSignatureWS() throws Exception { + checker.parseSignaturesString("java.lang.String#copyValueOf ( ** \t ) @ Foobar"); + + // For Java 7 it should at least contain those 2 signatures: + assertTrue(forbiddenSignatures.signatures.containsKey(Signatures.getKey("java/lang/String", new Method("copyValueOf", "([C)Ljava/lang/String;")))); + assertTrue(forbiddenSignatures.signatures.containsKey(Signatures.getKey("java/lang/String", new Method("copyValueOf", "([CII)Ljava/lang/String;")))); + + assertEquals(Collections.emptySet(), forbiddenSignatures.classPatterns); + assertFalse(checker.hasNoSignatures()); + assertFalse(checker.noSignaturesFilesParsed()); + } + + @Test + public void testWildcardMethodSignatureNoArgs() throws Exception { + checker.parseSignaturesString("java.lang.Object#toString(**) @ Foobar"); + assertEquals(Collections.singletonMap(Signatures.getKey("java/lang/Object", new Method("toString", "()Ljava/lang/String;")), "java.lang.Object#toString(**) [Foobar]"), + forbiddenSignatures.signatures); + assertEquals(Collections.emptySet(), forbiddenSignatures.classPatterns); + assertFalse(checker.hasNoSignatures()); + assertFalse(checker.noSignaturesFilesParsed()); + } + + @Test + public void testWildcardMethodSignatureNotExist() throws Exception { + try { + checker.parseSignaturesString("java.lang.Object#foobarNotExist(**) @ Foobar"); + fail("Should fail to parse because method does not exist"); + } catch (ParseException pe) { + assertEquals("Method not found while parsing signature: java.lang.Object#foobarNotExist(**)", pe.getMessage()); + } + } + @Test public void testEmptyCtor() throws Exception { Checker chk = new Checker(StdIoLogger.INSTANCE, ClassLoader.getSystemClassLoader());