Skip to content

Allow excluding of JDK signature checking in Jakarta EE Platform TCK testing #2

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

Merged
merged 2 commits into from
Apr 13, 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
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,14 @@ then try:

with the action option set to `strictcheck` the plugin will detect any API change and fail even if it is compatible.

## Relax verification of JDK signatures

There are some cases where avoiding the verification of certain JDK classes entirely or their signatures can improve the ability to verify your API on different JDK versions.
The `-IgnoreJDKClass` option can be used to specify a set of JDK classes that can benefit from relaxed signature verification rules when it comes to dealing with JDK
specific signature changes introduced by a later JDK version. As an example, a Signature file with @java.lang.Deprecated annotations from JDK8 may be seeing verification failures on JDK9+
due to `default` fields being added to @Deprecated. With `-IgnoreJDKClass java.lang.Deprecated` enabled, verification of the @Deprecated will only check that the tested class member has the
@Deprecated class but no verification of the @Deprecated signature will be performed.

## History

This tool is based on original [SigTest](https://wiki.openjdk.java.net/display/CodeTools/sigtest) sources,
Expand Down
48 changes: 42 additions & 6 deletions src/main/java/com/sun/tdk/signaturetest/SignatureTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ public class SignatureTest extends SigTest {
public static final String NOMERGE_OPTION = "-NoMerge";
public static final String WRITE_OPTION = "-Write";
public static final String UPDATE_FILE_OPTION = "-Update";
public static final String EXCLUDE_JDK_CLASS_OPTION = "-IgnoreJDKClass";

private String logName = null;
private String outFormat = null;
Expand Down Expand Up @@ -241,6 +242,15 @@ public class SignatureTest extends SigTest {

protected Exclude exclude;
private int readMode = MultipleFileReader.MERGE_MODE;
private JDKExclude jdkExclude = new DefaultJDKExclude();
/**
* List of names of JDK classes and/or packages to be ignored along with subpackages.
*/
private static PackageGroup excludedJdkClasses = new PackageGroup(true);

public SignatureTest() {
normalizer = new ThrowsNormalizer(jdkExclude);
}

/**
* Run the test using command-line; return status via numeric exit code.
Expand Down Expand Up @@ -325,7 +335,6 @@ private void correctConstants(final ClassDescription currentClass) {
*/
private boolean parseParameters(String[] args) {


CommandLineParser parser = new CommandLineParser(this, "-");

// Print help text only and exit.
Expand Down Expand Up @@ -374,7 +383,8 @@ private boolean parseParameters(String[] args) {
parser.addOption(UPDATE_FILE_OPTION, OptionInfo.option(1), optionsDecoder);
parser.addOption(MODE_OPTION, OptionInfo.option(1), optionsDecoder);
parser.addOption(ALLPUBLIC_OPTION, OptionInfo.optionalFlag(), optionsDecoder);

parser.addOption(EXCLUDE_JDK_CLASS_OPTION, OptionInfo.optionVariableParams(1, OptionInfo.UNLIMITED), optionsDecoder);
Copy link
Owner

@jtulach jtulach Apr 10, 2021

Choose a reason for hiding this comment

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

This option influences only command line interface. Neither integration with Maven or Ant! Are you running your checks from command line only?

Copy link
Author

Choose a reason for hiding this comment

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

This option influences only command line interface. Neither integration with Maven or Ant! Are you running your checks from command line only?

The Jakarta EE Platform TCK is tightly integrated with sigtest classes for running the signature tests via code like https://github.com/eclipse-ee4j/jakartaee-tck/blob/master/src/com/sun/ts/tests/signaturetest/SigTestDriver.java#L136

I'll give feedback on what I am seeing now with #3 on the pr.


parser.addOption(VERBOSE_OPTION, OptionInfo.optionalFlag(), optionsDecoder);

parser.addOption(HELP_OPTION, OptionInfo.optionalFlag(), optionsDecoder);
Expand Down Expand Up @@ -494,6 +504,8 @@ public void decodeOptions(String optionName, String[] args) throws CommandLinePa
isSupersettingEnabled = true;
} else if (optionName.equalsIgnoreCase(NOMERGE_OPTION)) {
readMode = MultipleFileReader.CLASSPATH_MODE;
} else if (optionName.equalsIgnoreCase(EXCLUDE_JDK_CLASS_OPTION)) {
excludedJdkClasses.addPackages(CommandLineParser.parseListOption(args));
} else {
super.decodeCommonOptions(optionName, args);
}
Expand Down Expand Up @@ -526,6 +538,7 @@ protected void usage() {
sb.append(nl).append(i18n.getString("SignatureTest.usage.exclude", EXCLUDE_OPTION));
sb.append(nl).append(i18n.getString("SignatureTest.usage.nomerge", NOMERGE_OPTION));
sb.append(nl).append(i18n.getString("SignatureTest.usage.update", UPDATE_FILE_OPTION));
sb.append(nl).append(i18n.getString("SignatureTest.usage.excludejdkclass", EXCLUDE_JDK_CLASS_OPTION));
sb.append(nl).append(i18n.getString("SignatureTest.usage.apiversion", APIVERSION_OPTION));
sb.append(nl).append(i18n.getString("SignatureTest.usage.checkvalue", CHECKVALUE_OPTION));
sb.append(nl).append(i18n.getString("SignatureTest.usage.formatplain", FORMATPLAIN_OPTION));
Expand Down Expand Up @@ -709,7 +722,7 @@ private boolean check() {

testableHierarchy = new ClassHierarchyImpl(loader, trackMode);

testableMCBuilder = new MemberCollectionBuilder(this);
testableMCBuilder = new MemberCollectionBuilder(this, jdkExclude);

signatureClassesHierarchy = new ClassHierarchyImpl(in, trackMode);

Expand All @@ -730,7 +743,7 @@ else if ((outFormat != null) && FORMAT_BACKWARD.equals(outFormat))
boolean buildMembers = in.isFeatureSupported(FeaturesHolder.BuildMembers);
MemberCollectionBuilder sigfileMCBuilder = null;
if (buildMembers) {
sigfileMCBuilder = new MemberCollectionBuilder(this);
sigfileMCBuilder = new MemberCollectionBuilder(this, jdkExclude);
}

// Reading the sigfile: main loop.
Expand Down Expand Up @@ -1388,7 +1401,7 @@ private void trackMember(ClassDescription parentReq, ClassDescription parentFou,
}
}

if (!isSupersettingEnabled && found != null) {
if (!isSupersettingEnabled && found != null && !jdkExclude.isJdkClass(found.getDeclaringClassName())) {
errorManager.addError(MessageType.getAddedMessageType(found.getMemberType()), name, found.getMemberType(), found.toString(), found);
if (logger.isLoggable(Level.FINE)) {
logger.fine("added :-( " + found);
Expand Down Expand Up @@ -1423,8 +1436,22 @@ private void checkAnnotations(MemberDescription base, MemberDescription test) {
int bPos = 0;
int tPos = 0;

if (base != null && jdkExclude.isJdkClass(base.getDeclaringClassName())) {
return;
}

if (test != null && jdkExclude.isJdkClass(test.getDeclaringClassName())) {
return;
}

while ((bPos < bl) && (tPos < tl)) {
int comp = baseAnnotList[bPos].compareTo(testAnnotList[tPos]);
int comp = 0;
if (jdkExclude.isJdkClass(baseAnnotList[bPos].getName()) ||
jdkExclude.isJdkClass(testAnnotList[bPos].getName())) {
comp = baseAnnotList[bPos].getName().compareTo(testAnnotList[tPos].getName());
} else {
comp = baseAnnotList[bPos].compareTo(testAnnotList[tPos]);
}
if (comp < 0) {
reportError(base, baseAnnotList[bPos].toString(), false);
bPos++;
Expand Down Expand Up @@ -1539,4 +1566,13 @@ public String report() {
}

}

static class DefaultJDKExclude implements JDKExclude {

@Override
public boolean isJdkClass(String name) {
return name != null &&
excludedJdkClasses.checkName(name);
}
}
}
26 changes: 22 additions & 4 deletions src/main/java/com/sun/tdk/signaturetest/core/ClassCorrector.java
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,12 @@ public class ClassCorrector implements Transformer {

protected ClassHierarchy classHierarchy = null;
private Log log;

private JDKExclude jdkExclude = new JDKExclude() {
@Override
public boolean isJdkClass(String name) {
return false;
}
};

/**
* Selftracing can be turned on by setting FINER level
Expand All @@ -78,12 +83,24 @@ public class ClassCorrector implements Transformer {

private static I18NResourceBundle i18n = I18NResourceBundle.getBundleForClass(ClassCorrector.class);

public ClassCorrector(Log log) {
public ClassCorrector(Log log, JDKExclude jdkExclude) {
this.jdkExclude = jdkExclude != null ? jdkExclude :
new JDKExclude() {
@Override
public boolean isJdkClass(String name) {
return false;
}
};
this.log = log;
// not configured externally
if(logger.getLevel() == null) {
logger.setLevel(Level.OFF);
}

}

public ClassCorrector(Log log) {
this(log, null);
}


Expand Down Expand Up @@ -181,7 +198,7 @@ private void replaceInvisibleExceptions(MemberDescription mr) throws ClassNotFou
} else
exceptionName = throwables.substring(startPos);

if (isInvisibleClass(exceptionName)) {
if (!jdkExclude.isJdkClass(exceptionName) && isInvisibleClass(exceptionName)) {
List supers = classHierarchy.getSuperClasses(exceptionName);
exceptionName = findVisibleReplacement(exceptionName, supers, "java.lang.Throwable", true);
mustCorrect = true;
Expand Down Expand Up @@ -701,7 +718,8 @@ private boolean isInvisibleClass(String fqname) {

if (fqname.startsWith("?"))
return false;

if (jdkExclude.isJdkClass(fqname))
return false;
String pname = ClassCorrector.stripArrays(ClassCorrector.stripGenerics(fqname));

if (PrimitiveTypes.isPrimitive(pname))
Expand Down
44 changes: 44 additions & 0 deletions src/main/java/com/sun/tdk/signaturetest/core/JDKExclude.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* $Id:$
*
* Copyright 2021 Sun Microsystems, Inc. All Rights Reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Sun designates this
* particular file as subject to the "Classpath" exception as provided
* by Sun in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
* CA 95054 USA or visit www.sun.com if you need additional information or
* have any questions.
*/

package com.sun.tdk.signaturetest.core;

/**
* JDKExclude represents the JDK classes to be excluded from signature testing.
*
* @author Scott Marlow
*/
public interface JDKExclude {

/**
* Check for JDK classes that should be excluded from signature testing.
* @param name is class name (with typical dot separators) to check.
* @return true if the class should be excluded from signature testing.
*/
boolean isJdkClass(String name);

}
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,16 @@ public class MemberCollectionBuilder {
*/
static Logger logger = Logger.getLogger(MemberCollectionBuilder.class.getName());

public MemberCollectionBuilder(Log log, JDKExclude jdkExclude) {
this.cc = jdkExclude == null ? new ClassCorrector(log) : new ClassCorrector(log, jdkExclude);
this.log = log;

// not configured externally
if (logger.getLevel() == null) {
logger.setLevel(Level.OFF);
}
}

public MemberCollectionBuilder(Log log) {
this.cc = new ClassCorrector(log);
this.log = log;
Expand Down Expand Up @@ -764,4 +774,4 @@ public void addMethods(MemberDescription[] methods) {
for (int i = 0; i < methods.length; ++i)
addMethod((MethodDescr) methods[i]);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,12 @@
*/
public class ThrowsNormalizer {

public ThrowsNormalizer() {

}
public ThrowsNormalizer(JDKExclude jdkExclude) {
this.jdkExclude = jdkExclude;
}
public void normThrows(ClassDescription c, boolean removeJLE) throws ClassNotFoundException {

ClassHierarchy h = c.getClassHierarchy();
Expand Down Expand Up @@ -93,7 +99,8 @@ private void normThrows(ClassHierarchy h, MemberDescription mr, boolean removeJL
if (s == null)
continue;

if (s.charAt(0) != '{' /* if not generic */) {

if (!jdkExclude.isJdkClass(s) && s.charAt(0) != '{' /* if not generic */) {

if (checkException(h, s, "java.lang.RuntimeException") || (removeJLE && checkException(h, s, "java.lang.Error"))) {
xthrows.set(i, null);
Expand Down Expand Up @@ -145,4 +152,10 @@ private void normThrows(ClassHierarchy h, MemberDescription mr, boolean removeJL

private List/*String*/ xthrows = new ArrayList();
private StringBuffer sb = new StringBuffer();
private JDKExclude jdkExclude = new JDKExclude() {
@Override
public boolean isJdkClass(String name) {
return false;
}
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ SignatureTest.usage.files={0} <filelist> Specify list of signature files. Use {1
SignatureTest.usage.package={0} <name> Specify package to be tested along with subpackages
SignatureTest.usage.packagewithoutsubpackages={0} <name> Specify package to be tested excluding subpackages
SignatureTest.usage.exclude={0} <name> Specify package or class, which is not required to be tested
SignatureTest.usage.excludejdkclass={0} <name> Specify JDK package or class, which is not required to be tested
SignatureTest.usage.classpath={0} <path> Specify search path for tested classes in static mode
SignatureTest.usage.out={0} <file> Specify report file name
SignatureTest.usage.static={0} Run SignatureTest in static mode (default: reflection)
Expand Down