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

Allow to silently ignore signatures if the class is not found #164

Merged
merged 6 commits into from
Apr 23, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
21 changes: 20 additions & 1 deletion src/main/docs/ant-task.html
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,15 @@ <h2>Parameters</h2>
<td>failOnUnresolvableSignatures</td>
<td><code>boolean</code></td>
<td><code>true</code></td>
<td>Fail the build if a signature is not resolving. If this parameter is set to <code>false</code>, then such signatures are silently ignored.</td>
<td>Fail the build if a signature is not resolving. If this parameter is set to
to false, then such signatures are ignored.<br>
When disabling this setting, the task still prints a warning to inform the user about
broken signatures. This cannot be disabled. There is a second setting
<code>ignoreSignaturesOfMissingClasses</code> that can be used to silently ignore
signatures that refer to methods or field in classes that are not on classpath,
e.g. This is useful in multi-module builds where a common set of signatures is used,
that are not part of every sub-modules dependencies.<br>
<strong>Deprecated.</strong> Use <code>ignoreSignaturesOfMissingClasses</code> instead.</td>
</tr>

<tr>
Expand All @@ -137,6 +145,17 @@ <h2>Parameters</h2>
<td>Ignore empty fileset/resource collection and print a warning instead.</td>
</tr>

<tr>
<td>ignoreSignaturesOfMissingClasses</td>
<td><code>boolean</code></td>
<td><code>false</code></td>
<td>If a class is missing while parsing signatures files, all methods and fields from this
class are silently ignored. This is useful in multi-module
projects where only some modules have the dependency to which the signature file(s) apply.
This settings prints no warning at all, so verify the signatures at least once with
full dependencies.</td>
</tr>

<tr>
<td>suppressAnnotation</td>
<td><code>class name</code></td>
Expand Down
6 changes: 6 additions & 0 deletions src/main/java/de/thetaphi/forbiddenapis/Checker.java
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ public static enum Option {
FAIL_ON_MISSING_CLASSES,
FAIL_ON_VIOLATION,
FAIL_ON_UNRESOLVABLE_SIGNATURES,
IGNORE_SIGNATURES_OF_MISSING_CLASSES,
DISABLE_CLASSLOADING_CACHE
}

Expand Down Expand Up @@ -341,6 +342,11 @@ public boolean hasNoSignatures() {
return forbiddenSignatures.hasNoSignatures();
}

/** Returns if no signatures files / inline signatures were parsed */
public boolean noSignaturesFilesParsed() {
return forbiddenSignatures.noSignaturesFilesParsed();
}

