Skip to content

Commit 7f7767c

Browse files
madrobuschindler
andauthored
Wildcard method signatures (#188)
Co-authored-by: Mike Drob <[email protected]> Co-authored-by: Uwe Schindler <[email protected]>
1 parent d746151 commit 7f7767c

File tree

4 files changed

+89
-12
lines changed

4 files changed

+89
-12
lines changed

src/main/docs/signatures-syntax.html

+4-3
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,10 @@ <h1>Syntax of Custom Signatures Files</h1>
4141
<li><em>A field of a class:</em> <code>package.Class#fieldName</code></li>
4242
<li><em>A method signature:</em> It consists of a binary class name, followed by <code>#</code>
4343
and a method name including method parameters: <code>java.lang.String#concat(java.lang.String)</code>
44-
&ndash; All method parameters need to use fully qualified class names!
45-
To refer to instance constructors, use the method name <code>&lt;init&gt;</code>,
46-
e.g. <code>java.lang.Integer#&lt;init&gt;(int)</code>.</li>
44+
&ndash; All method parameters need to use fully qualified class names! Instead of
45+
method parameters, the special wildcard string <code>**</code> may be used to add all variants
46+
of a method, regardless of their parameter types. To refer to instance constructors, use the
47+
method name <code>&lt;init&gt;</code>, e.g. <code>java.lang.Integer#&lt;init&gt;(int)</code>.</li>
4748
</ul>
4849

4950
<p>The error message displayed when the signature matches can be given at the end of each

src/main/java/de/thetaphi/forbiddenapis/Signatures.java

+19-9
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import java.util.Set;
3535
import java.util.TreeSet;
3636
import java.util.regex.Matcher;
37+
import java.util.regex.Pattern;
3738

3839
import org.objectweb.asm.Type;
3940
import org.objectweb.asm.commons.Method;
@@ -48,6 +49,9 @@ public final class Signatures implements Constants {
4849
private static final String DEFAULT_MESSAGE_PREFIX = "@defaultMessage ";
4950
private static final String IGNORE_UNRESOLVABLE_LINE = "@ignoreUnresolvable";
5051
private static final String IGNORE_MISSING_CLASSES_LINE = "@ignoreMissingClasses";
52+
private static final String WILDCARD_ARGS = "**";
53+
private static final Pattern PATTERN_WILDCARD_ARGS = Pattern.compile(String.format(Locale.ROOT, "%s\\s*%s\\s*%s",
54+
Pattern.quote("("), Pattern.quote(WILDCARD_ARGS), Pattern.quote(")")));
5155

5256
private static enum UnresolvableReporting {
5357
FAIL(true) {
@@ -139,21 +143,26 @@ private void addSignature(final String line, final String defaultMessage, final
139143
p = signature.indexOf('#');
140144
if (p >= 0) {
141145
clazz = signature.substring(0, p);
142-
final String s = signature.substring(p + 1);
143-
p = s.indexOf('(');
146+
final String methodOrField = signature.substring(p + 1);
147+
p = methodOrField.indexOf('(');
144148
if (p >= 0) {
145149
if (p == 0) {
146150
throw new ParseException("Invalid method signature (method name missing): " + signature);
147151
}
148-
// we ignore the return type, its just to match easier (so return type is void):
149-
try {
150-
method = Method.getMethod("void " + s, true);
151-
} catch (IllegalArgumentException iae) {
152-
throw new ParseException("Invalid method signature: " + signature);
152+
if (PATTERN_WILDCARD_ARGS.matcher(methodOrField.substring(p)).matches()) {
153+
// we create a method instance with the special descriptor string "**", which gets detected later:
154+
method = new Method(methodOrField.substring(0, p).trim(), WILDCARD_ARGS);
155+
} else {
156+
// we ignore the return type, it just allows the parser to succeed (so return type is void):
157+
try {
158+
method = Method.getMethod("void ".concat(methodOrField), true);
159+
} catch (IllegalArgumentException iae) {
160+
throw new ParseException("Invalid method signature: " + signature);
161+
}
153162
}
154163
field = null;
155164
} else {
156-
field = s;
165+
field = methodOrField;
157166
method = null;
158167
}
159168
} else {
@@ -192,7 +201,8 @@ private void addSignature(final String line, final String defaultMessage, final
192201
// list all methods with this signature:
193202
boolean found = false;
194203
for (final Method m : c.methods) {
195-
if (m.getName().equals(method.getName()) && Arrays.equals(m.getArgumentTypes(), method.getArgumentTypes())) {
204+
if (m.getName().equals(method.getName()) &&
205+
(WILDCARD_ARGS.equals(method.getDescriptor()) || Arrays.equals(m.getArgumentTypes(), method.getArgumentTypes()))) {
196206
found = true;
197207
signatures.put(getKey(c.className, m), printout);
198208
// don't break when found, as there may be more covariant overrides!

src/test/antunit/TestInlineSignatures.xml

+10
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,16 @@
6767
<au:assertLogContains level="error" text="java.lang.String#substring(int,int) [You are crazy that you disallow substrings]"/>
6868
</target>
6969

70+
<target name="testForbiddenWildcardMethodWithMessage">
71+
<au:expectfailure expectedMessage="Check for forbidden API calls failed, see log">
72+
<forbiddenapis classpathref="path.all">
73+
<fileset refid="main.classes"/>
74+
java.lang.String#substring(**) @ You are crazy that you disallow all substrings
75+
</forbiddenapis>
76+
</au:expectfailure>
77+
<au:assertLogContains level="error" text="java.lang.String#substring(**) [You are crazy that you disallow all substrings]"/>
78+
</target>
79+
7080
<target name="testForbiddenFieldWithMessage">
7181
<au:expectfailure expectedMessage="Check for forbidden API calls failed, see log">
7282
<forbiddenapis classpathref="path.all">

src/test/java/de/thetaphi/forbiddenapis/CheckerSetupTest.java

+56
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,62 @@ public void testMethodSignature() throws Exception {
8888
assertFalse(checker.noSignaturesFilesParsed());
8989
}
9090

91+
@Test
92+
public void testMethodSignatureWS() throws Exception {
93+
checker.parseSignaturesString("java.lang.Object# toString\t ( ) @ Foobar");
94+
assertEquals(Collections.singletonMap(Signatures.getKey("java/lang/Object", new Method("toString", "()Ljava/lang/String;")), "java.lang.Object# toString\t ( ) [Foobar]"),
95+
forbiddenSignatures.signatures);
96+
assertEquals(Collections.emptySet(), forbiddenSignatures.classPatterns);
97+
assertFalse(checker.hasNoSignatures());
98+
assertFalse(checker.noSignaturesFilesParsed());
99+
}
100+
101+
@Test
102+
public void testWildcardMethodSignature() throws Exception {
103+
checker.parseSignaturesString("java.lang.String#copyValueOf(**) @ Foobar");
104+
105+
// For Java 7 it should at least contain those 2 signatures:
106+
assertTrue(forbiddenSignatures.signatures.containsKey(Signatures.getKey("java/lang/String", new Method("copyValueOf", "([C)Ljava/lang/String;"))));
107+
assertTrue(forbiddenSignatures.signatures.containsKey(Signatures.getKey("java/lang/String", new Method("copyValueOf", "([CII)Ljava/lang/String;"))));
108+
109+
assertEquals(Collections.emptySet(), forbiddenSignatures.classPatterns);
110+
assertFalse(checker.hasNoSignatures());
111+
assertFalse(checker.noSignaturesFilesParsed());
112+
}
113+
114+
@Test
115+
public void testWildcardMethodSignatureWS() throws Exception {
116+
checker.parseSignaturesString("java.lang.String#copyValueOf ( ** \t ) @ Foobar");
117+
118+
// For Java 7 it should at least contain those 2 signatures:
119+
assertTrue(forbiddenSignatures.signatures.containsKey(Signatures.getKey("java/lang/String", new Method("copyValueOf", "([C)Ljava/lang/String;"))));
120+
assertTrue(forbiddenSignatures.signatures.containsKey(Signatures.getKey("java/lang/String", new Method("copyValueOf", "([CII)Ljava/lang/String;"))));
121+
122+
assertEquals(Collections.emptySet(), forbiddenSignatures.classPatterns);
123+
assertFalse(checker.hasNoSignatures());
124+
assertFalse(checker.noSignaturesFilesParsed());
125+
}
126+
127+
@Test
128+
public void testWildcardMethodSignatureNoArgs() throws Exception {
129+
checker.parseSignaturesString("java.lang.Object#toString(**) @ Foobar");
130+
assertEquals(Collections.singletonMap(Signatures.getKey("java/lang/Object", new Method("toString", "()Ljava/lang/String;")), "java.lang.Object#toString(**) [Foobar]"),
131+
forbiddenSignatures.signatures);
132+
assertEquals(Collections.emptySet(), forbiddenSignatures.classPatterns);
133+
assertFalse(checker.hasNoSignatures());
134+
assertFalse(checker.noSignaturesFilesParsed());
135+
}
136+
137+
@Test
138+
public void testWildcardMethodSignatureNotExist() throws Exception {
139+
try {
140+
checker.parseSignaturesString("java.lang.Object#foobarNotExist(**) @ Foobar");
141+
fail("Should fail to parse because method does not exist");
142+
} catch (ParseException pe) {
143+
assertEquals("Method not found while parsing signature: java.lang.Object#foobarNotExist(**)", pe.getMessage());
144+
}
145+
}
146+
91147
@Test
92148
public void testEmptyCtor() throws Exception {
93149
Checker chk = new Checker(StdIoLogger.INSTANCE, ClassLoader.getSystemClassLoader());

0 commit comments

Comments
 (0)