From 5cf8cd2d2bb82afb5c1940351de9d5488e77b14e Mon Sep 17 00:00:00 2001 From: Mike Drob Date: Fri, 12 Nov 2021 16:59:59 -0600 Subject: [PATCH 1/5] Wildcard method signatures --- .../de/thetaphi/forbiddenapis/Signatures.java | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/main/java/de/thetaphi/forbiddenapis/Signatures.java b/src/main/java/de/thetaphi/forbiddenapis/Signatures.java index a6147f8..64868a4 100644 --- a/src/main/java/de/thetaphi/forbiddenapis/Signatures.java +++ b/src/main/java/de/thetaphi/forbiddenapis/Signatures.java @@ -125,6 +125,7 @@ private void addSignature(final String line, final String defaultMessage, final final String clazz, field, signature; String message = null; final Method method; + boolean ignoreMethodArguments = false; int p = line.indexOf('@'); if (p >= 0) { signature = line.substring(0, p).trim(); @@ -139,9 +140,15 @@ 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('('); + String methodOrField = signature.substring(p + 1); + p = methodOrField.indexOf('('); if (p >= 0) { + String s = methodOrField; + if (methodOrField.substring(p).equals("(**)")) { + // We will ignore method arguments and match everything with the same name + s = signature.substring(0, p) + "()"; + ignoreMethodArguments = true; + } if (p == 0) { throw new ParseException("Invalid method signature (method name missing): " + signature); } @@ -153,7 +160,7 @@ private void addSignature(final String line, final String defaultMessage, final } field = null; } else { - field = s; + field = methodOrField; method = null; } } else { @@ -192,7 +199,7 @@ 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()) && (ignoreMethodArguments || 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! From c5825449dae882c3d41cda4554ed8f93ea061a81 Mon Sep 17 00:00:00 2001 From: Uwe Schindler Date: Thu, 16 Dec 2021 11:39:04 +0100 Subject: [PATCH 2/5] Improve parsing and add tests --- .../de/thetaphi/forbiddenapis/Signatures.java | 31 +++++----- .../forbiddenapis/CheckerSetupTest.java | 56 +++++++++++++++++++ 2 files changed, 73 insertions(+), 14 deletions(-) diff --git a/src/main/java/de/thetaphi/forbiddenapis/Signatures.java b/src/main/java/de/thetaphi/forbiddenapis/Signatures.java index 64868a4..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) { @@ -125,7 +129,6 @@ private void addSignature(final String line, final String defaultMessage, final final String clazz, field, signature; String message = null; final Method method; - boolean ignoreMethodArguments = false; int p = line.indexOf('@'); if (p >= 0) { signature = line.substring(0, p).trim(); @@ -140,23 +143,22 @@ private void addSignature(final String line, final String defaultMessage, final p = signature.indexOf('#'); if (p >= 0) { clazz = signature.substring(0, p); - String methodOrField = signature.substring(p + 1); + final String methodOrField = signature.substring(p + 1); p = methodOrField.indexOf('('); if (p >= 0) { - String s = methodOrField; - if (methodOrField.substring(p).equals("(**)")) { - // We will ignore method arguments and match everything with the same name - s = signature.substring(0, p) + "()"; - ignoreMethodArguments = true; - } 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 { @@ -199,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()) && (ignoreMethodArguments || 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/java/de/thetaphi/forbiddenapis/CheckerSetupTest.java b/src/test/java/de/thetaphi/forbiddenapis/CheckerSetupTest.java index 8719e81..b7f07c0 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( ) @ 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 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()); From abdbdbbb1f2334de91e7dd8ac667a445b1493a37 Mon Sep 17 00:00:00 2001 From: Uwe Schindler Date: Thu, 16 Dec 2021 11:46:00 +0100 Subject: [PATCH 3/5] More crazy test --- .../java/de/thetaphi/forbiddenapis/CheckerSetupTest.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/test/java/de/thetaphi/forbiddenapis/CheckerSetupTest.java b/src/test/java/de/thetaphi/forbiddenapis/CheckerSetupTest.java index b7f07c0..670082e 100644 --- a/src/test/java/de/thetaphi/forbiddenapis/CheckerSetupTest.java +++ b/src/test/java/de/thetaphi/forbiddenapis/CheckerSetupTest.java @@ -90,8 +90,8 @@ public void testMethodSignature() throws Exception { @Test public void testMethodSignatureWS() 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]"), + 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()); @@ -113,7 +113,7 @@ public void testWildcardMethodSignature() throws Exception { @Test public void testWildcardMethodSignatureWS() throws Exception { - checker.parseSignaturesString("java.lang.String#copyValueOf( ** \t ) @ Foobar"); + 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;")))); From 598a618dcea2a08451fdb6d7d513130528e68e82 Mon Sep 17 00:00:00 2001 From: Uwe Schindler Date: Thu, 16 Dec 2021 11:58:34 +0100 Subject: [PATCH 4/5] Add documentation --- src/main/docs/signatures-syntax.html | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) 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 From 6f5e2ac4105d7200822e8a216e92a04838dc961a Mon Sep 17 00:00:00 2001 From: Uwe Schindler Date: Thu, 16 Dec 2021 12:03:04 +0100 Subject: [PATCH 5/5] Add also AntUnit test run against ourselves --- src/test/antunit/TestInlineSignatures.xml | 10 ++++++++++ 1 file changed, 10 insertions(+) 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 + + + + +