/** Parses and adds a class from the given stream to the list of classes to check. Closes the stream when parsed (on Exception, too)! Does not log anything. */
public void addClassToCheck(final InputStream in, String name) throws IOException {
final ClassReader reader;
Expand Down
3 changes: 3 additions & 0 deletions src/main/java/de/thetaphi/forbiddenapis/Constants.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ public interface Constants {

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

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

final Type DEPRECATED_TYPE = Type.getType(Deprecated.class);
final String DEPRECATED_DESCRIPTOR = DEPRECATED_TYPE.getDescriptor();

Expand Down
24 changes: 19 additions & 5 deletions src/main/java/de/thetaphi/forbiddenapis/Signatures.java
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ private UnresolvableReporting(boolean reportClassNotFound) {

private final RelatedClassLookup lookup;
private final Logger logger;
private final boolean failOnUnresolvableSignatures;
private final boolean failOnUnresolvableSignatures, ignoreSignaturesOfMissingClasses;

/** Key is used to lookup forbidden signature in following formats. Keys are generated by the corresponding
* {@link #getKey(String)} (classes), {@link #getKey(String, Method)} (methods),
Expand All @@ -90,14 +90,18 @@ private UnresolvableReporting(boolean reportClassNotFound) {

/** if enabled, the bundled signature to enable heuristics for detection of non-portable runtime calls is used */
private boolean forbidNonPortableRuntime = false;

/** number of files that were interpreted as signatures file. If 0, no (bundled) signatures files were added at all */
private int numberOfFiles = 0;

public Signatures(Checker checker) {
this(checker, checker.logger, checker.options.contains(Option.FAIL_ON_UNRESOLVABLE_SIGNATURES));
this(checker, checker.logger, checker.options.contains(Option.IGNORE_SIGNATURES_OF_MISSING_CLASSES), checker.options.contains(Option.FAIL_ON_UNRESOLVABLE_SIGNATURES));
}

public Signatures(RelatedClassLookup lookup, Logger logger, boolean failOnUnresolvableSignatures) {
public Signatures(RelatedClassLookup lookup, Logger logger, boolean ignoreSignaturesOfMissingClasses, boolean failOnUnresolvableSignatures) {
this.lookup = lookup;
this.logger = logger;
this.ignoreSignaturesOfMissingClasses = ignoreSignaturesOfMissingClasses;
this.failOnUnresolvableSignatures = failOnUnresolvableSignatures;
}

Expand Down Expand Up @@ -170,6 +174,9 @@ private void addSignature(final String line, final String defaultMessage, final
try {
c = lookup.getClassFromClassLoader(clazz);
} catch (ClassNotFoundException cnfe) {
if (this.ignoreSignaturesOfMissingClasses) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the main point doing the magic!

return;
}
if (report.reportClassNotFound) {
report.parseFailed(logger, String.format(Locale.ENGLISH, "Class '%s' not found on classpath", cnfe.getMessage()), signature);
} else {
Expand Down Expand Up @@ -234,6 +241,7 @@ private void addBundledSignatures(String name, String jdkTargetVersion, boolean
}
if (BS_JDK_NONPORTABLE.equals(name)) {
if (logging) logger.info("Reading bundled API signatures: " + name);
numberOfFiles++;
forbidNonPortableRuntime = true;
return;
}
Expand All @@ -253,11 +261,12 @@ private void addBundledSignatures(String name, String jdkTargetVersion, boolean
parseSignaturesStream(in, true, missingClasses);
}

private void parseSignaturesStream(InputStream in, boolean allowBundled, Set<String> missingClasses) throws IOException,ParseException {
parseSignaturesFile(new InputStreamReader(in, "UTF-8"), allowBundled, missingClasses);
private void parseSignaturesStream(InputStream in, boolean isBundled, Set<String> missingClasses) throws IOException,ParseException {
parseSignaturesFile(new InputStreamReader(in, "UTF-8"), isBundled, missingClasses);
}

private void parseSignaturesFile(Reader reader, boolean isBundled, Set<String> missingClasses) throws IOException,ParseException {
numberOfFiles++;
try (final BufferedReader r = new BufferedReader(reader)) {
String line, defaultMessage = null;
UnresolvableReporting reporter = failOnUnresolvableSignatures ? UnresolvableReporting.FAIL : UnresolvableReporting.WARNING;
Expand Down Expand Up @@ -314,6 +323,11 @@ public boolean hasNoSignatures() {
(forbidNonPortableRuntime ? 1 : 0);
}

/** Returns if no signatures files / inline signatures were parsed */
public boolean noSignaturesFilesParsed() {
return numberOfFiles == 0;
}

/** Returns if bundled signature to enable heuristics for detection of non-portable runtime calls is used */
public boolean isNonPortableRuntimeForbidden() {
return this.forbidNonPortableRuntime;
Expand Down
40 changes: 36 additions & 4 deletions src/main/java/de/thetaphi/forbiddenapis/ant/AntTask.java
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ public class AntTask extends Task implements Constants {
private boolean restrictClassFilename = true;
private boolean failOnMissingClasses = true;
private boolean failOnUnresolvableSignatures = true;
private boolean ignoreSignaturesOfMissingClasses = false;
private boolean failOnViolation = true;
private boolean ignoreEmptyFileset = false;
private String targetVersion = null;
Expand Down Expand Up @@ -108,7 +109,12 @@ public void info(String msg) {
final EnumSet<Checker.Option> options = EnumSet.noneOf(Checker.Option.class);
if (failOnMissingClasses) options.add(FAIL_ON_MISSING_CLASSES);
if (failOnViolation) options.add(FAIL_ON_VIOLATION);
if (failOnUnresolvableSignatures) options.add(FAIL_ON_UNRESOLVABLE_SIGNATURES);
if (failOnUnresolvableSignatures) {
options.add(FAIL_ON_UNRESOLVABLE_SIGNATURES);
} else {
log.warn(DEPRECATED_WARN_FAIL_ON_UNRESOLVABLE_SIGNATURES);
}
if (ignoreSignaturesOfMissingClasses) options.add(IGNORE_SIGNATURES_OF_MISSING_CLASSES);
if (disableClassloadingCache) options.add(DISABLE_CLASSLOADING_CACHE);
final Checker checker = new Checker(log, loader, options);

Expand Down Expand Up @@ -169,7 +175,12 @@ public void info(String msg) {
}

if (checker.hasNoSignatures()) {
throw new BuildException("No API signatures found; use signaturesFile=, <signatures*/>, <bundledSignatures/> or inner text to define those!");
if (checker.noSignaturesFilesParsed()) {
throw new BuildException("No API signatures found; use signaturesFile=, <signatures*/>, <bundledSignatures/> or inner text to define those!");
} else {
log.info("Skipping execution because no API signatures are available.");
return;
}
}

log.info("Loading classes to check...");
Expand Down Expand Up @@ -325,13 +336,34 @@ public void setFailOnMissingClasses(boolean failOnMissingClasses) {

/**
* Fail the build if a signature is not resolving. If this parameter is set to
* to false, then such signatures are silently ignored.
* Defaults to {@code true}.
* to false, then such signatures are ignored. Defaults to {@code true}.
* <p>When disabling this setting, the task still prints a warning to inform the user about
* broken signatures. This cannot be disabled. There is a second setting
* {@link #setIgnoreMissingSignaturesClasses()} that can be used to silently ignore
* signatures that refer to methods or field in classes that are not on classpath,
* e.g. This is useful in multi-module builds where a common set of signatures is used,
* that are not part of every sub-modules dependencies.
* @see #setIgnoreMissingSignaturesClasses()
* @deprecated Use {@link #setIgnoreSignaturesOfMissingClasses(boolean)} instead.
*/
@Deprecated
public void setFailOnUnresolvableSignatures(boolean failOnUnresolvableSignatures) {
this.failOnUnresolvableSignatures = failOnUnresolvableSignatures;
}

/**
* If a class is missing while parsing signatures files, all methods and fields from this
* class are silently ignored. This is useful in multi-module
* projects where only some modules have the dependency to which the signature file(s) apply.
* This settings prints no warning at all, so verify the signatures at least once with
* full dependencies.
* Defaults to {@code false}.
* @since 3.0
*/
public void setIgnoreSignaturesOfMissingClasses(boolean ignoreSignaturesOfMissingClasses) {
this.ignoreSignaturesOfMissingClasses = ignoreSignaturesOfMissingClasses;
}

/** Automatically restrict resource names included to files with a name ending in '.class'.
* This makes filesets easier, as the includes="**&#47;*.class" is not needed.
* Defaults to {@code true}.
Expand Down
28 changes: 21 additions & 7 deletions src/main/java/de/thetaphi/forbiddenapis/cli/CliMain.java
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
public final class CliMain implements Constants {

private final Option classpathOpt, dirOpt, includesOpt, excludesOpt, signaturesfileOpt, bundledsignaturesOpt, suppressannotationsOpt,
allowmissingclassesOpt, allowunresolvablesignaturesOpt, versionOpt, helpOpt;
allowmissingclassesOpt, ignoresignaturesofmissingclassesOpt, allowunresolvablesignaturesOpt, versionOpt, helpOpt;
private final CommandLine cmd;

private static final Logger LOG = StdIoLogger.INSTANCE;
Expand Down Expand Up @@ -129,8 +129,12 @@ public CliMain(String... args) throws ExitException {
.desc("don't fail if a referenced class is missing on classpath")
.longOpt("allowmissingclasses")
.build());
options.addOption(ignoresignaturesofmissingclassesOpt = Option.builder()
.desc("if a class is missing while parsing signatures files, all methods and fields from this class are silently ignored")
.longOpt("ignoresignaturesofmissingclasses")
.build());
options.addOption(allowunresolvablesignaturesOpt = Option.builder()
.desc("don't fail if a signature is not resolving")
.desc("DEPRECATED: don't fail if a signature is not resolving")
.longOpt("allowunresolvablesignatures")
.build());

Expand Down Expand Up @@ -212,7 +216,12 @@ public void run() throws ExitException {
try (final URLClassLoader loader = URLClassLoader.newInstance(urls, ClassLoader.getSystemClassLoader())) {
final EnumSet<Checker.Option> options = EnumSet.of(FAIL_ON_VIOLATION);
if (!cmd.hasOption(allowmissingclassesOpt.getLongOpt())) options.add(FAIL_ON_MISSING_CLASSES);
if (!cmd.hasOption(allowunresolvablesignaturesOpt.getLongOpt())) options.add(FAIL_ON_UNRESOLVABLE_SIGNATURES);
if (cmd.hasOption(allowunresolvablesignaturesOpt.getLongOpt())) {
LOG.warn(DEPRECATED_WARN_FAIL_ON_UNRESOLVABLE_SIGNATURES);
} else {
options.add(FAIL_ON_UNRESOLVABLE_SIGNATURES);
}
if (cmd.hasOption(ignoresignaturesofmissingclassesOpt.getLongOpt())) options.add(IGNORE_SIGNATURES_OF_MISSING_CLASSES);
final Checker checker = new Checker(LOG, loader, options);

if (!checker.isSupportedJDK) {
Expand Down Expand Up @@ -267,10 +276,15 @@ public void run() throws ExitException {
}

if (checker.hasNoSignatures()) {
throw new ExitException(EXIT_ERR_CMDLINE, String.format(Locale.ENGLISH,
"No API signatures found; use parameters '--%s' and/or '--%s' to specify those!",
bundledsignaturesOpt.getLongOpt(), signaturesfileOpt.getLongOpt()
));
if (checker.noSignaturesFilesParsed()) {
throw new ExitException(EXIT_ERR_CMDLINE, String.format(Locale.ENGLISH,
"No API signatures found; use parameters '--%s' and/or '--%s' to specify those!",
bundledsignaturesOpt.getLongOpt(), signaturesfileOpt.getLongOpt()
));
} else {
LOG.info("Skipping execution because no API signatures are available.");
return;
}
}

try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -258,20 +258,47 @@ public void setFailOnMissingClasses(boolean failOnMissingClasses) {

/**
* Fail the build if a signature is not resolving. If this parameter is set to
* to false, then such signatures are silently ignored. This is useful in multi-module Maven
* projects where only some modules have the dependency to which the signature file(s) apply.
* Defaults to {@code true}.
* to false, then such signatures are ignored. Defaults to {@code true}.
* <p>When disabling this setting, the task still prints a warning to inform the user about
* broken signatures. This cannot be disabled. There is a second setting
* {@link #getIgnoreSignaturesOfMissingClasses()} that can be used to silently ignore
* signatures that refer to methods or field in classes that are not on classpath,
* e.g. This is useful in multi-module Gradle builds where a common set of signatures is used,
* that are not part of every sub-modules dependencies.
* @see #getIgnoreSignaturesOfMissingClasses()
* @deprecated Use {@link #getIgnoreSignaturesOfMissingClasses()} instead.
*/
@Input
@Deprecated
public boolean getFailOnUnresolvableSignatures() {
return data.failOnUnresolvableSignatures;
}

/** @see #getFailOnUnresolvableSignatures */
@Deprecated
public void setFailOnUnresolvableSignatures(boolean failOnUnresolvableSignatures) {
data.failOnUnresolvableSignatures = failOnUnresolvableSignatures;
}

/**
* If a class is missing while parsing signatures files, all methods and fields from this
* class are silently ignored. This is useful in multi-module Gradle
* projects where only some modules have the dependency to which the signature file(s) apply.
* This settings prints no warning at all, so verify the signatures at least once with
* full dependencies.
* Defaults to {@code false}.
* @since 3.0
*/
@Input
public boolean getIgnoreSignaturesOfMissingClasses() {
return data.ignoreSignaturesOfMissingClasses;
}

/** @see #getFailOnUnresolvableSignatures */
public void setIgnoreSignaturesOfMissingClasses(boolean ignoreSignaturesOfMissingClasses) {
data.ignoreSignaturesOfMissingClasses = ignoreSignaturesOfMissingClasses;
}

/**
* {@inheritDoc}
* <p>
Expand Down Expand Up @@ -492,7 +519,12 @@ public void info(String msg) {
final EnumSet<Checker.Option> options = EnumSet.noneOf(Checker.Option.class);
if (getFailOnMissingClasses()) options.add(FAIL_ON_MISSING_CLASSES);
if (!getIgnoreFailures()) options.add(FAIL_ON_VIOLATION);
if (getFailOnUnresolvableSignatures()) options.add(FAIL_ON_UNRESOLVABLE_SIGNATURES);
if (getFailOnUnresolvableSignatures()) {
options.add(FAIL_ON_UNRESOLVABLE_SIGNATURES);
} else {
log.warn(DEPRECATED_WARN_FAIL_ON_UNRESOLVABLE_SIGNATURES);
}
if (getIgnoreSignaturesOfMissingClasses()) options.add(IGNORE_SIGNATURES_OF_MISSING_CLASSES);
if (getDisableClassloadingCache()) options.add(DISABLE_CLASSLOADING_CACHE);
final Checker checker = new Checker(log, loader, options);

Expand Down Expand Up @@ -552,7 +584,7 @@ public void info(String msg) {
}

if (checker.hasNoSignatures()) {
if (options.contains(FAIL_ON_UNRESOLVABLE_SIGNATURES)) {
if (checker.noSignaturesFilesParsed()) {
throw new InvalidUserDataException("No API signatures found; use properties 'signatures', 'bundledSignatures', 'signaturesURLs', and/or 'signaturesFiles' to define those!");
} else {
log.info("Skipping execution because no API signatures are available.");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ public CheckForbiddenApisExtension(Project project) {
failOnMissingClasses = true,
failOnUnresolvableSignatures = true,
ignoreFailures = false,
ignoreSignaturesOfMissingClasses = false,
disableClassloadingCache = ForbiddenApisPlugin.DEFAULT_DISABLE_CLASSLOADING_CACHE;

}
Loading