Skip to content

Commit cf99ebe

Browse files
authored
Automatically downgrade/fallback bundled signatures to next lower version if missing (#274)
* Add a set with all bundled Signatures and also check against it instead of searching classpath * Use NavigableSet to find closest matching version by using a custom comparator * Change logic to also normalize the bundled-sig name (use canonic name) and cleanup api * make compatible to java 7 * add test * add antunit test for commons-io version downgrade or equivalence * add documentation about bundled signatures
1 parent 53aa89f commit cf99ebe

7 files changed

Lines changed: 272 additions & 14 deletions

File tree

build.xml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,14 @@
316316
<path refid="path.main-build"/>
317317
</classpath>
318318
</groovyc>
319+
<pathconvert property="bundled-signatures-list" pathsep="&#10;">
320+
<fileset dir="${signatures.dir}" includes="*.txt" excludes="incr-*"/>
321+
<chainedmapper>
322+
<flattenmapper />
323+
<globmapper from="*.txt" to="*"/>
324+
</chainedmapper>
325+
</pathconvert>
326+
<echo file="build/main/de/thetaphi/forbiddenapis/signatures/list" encoding="${build.encoding}">${bundled-signatures-list}</echo>
319327
</target>
320328

321329
<target name="compile-tools" depends="compile" description="Compile tools">

src/main/docs/bundled-signatures.html

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ <h1>Bundled Signatures Documentation</h1>
4343
<em>Internally this is implemented using heuristics:</em> Any reference to an API that is part of the Java runtime (<tt>rt.jar</tt>, extensions,
4444
Java 9+ <tt>java.*</tt> / <tt>jdk.*</tt> core modules) and is <strong>not</strong> part of the Java SE specification packages
4545
(mainly <tt>java</tt>, <tt>javax</tt>, but also <tt>org.ietf.jgss</tt>, <tt>org.omg</tt>, <tt>org.w3c.dom</tt>, and <tt>org.xml.sax</tt>) is forbidden
46-
(any java version, no specific JDK version, <em>since forbiddenapis v2.1).</li>
46+
(any java version, no specific JDK version, <em>since forbiddenapis v2.1</em>).</li>
4747

4848
<li><strong><tt>jdk-system-out</tt>:</strong> On server-side applications or libraries used by other programs, printing to
4949
<tt>System.out</tt> or <tt>System.err</tt> is discouraged and should be avoided (any java version, no specific JDK version).</li>
@@ -53,9 +53,16 @@ <h1>Bundled Signatures Documentation</h1>
5353

5454
<li><strong><tt>commons-io-unsafe-*</tt>:</strong> If your application uses the famous <i>Apache Common-IO</i> library,
5555
this adds signatures of all methods that depend on default charset
56-
(for versions <tt>*</tt> = 1.0, 1.1, 1.2, 1.3, 1.4, 2.0, 2.1, 2.2, 2.3, 2.4, 2.5, 2.6, 2.7, 2.8.0, 2.9.0, 2.10.0, 2.11.0, 2.12.0, 2.13.0, 2.14.0, 2.15.0, 2.15.1, 2.16.0, 2.16.1, 2.17.0, 2.18.0).</li>
56+
(for versions <tt>*</tt> = 1.0,..., 2.8.0,..., 2.20.0).</li>
5757

5858
</ul>
5959

60+
<p>You can specify newer JDK versions but this will lead to a warning that the signatures were downgraded. This may fail to detect all
61+
deprecated APIs with <tt>jdk-deprecated-*</tt>, but it should be OK till an update is available.
62+
Please do not open issues about adding new JDK versions! This will be done regularily due to maintenance releases. Only open new
63+
issues if the classfile format version is no longer supported by forbiddenapis and parsing class files leads to an error.</p>
64+
65+
<p>The same applies to <tt>commons-io-unsafe-*</tt>.</p>
66+
6067
</body>
6168
</html>

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ public interface Constants {
2727
final String BS_JDK_NONPORTABLE = "jdk-non-portable";
2828

2929
final Pattern JDK_SIG_PATTERN = Pattern.compile("(jdk\\-.*?\\-)(\\d+)(\\.\\d+)?(\\.\\d+)*");
30+
final Pattern ENDS_WITH_VERSION_PATTERN = Pattern.compile("(.*?)\\-(\\d+(\\.\\d+)*)");
3031

3132
final String DEPRECATED_WARN_FAIL_ON_UNRESOLVABLE_SIGNATURES =
3233
"The setting 'failOnUnresolvableSignatures' was deprecated and will be removed in next version. Use 'ignoreSignaturesOfMissingClasses' instead.";

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

Lines changed: 47 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import java.io.InputStreamReader;
2626
import java.io.Reader;
2727
import java.io.StringReader;
28+
import java.net.URL;
2829
import java.nio.charset.StandardCharsets;
2930
import java.util.ArrayList;
3031
import java.util.Arrays;
@@ -35,6 +36,7 @@
3536
import java.util.LinkedHashSet;
3637
import java.util.Locale;
3738
import java.util.Map;
39+
import java.util.NavigableSet;
3840
import java.util.Set;
3941
import java.util.TreeSet;
4042
import java.util.regex.Matcher;
@@ -57,6 +59,22 @@ public final class Signatures implements Constants {
5759
private static final String WILDCARD_ARGS = "**";
5860
private static final Pattern PATTERN_WILDCARD_ARGS = Pattern.compile(String.format(Locale.ROOT, "%s\\s*%s\\s*%s",
5961
Pattern.quote("("), Pattern.quote(WILDCARD_ARGS), Pattern.quote(")")));
62+
63+
// not public yet because its modifiable (Java 7 misses Collections#unmodifiableNavigableSet):
64+
private static final NavigableSet<String> BUNDLED_SIGNATURES_NAMES;
65+
static {
66+
try (final InputStream in = Checker.class.getResourceAsStream("signatures/list");
67+
final BufferedReader reader = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8))) {
68+
final NavigableSet<String> names = new TreeSet<String>(VersionCompare.BUNDLED_SIGNATURES_COMPARATOR);
69+
String name;
70+
while ((name = reader.readLine()) != null) {
71+
names.add(name);
72+
}
73+
BUNDLED_SIGNATURES_NAMES = names;
74+
} catch (IOException ioe) {
75+
throw new RuntimeException(ioe);
76+
}
77+
}
6078

6179
private static enum UnresolvableReporting {
6280
FAIL(true) {
@@ -272,30 +290,47 @@ private void reportMissingSignatureClasses(Set<String> missingClasses) {
272290
logger.warn(AsmUtils.formatClassesAbbreviated(missingClasses));
273291
}
274292

275-
private void addBundledSignatures(String name, String jdkTargetVersion, boolean logging, Set<String> missingClasses) throws IOException,ParseException {
293+
private void addBundledSignatures(String name, String jdkTargetVersion, boolean readExternal, Set<String> missingClasses) throws IOException,ParseException {
276294
if (!name.matches("[A-Za-z0-9\\-\\.]+")) {
277295
throw new ParseException("Invalid bundled signature reference: " + name);
278296
}
279297
if (BS_JDK_NONPORTABLE.equals(name)) {
280-
if (logging) logger.info("Reading bundled API signatures: " + name);
298+
if (readExternal) logger.info("Reading bundled API signatures: " + name);
281299
numberOfFiles++;
282300
forbidNonPortableRuntime = true;
283301
return;
284302
}
285-
name = fixTargetVersion(name);
286-
// use Checker.class hardcoded (not getClass) so we have a fixed package name:
287-
InputStream in = Checker.class.getResourceAsStream("signatures/" + name + ".txt");
288-
// automatically expand the compiler version in here (for jdk-* signatures without version):
289-
if (in == null && jdkTargetVersion != null && name.startsWith("jdk-") && !name.matches(".*?\\-\\d+(\\.\\d+)*")) {
290-
name = name + "-" + jdkTargetVersion;
303+
if (readExternal) {
291304
name = fixTargetVersion(name);
292-
in = Checker.class.getResourceAsStream("signatures/" + name + ".txt");
305+
// automatically expand the compiler version in here (for jdk-* signatures without version):
306+
if (!BUNDLED_SIGNATURES_NAMES.contains(name) && jdkTargetVersion != null && name.startsWith("jdk-") && !ENDS_WITH_VERSION_PATTERN.matcher(name).matches()) {
307+
name = name + "-" + jdkTargetVersion;
308+
name = fixTargetVersion(name);
309+
}
310+
// downgrade the version number to next lower signatures file:
311+
final Matcher m = ENDS_WITH_VERSION_PATTERN.matcher(name);
312+
if (m.matches()) {
313+
final String closest = BUNDLED_SIGNATURES_NAMES.floor(name);
314+
if (closest != null && closest.startsWith(m.group(1)) && ENDS_WITH_VERSION_PATTERN.matcher(closest).matches()) {
315+
if (VersionCompare.compareBundledSignatures(closest, name) != 0) {
316+
logger.warn("Bundled signatures '" + name + "' not found, choosing next lower available signature: " + closest);
317+
}
318+
// Assign the found name (normalized, e.g. the "0" version components removed):
319+
name = closest;
320+
}
321+
}
322+
// check name again:
323+
if (!BUNDLED_SIGNATURES_NAMES.contains(name)) {
324+
throw new FileNotFoundException("Bundled signatures resource not found: " + name);
325+
}
326+
logger.info("Reading bundled API signatures: " + name);
293327
}
294-
if (in == null) {
328+
// use Checker.class hardcoded (not getClass) so we have a fixed package name:
329+
final URL url = Checker.class.getResource("signatures/" + name + ".txt");
330+
if (url == null) {
295331
throw new FileNotFoundException("Bundled signatures resource not found: " + name);
296332
}
297-
if (logging) logger.info("Reading bundled API signatures: " + name);
298-
parseSignaturesStream(in, true, missingClasses);
333+
parseSignaturesStream(url.openStream(), true, missingClasses);
299334
}
300335

301336
private void parseSignaturesStream(InputStream in, boolean isBundled, Set<String> missingClasses) throws IOException,ParseException {
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/*
2+
* (C) Copyright Uwe Schindler (Generics Policeman) and others.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package de.thetaphi.forbiddenapis;
18+
19+
import java.util.Comparator;
20+
import java.util.regex.Matcher;
21+
import java.util.regex.Pattern;
22+
23+
/** Simple version comparator (to support downgrading versions in bundled-signatures names). */
24+
final class VersionCompare {
25+
26+
private static final Pattern DOT_SPLITTER_PATTERN = Pattern.compile("\\.");
27+
28+
public static final Comparator<String> VERSION_COMPARATOR = new Comparator<String>() {
29+
@Override
30+
public int compare(String version1, String version2) {
31+
return compareVersion(version1, version2);
32+
}
33+
};
34+
35+
public static final Comparator<String> BUNDLED_SIGNATURES_COMPARATOR = new Comparator<String>() {
36+
@Override
37+
public int compare(String bs1, String bs2) {
38+
return compareBundledSignatures(bs1, bs2);
39+
}
40+
};
41+
42+
private VersionCompare() {}
43+
44+
public static int compareVersion(String version1, String version2) {
45+
final String[] version1Splits = DOT_SPLITTER_PATTERN.split(version1),
46+
version2Splits = DOT_SPLITTER_PATTERN.split(version2);
47+
final int maxLengthOfVersionSplits = Math.max(version1Splits.length, version2Splits.length);
48+
49+
for (int i = 0; i < maxLengthOfVersionSplits; i++) {
50+
final int v1 = i < version1Splits.length ? Integer.parseInt(version1Splits[i]) : 0;
51+
final int v2 = i < version2Splits.length ? Integer.parseInt(version2Splits[i]) : 0;
52+
final int compare = Integer.compare(v1, v2);
53+
if (compare != 0) {
54+
return compare;
55+
}
56+
}
57+
return 0;
58+
}
59+
60+
public static int compareBundledSignatures(String bs1, String bs2) {
61+
final Matcher m1 = Constants.ENDS_WITH_VERSION_PATTERN.matcher(bs1),
62+
m2 = Constants.ENDS_WITH_VERSION_PATTERN.matcher(bs2);
63+
if (m1.matches() && m2.matches()) {
64+
final int prefixCmp = m1.group(1).compareTo(m2.group(1));
65+
if (prefixCmp != 0) {
66+
return prefixCmp;
67+
}
68+
return compareVersion(m1.group(2), m2.group(2));
69+
} else {
70+
return bs1.compareTo(bs2);
71+
}
72+
}
73+
74+
}

src/test/antunit/TestCommonsIOSignatures.xml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,23 @@
3636
<forbiddenapis bundledSignatures="${commons-io-signature}" ignoreEmptyFileset="true" classpathref="commons-io.classpath" targetVersion="${jdk.version}"/>
3737
</target>
3838

39+
<target name="testVersionDowngradeAndEquivalence">
40+
<ivy:cachepath organisation="commons-io" module="commons-io" revision="2.10.0"
41+
inline="true" conf="master" type="jar" pathid="commons-io.classpath" log="${ivy.logging}"/>
42+
<ac:foreach param="commons-version" target="-check-version-equivalent" inheritall="true" inheritrefs="true" delimiter="/" list="2.10.00/2.10/2.010"/>
43+
<ac:foreach param="commons-version" target="-check-version-downgrade" inheritall="true" inheritrefs="true" delimiter="/" list="2.10.1/2.010.1/2.10.02"/>
44+
</target>
45+
46+
<target name="-check-version-equivalent">
47+
<forbiddenapis bundledSignatures="commons-io-unsafe-${commons-version}" ignoreEmptyFileset="true" classpathref="commons-io.classpath" targetVersion="${jdk.version}"/>
48+
<au:assertLogContains level="info" text="Reading bundled API signatures: commons-io-unsafe-2.10.0"/>
49+
<au:assertLogDoesntContain level="warning" text="choosing next lower available signature"/>
50+
</target>
51+
52+
<target name="-check-version-downgrade">
53+
<forbiddenapis bundledSignatures="commons-io-unsafe-${commons-version}" ignoreEmptyFileset="true" classpathref="commons-io.classpath" targetVersion="${jdk.version}"/>
54+
<au:assertLogContains level="info" text="Reading bundled API signatures: commons-io-unsafe-2.10.0"/>
55+
<au:assertLogContains level="warning" text="Bundled signatures 'commons-io-unsafe-${commons-version}' not found, choosing next lower available signature: commons-io-unsafe-2.10.0"/>
56+
</target>
57+
3958
</project>
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
/*
2+
* (C) Copyright Uwe Schindler (Generics Policeman) and others.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package de.thetaphi.forbiddenapis;
18+
19+
import static org.junit.Assert.assertTrue;
20+
21+
import org.junit.Test;
22+
23+
public final class VersionCompareTest {
24+
25+
@Test
26+
public void testVersionCompare() throws Exception {
27+
assertVersionEquals("1.0.0", "1.0.0");
28+
assertVersionEquals("1.0.0", "1.00.0");
29+
assertVersionEquals("1.0.0", "01.00.0");
30+
assertVersionEquals("1.0.0", "1.0");
31+
assertVersionEquals("1.0.0", "1");
32+
33+
assertVersionLess("1.0.0", "2.0");
34+
assertVersionLess("1.0.0", "1.1");
35+
assertVersionLess("1", "1.1.0");
36+
assertVersionLess("1.0", "1.1.0");
37+
assertVersionLess("1.2", "1.10.0");
38+
39+
assertVersionGreater("1.1.0", "1.0.0");
40+
assertVersionGreater("1.1.0", "1.0");
41+
assertVersionGreater("1.1.0", "1");
42+
assertVersionGreater("2", "1");
43+
assertVersionGreater("2.0", "1");
44+
assertVersionGreater("2.1.1", "2.1");
45+
assertVersionGreater("1.11", "1.2.0");
46+
assertVersionGreater("1.11", "1.02.0");
47+
}
48+
49+
private void assertVersionEquals(String v1, String v2) {
50+
assertTrue(v1 + " <> " + v2, VersionCompare.compareVersion(v1, v2) == 0);
51+
assertTrue(v1 + " <> " + v2, VersionCompare.VERSION_COMPARATOR.compare(v1, v2) == 0);
52+
}
53+
54+
private void assertVersionLess(String v1, String v2) {
55+
assertTrue(v1 + " <> " + v2, VersionCompare.compareVersion(v1, v2) < 0);
56+
assertTrue(v1 + " <> " + v2, VersionCompare.VERSION_COMPARATOR.compare(v1, v2) < 0);
57+
}
58+
59+
private void assertVersionGreater(String v1, String v2) {
60+
assertTrue(v1 + " <> " + v2, VersionCompare.compareVersion(v1, v2) > 0);
61+
assertTrue(v1 + " <> " + v2, VersionCompare.VERSION_COMPARATOR.compare(v1, v2) > 0);
62+
}
63+
64+
@Test
65+
public void testBundledSignaturesCompare() throws Exception {
66+
assertBsEquals("foo-1.0.0", "foo-1.00.0");
67+
assertBsEquals("foo-1.0.0", "foo-1.0");
68+
assertBsEquals("foo-1.0.0", "foo-01");
69+
70+
assertBsLess("foo-1.0.0", "foo-2.0");
71+
assertBsLess("foo-1.2.0", "foo-10.1");
72+
assertBsLess("foo-1", "foo-1.2.0");
73+
assertBsLess("foo-1.2", "foo-1.010.0");
74+
assertBsLess("foo-2", "foo-10");
75+
76+
assertBsLess("bar-1.0", "foo-1.0");
77+
assertBsLess("bar-1.0", "foo-1.1");
78+
assertBsLess("bar-1", "foo-0");
79+
assertBsLess("bar-1.0", "foo-0");
80+
assertBsLess("bar-1.0", "foo");
81+
assertBsLess("bar", "foo-1.0");
82+
83+
assertBsGreater("foo-1.1.0", "foo-1.0.0");
84+
assertBsGreater("foo-1.1.0", "foo-1.0");
85+
assertBsGreater("foo-1.1.0", "foo-1");
86+
assertBsGreater("foo-2", "foo-1");
87+
assertBsGreater("foo-2.0", "foo-1");
88+
assertBsGreater("foo-2.1.1", "foo-2.1");
89+
assertBsGreater("foo-1.11", "foo-1.2.0");
90+
assertBsGreater("foo-1.11", "foo-1.02.0");
91+
92+
assertBsGreater("foo-1", "bar-1");
93+
assertBsGreater("foo-0", "bar-1");
94+
assertBsGreater("foo", "bar");
95+
assertBsGreater("foo-2.1", "bar");
96+
assertBsGreater("foo", "bar-1.1");
97+
}
98+
99+
private void assertBsEquals(String v1, String v2) {
100+
assertTrue(v1 + " <> " + v2, VersionCompare.compareBundledSignatures(v1, v2) == 0);
101+
assertTrue(v1 + " <> " + v2, VersionCompare.BUNDLED_SIGNATURES_COMPARATOR.compare(v1, v2) == 0);
102+
}
103+
104+
private void assertBsLess(String v1, String v2) {
105+
assertTrue(v1 + " <> " + v2, VersionCompare.compareBundledSignatures(v1, v2) < 0);
106+
assertTrue(v1 + " <> " + v2, VersionCompare.BUNDLED_SIGNATURES_COMPARATOR.compare(v1, v2) < 0);
107+
}
108+
109+
private void assertBsGreater(String v1, String v2) {
110+
assertTrue(v1 + " <> " + v2, VersionCompare.compareBundledSignatures(v1, v2) > 0);
111+
assertTrue(v1 + " <> " + v2, VersionCompare.BUNDLED_SIGNATURES_COMPARATOR.compare(v1, v2) > 0);
112+
}
113+
114+
}

0 commit comments

Comments
 (0)