Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Wildcard method signatures #188

Merged
merged 6 commits into from
Dec 16, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions src/main/docs/signatures-syntax.html
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,10 @@ <h1>Syntax of Custom Signatures Files</h1>
<li><em>A field of a class:</em> <code>package.Class#fieldName</code></li>
<li><em>A method signature:</em> It consists of a binary class name, followed by <code>#</code>
and a method name including method parameters: <code>java.lang.String#concat(java.lang.String)</code>
&ndash; All method parameters need to use fully qualified class names!
To refer to instance constructors, use the method name <code>&lt;init&gt;</code>,
e.g. <code>java.lang.Integer#&lt;init&gt;(int)</code>.</li>
&ndash; All method parameters need to use fully qualified class names! Instead of
method parameters, the special wildcard string <code>**</code> may be used to add all variants
of a method, regardless of their parameter types. To refer to instance constructors, use the
method name <code>&lt;init&gt;</code>, e.g. <code>java.lang.Integer#&lt;init&gt;(int)</code>.</li>
</ul>

<p>The error message displayed when the signature matches can be given at the end of each
Expand Down
28 changes: 19 additions & 9 deletions src/main/java/de/thetaphi/forbiddenapis/Signatures.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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) {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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!
Expand Down
10 changes: 10 additions & 0 deletions src/test/antunit/TestInlineSignatures.xml
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,16 @@
<au:assertLogContains level="error" text="java.lang.String#substring(int,int) [You are crazy that you disallow substrings]"/>
</target>

<target name="testForbiddenWildcardMethodWithMessage">
<au:expectfailure expectedMessage="Check for forbidden API calls failed, see log">
<forbiddenapis classpathref="path.all">
<fileset refid="main.classes"/>
java.lang.String#substring(**) @ You are crazy that you disallow all substrings
</forbiddenapis>
</au:expectfailure>
<au:assertLogContains level="error" text="java.lang.String#substring(**) [You are crazy that you disallow all substrings]"/>
</target>

<target name="testForbiddenFieldWithMessage">
<au:expectfailure expectedMessage="Check for forbidden API calls failed, see log">
<forbiddenapis classpathref="path.all">
Expand Down
56 changes: 56 additions & 0 deletions src/test/java/de/thetaphi/forbiddenapis/CheckerSetupTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down