Merge branch 'main' into issue-5287

This commit is contained in:
Andreas Dangel
2024-11-07 19:51:00 +01:00
17 changed files with 366 additions and 71 deletions

View File

@ -45,7 +45,7 @@ GEM
git (1.19.1)
addressable (~> 2.8)
rchardet (~> 1.8)
json (2.7.2)
json (2.7.5)
kramdown (2.4.0)
rexml
kramdown-parser-gfm (1.1.0)
@ -74,10 +74,10 @@ GEM
raabro (1.4.0)
racc (1.8.1)
rchardet (1.8.0)
rexml (3.3.8)
rexml (3.3.9)
rouge (4.4.0)
rufus-scheduler (3.9.1)
fugit (~> 1.1, >= 1.1.6)
rufus-scheduler (3.9.2)
fugit (~> 1.1, >= 1.11.1)
safe_yaml (1.0.5)
sawyer (0.9.2)
addressable (>= 2.3.5)
@ -102,4 +102,4 @@ DEPENDENCIES
safe_yaml
BUNDLED WITH
2.5.3
2.5.22

View File

@ -1,8 +1,9 @@
GEM
remote: https://rubygems.org/
specs:
activesupport (7.2.1)
activesupport (7.2.2)
base64
benchmark (>= 0.3)
bigdecimal
concurrent-ruby (~> 1.0, >= 1.3.1)
connection_pool (>= 2.2.5)
@ -15,6 +16,7 @@ GEM
addressable (2.8.7)
public_suffix (>= 2.0.2, < 7.0)
base64 (0.2.0)
benchmark (0.3.0)
bigdecimal (3.1.8)
coffee-script (2.4.1)
coffee-script-source
@ -34,9 +36,10 @@ GEM
ethon (0.16.0)
ffi (>= 1.15.0)
eventmachine (1.2.7)
execjs (2.9.1)
faraday (2.11.0)
execjs (2.10.0)
faraday (2.12.0)
faraday-net_http (>= 2.0, < 3.4)
json
logger
faraday-net_http (3.3.0)
net-http
@ -99,7 +102,7 @@ GEM
activesupport (>= 2)
nokogiri (>= 1.4)
http_parser.rb (0.8.0)
i18n (1.14.5)
i18n (1.14.6)
concurrent-ruby (~> 1.0)
jekyll (3.10.0)
addressable (~> 2.4)
@ -211,6 +214,7 @@ GEM
gemoji (>= 3, < 5)
html-pipeline (~> 2.2)
jekyll (>= 3.0, < 5.0)
json (2.7.5)
kramdown (2.4.0)
rexml
kramdown-parser-gfm (1.1.0)
@ -219,7 +223,7 @@ GEM
listen (3.9.0)
rb-fsevent (~> 0.10, >= 0.10.3)
rb-inotify (~> 0.9, >= 0.9.10)
logger (1.6.0)
logger (1.6.1)
mercenary (0.3.6)
minima (2.5.1)
jekyll (>= 3.5, < 5.0)
@ -240,8 +244,7 @@ GEM
rb-fsevent (0.11.2)
rb-inotify (0.11.1)
ffi (~> 1.0)
rexml (3.3.6)
strscan
rexml (3.3.9)
rouge (3.30.0)
rubyzip (2.3.2)
safe_yaml (1.0.5)
@ -255,7 +258,6 @@ GEM
faraday (>= 0.17.3, < 3)
securerandom (0.3.1)
simpleidn (0.2.3)
strscan (3.1.0)
terminal-table (1.8.0)
unicode-display_width (~> 1.1, >= 1.1.1)
typhoeus (1.4.1)
@ -264,7 +266,7 @@ GEM
concurrent-ruby (~> 1.0)
unicode-display_width (1.8.0)
uri (0.13.1)
webrick (1.8.2)
webrick (1.9.0)
PLATFORMS
x86_64-linux
@ -276,4 +278,4 @@ DEPENDENCIES
webrick
BUNDLED WITH
2.5.3
2.5.22

View File

@ -15,11 +15,20 @@ This is a {{ site.pmd.release_type }} release.
### 🚀 New and noteworthy
### 🐛 Fixed Issues
* ant
* [#1860](https://github.com/pmd/pmd/issues/1860): \[ant] Reflective access warnings on java > 9 and java < 17
* java
* [#5293](https://github.com/pmd/pmd/issues/5293): \[java] Deadlock when executing PMD in multiple threads
* java-performance
* [#5287](https://github.com/pmd/pmd/issues/5287): \[java] TooFewBranchesForSwitch false-positive with switch using list of case constants
### 🚨 API Changes
#### Deprecations
* pmd-xml
* {%jdoc xml::lang.xml.antlr4.XMLLexer %} is deprecated for removal. Use {%jdoc !!xml::lang.xml.ast.XMLLexer %}
instead (note different package `ast` instead of `antlr4`).
### ✨ External Contributions
{% endtocmaker %}

View File

@ -11,6 +11,7 @@ import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.charset.Charset;
@ -21,8 +22,6 @@ import java.util.List;
import java.util.Properties;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.apache.commons.lang3.reflect.MethodUtils;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.types.Parameter;
@ -200,10 +199,12 @@ public class Formatter {
if (console != null) {
// Since Java 22, this returns a console even for redirected streams.
// In that case, we need to check Console.isTerminal()
// https://docs.oracle.com/en/java/javase/22/docs/api/java.base/java/io/Console.html#isTerminal()
// See: JLine As The Default Console Provider (JDK-8308591)
try {
Boolean isTerminal = (Boolean) MethodUtils.invokeMethod(console, "isTerminal");
if (!isTerminal) {
Method method = Console.class.getMethod("isTerminal");
Object isTerminal = method.invoke(console);
if (isTerminal instanceof Boolean && !(Boolean) isTerminal) {
// stop here, we don't have an interactive console.
return null;
}
@ -211,39 +212,58 @@ public class Formatter {
// fall-through - we use a Java Runtime < 22.
}
// Maybe this is Java17+? Then there will be a public method charset()
// https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/io/Console.html#charset()
try {
Object res = FieldUtils.readDeclaredField(console, "cs", true);
if (res instanceof Charset) {
return ((Charset) res).name();
Method method = Console.class.getMethod("charset");
Object charset = method.invoke(console);
if (charset instanceof Charset) {
return ((Charset) charset).name();
}
} catch (InvocationTargetException | NoSuchMethodException | IllegalAccessException ignored) {
// fall-through
}
{
// try to use the system property "sun.jnu.encoding", which is the platform encoding.
// this property is not specified and might not always be available, but it is for
// openjdk 11: https://github.com/openjdk/jdk11u/blob/cee8535a9d3de8558b4b5028d68e397e508bef71/src/java.base/share/native/libjava/System.c#L384
// if it exists, we use it - this avoids illegal reflective access below.
String jnuEncoding = System.getProperty("sun.jnu.encoding");
if (jnuEncoding != null) {
return jnuEncoding;
}
}
// the following parts are accessing private/protected fields via reflection
// this should work with Java 8 and 11. With Java 11, you'll see warnings abouts
// illegal reflective access, see #1860. However, the access still works.
// Fall-Back 1: private field "cs" in java.io.Console
try {
Field field = Console.class.getDeclaredField("cs");
field.setAccessible(true);
Object csField = field.get(console);
if (csField instanceof Charset) {
return ((Charset) csField).name();
}
} catch (IllegalArgumentException | ReflectiveOperationException ignored) {
// fall-through
}
// Maybe this is Java17+? Then there will be
// https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/io/Console.html#charset()
// instead of the field "cs".
// Fall-Back 2: private native method "encoding()" in java.io.Console
try {
Method charsetMethod = Console.class.getDeclaredMethod("charset");
Charset charset = (Charset) charsetMethod.invoke(console);
return charset.name();
} catch (IllegalArgumentException | ReflectiveOperationException ignored) {
Method method = Console.class.getDeclaredMethod("encoding");
method.setAccessible(true);
Object encoding = method.invoke(console);
if (encoding instanceof String) {
return (String) encoding;
}
} catch (InvocationTargetException | NoSuchMethodException | IllegalAccessException ignored) {
// fall-through
}
return getNativeConsoleEncoding();
}
return null;
}
private static String getNativeConsoleEncoding() {
try {
Object res = MethodUtils.invokeStaticMethod(Console.class, "encoding");
if (res instanceof String) {
return (String) res;
}
} catch (IllegalArgumentException | ReflectiveOperationException ignored) {
// fall-through
}
// we couldn't determine the correct platform console encoding
return null;
}

View File

@ -5,6 +5,8 @@
package net.sourceforge.pmd.dist;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.containsStringIgnoringCase;
import static org.hamcrest.Matchers.not;
import java.io.File;
import java.io.IOException;
@ -40,9 +42,9 @@ class AntIT extends AbstractBinaryDistributionTest {
ExecutionResult result = runAnt(antBasepath, pmdHome, antTestProjectFolder);
result.assertExitCode(0)
.assertStdOut(containsString("BUILD SUCCESSFUL"));
// the no package rule
result.assertExitCode(0)
.assertStdOut(containsString("BUILD SUCCESSFUL"))
.assertStdOut(not(containsStringIgnoringCase("Illegal reflective access"))) // #1860
// the no package rule
.assertStdOut(containsString("NoPackage"));
}

View File

@ -84,12 +84,7 @@ final class ClassStub implements JClassSymbol, AsmStub, AnnotationOwner {
this.resolver = resolver;
this.names = new Names(internalName);
this.parseLock = new ParseLock() {
// note to devs: to debug the parsing logic you might have
// to replace the implementation of toString temporarily,
// otherwise an IDE could call toString just to show the item
// in the debugger view (which could cause parsing of the class file).
this.parseLock = new ParseLock("ClassStub:" + internalName) {
@Override
protected boolean doParse() throws IOException {
try (InputStream instream = loader.getInputStream()) {
@ -315,9 +310,9 @@ final class ClassStub implements JClassSymbol, AsmStub, AnnotationOwner {
}
@Override
public boolean isGeneric() {
public int getTypeParameterCount() {
parseLock.ensureParsed();
return signature.isGeneric();
return signature.getTypeParameterCount();
}
@Override

View File

@ -46,9 +46,9 @@ abstract class GenericSigBase<T extends JTypeParameterOwnerSymbol & AsmStub> {
protected List<JTypeVar> typeParameters;
private final ParseLock lock;
protected GenericSigBase(T ctx) {
protected GenericSigBase(T ctx, String parseLockName) {
this.ctx = ctx;
this.lock = new ParseLock() {
this.lock = new ParseLock(parseLockName) {
@Override
protected boolean doParse() {
GenericSigBase.this.doParse();
@ -81,7 +81,11 @@ abstract class GenericSigBase<T extends JTypeParameterOwnerSymbol & AsmStub> {
protected abstract boolean postCondition();
protected abstract boolean isGeneric();
protected abstract int getTypeParameterCount();
protected boolean isGeneric() {
return getTypeParameterCount() > 0;
}
public void setTypeParams(List<JTypeVar> tvars) {
assert this.typeParameters == null : "Type params were already parsed for " + this;
@ -105,6 +109,7 @@ abstract class GenericSigBase<T extends JTypeParameterOwnerSymbol & AsmStub> {
private static final String OBJECT_BOUND = ":" + OBJECT_SIG;
private final @Nullable String signature;
private final int typeParameterCount;
private @Nullable JClassType superType;
private List<JClassType> superItfs;
@ -116,8 +121,9 @@ abstract class GenericSigBase<T extends JTypeParameterOwnerSymbol & AsmStub> {
@Nullable String signature, // null if doesn't use generics in header
@Nullable String superInternalName, // null if this is the Object class
String[] interfaces) {
super(ctx);
super(ctx, "LazyClassSignature:" + ctx.getInternalName() + "[" + signature + "]");
this.signature = signature;
this.typeParameterCount = GenericTypeParameterCounter.determineTypeParameterCount(this.signature);
this.rawItfs = CollectionUtil.map(interfaces, ctx.getResolver()::resolveFromInternalNameCannotFail);
this.rawSuper = ctx.getResolver().resolveFromInternalNameCannotFail(superInternalName);
@ -157,8 +163,9 @@ abstract class GenericSigBase<T extends JTypeParameterOwnerSymbol & AsmStub> {
}
@Override
protected boolean isGeneric() {
return signature != null && TypeParamsParser.hasTypeParams(signature);
protected int getTypeParameterCount() {
// note: no ensureParsed() needed, the type parameters are counted eagerly
return typeParameterCount;
}
@Override
@ -206,6 +213,7 @@ abstract class GenericSigBase<T extends JTypeParameterOwnerSymbol & AsmStub> {
static class LazyMethodType extends GenericSigBase<ExecutableStub> implements TypeAnnotationReceiver {
private final @NonNull String signature;
private final int typeParameterCount;
private @Nullable TypeAnnotationSet receiverAnnotations;
private List<JTypeMirror> parameterTypes;
@ -233,8 +241,9 @@ abstract class GenericSigBase<T extends JTypeParameterOwnerSymbol & AsmStub> {
@Nullable String genericSig,
@Nullable String[] exceptions,
boolean skipFirstParam) {
super(ctx);
super(ctx, "LazyMethodType:" + (genericSig != null ? genericSig : descriptor));
this.signature = genericSig != null ? genericSig : descriptor;
this.typeParameterCount = GenericTypeParameterCounter.determineTypeParameterCount(genericSig);
// generic signatures already omit the synthetic param
this.skipFirstParam = skipFirstParam && genericSig == null;
this.rawExceptions = exceptions;
@ -288,8 +297,9 @@ abstract class GenericSigBase<T extends JTypeParameterOwnerSymbol & AsmStub> {
@Override
protected boolean isGeneric() {
return TypeParamsParser.hasTypeParams(signature);
protected int getTypeParameterCount() {
// note: no ensureParsed() needed, the type parameters are counted eagerly
return typeParameterCount;
}
void setParameterTypes(List<JTypeMirror> params) {

View File

@ -0,0 +1,36 @@
/*
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.lang.java.symbols.internal.asm;
import org.objectweb.asm.signature.SignatureReader;
import org.objectweb.asm.signature.SignatureVisitor;
class GenericTypeParameterCounter extends SignatureVisitor {
private int count;
GenericTypeParameterCounter() {
super(AsmSymbolResolver.ASM_API_V);
}
@Override
public void visitFormalTypeParameter(String name) {
count++;
}
public int getCount() {
return count;
}
static int determineTypeParameterCount(String signature) {
if (signature == null) {
return 0;
}
SignatureReader signatureReader = new SignatureReader(signature);
GenericTypeParameterCounter counter = new GenericTypeParameterCounter();
signatureReader.accept(counter);
return counter.getCount();
}
}

View File

@ -35,7 +35,7 @@ class ModuleStub implements JModuleSymbol, AsmStub, AnnotationOwner {
this.resolver = resolver;
this.moduleName = moduleName;
this.parseLock = new ParseLock() {
this.parseLock = new ParseLock("ModuleStub:" + moduleName) {
@Override
protected boolean doParse() throws IOException {
try (InputStream instream = loader.getInputStream()) {

View File

@ -17,15 +17,30 @@ abstract class ParseLock {
private static final Logger LOG = LoggerFactory.getLogger(ParseLock.class);
private volatile ParseStatus status = ParseStatus.NOT_PARSED;
private final String name;
protected ParseLock(String name) {
this.name = name;
}
public void ensureParsed() {
getFinalStatus();
}
private void logParseLockTrace(String prefix) {
if (LOG.isTraceEnabled()) {
LOG.trace("{} {}: {}", Thread.currentThread().getName(), String.format("%-15s", prefix), this);
}
}
private ParseStatus getFinalStatus() {
ParseStatus status = this.status;
if (!status.isFinished) {
logParseLockTrace("waiting on");
synchronized (this) {
logParseLockTrace("locked");
status = this.status;
if (status == ParseStatus.NOT_PARSED) {
this.status = ParseStatus.BEING_PARSED;
@ -54,6 +69,7 @@ abstract class ParseLock {
throw new IllegalStateException("Thread is reentering the parse lock");
}
}
logParseLockTrace("released");
}
return status;
}
@ -85,7 +101,7 @@ abstract class ParseLock {
@Override
public String toString() {
return "ParseLock{status=" + status + '}';
return "ParseLock{name=" + name + ",status=" + status + '}';
}
private enum ParseStatus {

View File

@ -0,0 +1,157 @@
/*
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.lang.java.symbols;
import static org.junit.jupiter.api.Assertions.assertAll;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import java.util.ArrayList;
import java.util.List;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.Timeout;
import net.sourceforge.pmd.lang.java.JavaParsingHelper;
import net.sourceforge.pmd.lang.java.ast.ASTClassType;
import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit;
/**
* Tests to help analyze [java] Deadlock when executing PMD in multiple threads #5293.
*
* @see <a href="https://github.com/pmd/pmd/issues/5293">[java] Deadlock when executing PMD in multiple threads #5293</a>
*/
class DeadlockTest {
abstract static class Outer<T> implements GenericInterface<Outer<T>, GenericClass<T>> {
// must be a nested class, that is reusing the type param T of the outer class
abstract static class Inner<T> {
Inner(Outer<T> grid) { }
}
}
static class GenericBaseClass<T> { }
interface GenericInterface<T, S> { }
abstract static class GenericClass<T> extends GenericBaseClass<Outer.Inner<T>> { }
@Test
@Timeout(2)
void parseWithoutDeadlock() throws InterruptedException {
/*
Deadlock:
t1 -> locks parse for Outer.Inner and waits for parse lock for Outer
├─ t1 waiting on : ParseLock{name=LazyClassSignature:net/sourceforge/pmd/lang/java/symbols/DeadlockTest$Outer$Inner[<T:Ljava/lang/Object;>Ljava/lang/Object;],status=NOT_PARSED}
└─ t1 locked : ParseLock{name=LazyClassSignature:net/sourceforge/pmd/lang/java/symbols/DeadlockTest$Outer$Inner[<T:Ljava/lang/Object;>Ljava/lang/Object;],status=NOT_PARSED}
└─ t1 waiting on : ParseLock{name=LazyClassSignature:net/sourceforge/pmd/lang/java/symbols/DeadlockTest$Outer[<T:Ljava/lang/Object;>Ljava/lang/Object;Lnet/sourceforge/pmd/lang/java/symbols/DeadlockTest$GenericInterface<Lnet/sourceforge/pmd/lang/java/symbols/DeadlockTest$Outer<TT;>;Lnet/sourceforge/pmd/lang/java/symbols/DeadlockTest$GenericClass<TT;>;>;],status=BEING_PARSED}
t2 -> locks parse for Outer, locks parse for GenericInterface and then waits for parse lock for Outer.Inner
├─ t2 waiting on : ParseLock{name=LazyClassSignature:net/sourceforge/pmd/lang/java/symbols/DeadlockTest$Outer[<T:Ljava/lang/Object;>Ljava/lang/Object;Lnet/sourceforge/pmd/lang/java/symbols/DeadlockTest$GenericInterface<Lnet/sourceforge/pmd/lang/java/symbols/DeadlockTest$Outer<TT;>;Lnet/sourceforge/pmd/lang/java/symbols/DeadlockTest$GenericClass<TT;>;>;],status=NOT_PARSED}
└─ t2 locked : ParseLock{name=LazyClassSignature:net/sourceforge/pmd/lang/java/symbols/DeadlockTest$Outer[<T:Ljava/lang/Object;>Ljava/lang/Object;Lnet/sourceforge/pmd/lang/java/symbols/DeadlockTest$GenericInterface<Lnet/sourceforge/pmd/lang/java/symbols/DeadlockTest$Outer<TT;>;Lnet/sourceforge/pmd/lang/java/symbols/DeadlockTest$GenericClass<TT;>;>;],status=NOT_PARSED}
├─ t2 waiting on : ParseLock{name=LazyClassSignature:net/sourceforge/pmd/lang/java/symbols/DeadlockTest[null],status=NOT_PARSED}
├─ t2 locked : ParseLock{name=LazyClassSignature:net/sourceforge/pmd/lang/java/symbols/DeadlockTest[null],status=NOT_PARSED}
├─ t2 released : ParseLock{name=LazyClassSignature:net/sourceforge/pmd/lang/java/symbols/DeadlockTest[null],status=FULL}
├─ t2 waiting on : ParseLock{name=LazyClassSignature:net/sourceforge/pmd/lang/java/symbols/DeadlockTest$Outer[<T:Ljava/lang/Object;>Ljava/lang/Object;Lnet/sourceforge/pmd/lang/java/symbols/DeadlockTest$GenericInterface<Lnet/sourceforge/pmd/lang/java/symbols/DeadlockTest$Outer<TT;>;Lnet/sourceforge/pmd/lang/java/symbols/DeadlockTest$GenericClass<TT;>;>;],status=BEING_PARSED}
├─ t2 locked : ParseLock{name=LazyClassSignature:net/sourceforge/pmd/lang/java/symbols/DeadlockTest$Outer[<T:Ljava/lang/Object;>Ljava/lang/Object;Lnet/sourceforge/pmd/lang/java/symbols/DeadlockTest$GenericInterface<Lnet/sourceforge/pmd/lang/java/symbols/DeadlockTest$Outer<TT;>;Lnet/sourceforge/pmd/lang/java/symbols/DeadlockTest$GenericClass<TT;>;>;],status=BEING_PARSED}
├─ t2 released : ParseLock{name=LazyClassSignature:net/sourceforge/pmd/lang/java/symbols/DeadlockTest$Outer[<T:Ljava/lang/Object;>Ljava/lang/Object;Lnet/sourceforge/pmd/lang/java/symbols/DeadlockTest$GenericInterface<Lnet/sourceforge/pmd/lang/java/symbols/DeadlockTest$Outer<TT;>;Lnet/sourceforge/pmd/lang/java/symbols/DeadlockTest$GenericClass<TT;>;>;],status=BEING_PARSED}
└─ t2 waiting on : ParseLock{name=LazyClassSignature:net/sourceforge/pmd/lang/java/symbols/DeadlockTest$GenericClass[<T:Ljava/lang/Object;>Lnet/sourceforge/pmd/lang/java/symbols/DeadlockTest$GenericBaseClass<Lnet/sourceforge/pmd/lang/java/symbols/DeadlockTest$Outer$Inner<TT;>;>;],status=NOT_PARSED}
├─ t2 locked : ParseLock{name=LazyClassSignature:net/sourceforge/pmd/lang/java/symbols/DeadlockTest$GenericClass[<T:Ljava/lang/Object;>Lnet/sourceforge/pmd/lang/java/symbols/DeadlockTest$GenericBaseClass<Lnet/sourceforge/pmd/lang/java/symbols/DeadlockTest$Outer$Inner<TT;>;>;],status=NOT_PARSED}
└─ t2 waiting on : ParseLock{name=LazyClassSignature:net/sourceforge/pmd/lang/java/symbols/DeadlockTest$Outer$Inner[<T:Ljava/lang/Object;>Ljava/lang/Object;],status=NOT_PARSED}
In order to reproduce the deadlock reliably, add the following piece into ParseLock, just at the beginning
of the synchronized block (line 42):
// t1 needs to wait after having the lock, so that t2 can go on and wait on the same lock
if (Thread.currentThread().getName().equals("t1") && this.toString().contains("LazyClassSignature:net/sourceforge/pmd/lang/java/symbols/DeadlockTest$Outer$Inner[<T:L")) {
try {
Thread.sleep(500);
} catch (InterruptedException ignored) {
}
}
And then, introduce a bug again. One way to make the test fail is:
Comment out the method "public int getTypeParameterCount()", so that it is inherited again.
Add the following method:
@Override
public boolean isGeneric() {
parseLock.ensureParsed();
return signature.isGeneric();
}
*/
List<Throwable> exceptions = new ArrayList<>();
Thread.UncaughtExceptionHandler exceptionHandler = (t, e) -> {
exceptions.add(e);
e.printStackTrace();
};
Thread t1 = new Thread(() -> {
ASTCompilationUnit class1 = JavaParsingHelper.DEFAULT.parse(
"package net.sourceforge.pmd.lang.java.symbols;\n"
+ "import net.sourceforge.pmd.lang.java.symbols.DeadlockTest.Outer;\n"
+ " class Class1 {\n"
+ " public static <X> Outer.Inner<X> newInner(Outer<X> grid) {\n"
+ " return null;\n"
+ " }\n"
+ " }\n"
);
assertNotNull(class1);
// Outer.Inner<X> = return type of method "newInner"
List<ASTClassType> classTypes = class1.descendants(ASTClassType.class).toList();
ASTClassType outerInner = classTypes.get(0);
assertGenericClassType(outerInner, "Inner", "X", "T");
// Outer = qualifier of Outer.Inner<X>
ASTClassType outer = classTypes.get(1);
assertEquals("Outer", outer.getSimpleName());
assertNull(outer.getTypeArguments());
// Outer<X> = formal parameter type of method newInner
ASTClassType outerFormalParam = classTypes.get(3);
assertGenericClassType(outerFormalParam, "Outer", "X", "T");
}, "t1");
t1.setUncaughtExceptionHandler(exceptionHandler);
Thread t2 = new Thread(() -> {
ASTCompilationUnit class2 = JavaParsingHelper.DEFAULT.parse(
"package net.sourceforge.pmd.lang.java.symbols;\n"
+ "import net.sourceforge.pmd.lang.java.symbols.DeadlockTest.Outer;\n"
+ " class Class2<M> {\n"
+ " protected Outer<M> theOuter;\n"
+ " }\n"
);
assertNotNull(class2);
// Outer<M> = type of field "theOuter"
ASTClassType firstClassType = class2.descendants(ASTClassType.class).first();
assertNotNull(firstClassType);
assertGenericClassType(firstClassType, "Outer", "M", "T");
}, "t2");
t2.setUncaughtExceptionHandler(exceptionHandler);
t1.start();
t2.start();
t1.join();
t2.join();
assertAll(exceptions.stream()
.map(e -> () -> {
throw e;
}));
}
private static void assertGenericClassType(ASTClassType classType, String simpleName, String actualTypeParamName, String originalTypeParamName) {
assertEquals(simpleName, classType.getSimpleName());
assertEquals(1, classType.getTypeArguments().size());
assertEquals(actualTypeParamName, ((ASTClassType) classType.getTypeArguments().get(0)).getSimpleName());
JTypeParameterOwnerSymbol symbol = (JTypeParameterOwnerSymbol) classType.getTypeMirror().getSymbol();
assertEquals(1, symbol.getTypeParameterCount());
assertEquals(originalTypeParamName, symbol.getTypeParameters().get(0).getName());
}
}

View File

@ -0,0 +1,5 @@
#
# BSD-style license; for more info see http://pmd.sourceforge.net/license.html
#
#org.slf4j.simpleLogger.log.net.sourceforge.pmd.lang.java.symbols.internal.asm.ParseLock=trace

View File

@ -45,6 +45,28 @@
</parameter>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<executions>
<execution>
<id>antlr-cleanup</id>
<phase>generate-sources</phase>
<goals>
<goal>run</goal>
</goals>
<configuration>
<target>
<ant antfile="${antlr4.ant.wrapper}" target="cpd-language">
<property name="lang-name" value="XML" />
<property name="lang-id" value="xml" />
</ant>
</target>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
<dependencies>

View File

@ -0,0 +1,21 @@
/**
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.lang.xml.antlr4;
import org.antlr.v4.runtime.CharStream;
/**
* Backwards compatible bridge. The XMLLexer was moved to align it with other PMD modules.
* This class will be removed in PMD 8.0.0.
* Use {@link net.sourceforge.pmd.lang.xml.ast.XMLLexer} directly instead.
*
* @deprecated since 7.8.0. Use {@link net.sourceforge.pmd.lang.xml.ast.XMLLexer} directly instead.
*/
@Deprecated
public class XMLLexer extends net.sourceforge.pmd.lang.xml.ast.XMLLexer {
public XMLLexer(CharStream input) {
super(input);
}
}

View File

@ -8,7 +8,7 @@ import org.antlr.v4.runtime.CharStream;
import org.antlr.v4.runtime.Lexer;
import net.sourceforge.pmd.cpd.impl.AntlrCpdLexer;
import net.sourceforge.pmd.lang.xml.antlr4.XMLLexer;
import net.sourceforge.pmd.lang.xml.ast.XMLLexer;
/**
* <p>Note: This class has been called XmlTokenizer in PMD 6</p>.

12
pom.xml
View File

@ -101,7 +101,7 @@
<surefire.version>3.2.5</surefire.version>
<checkstyle.version>10.18.1</checkstyle.version>
<checkstyle.plugin.version>3.5.0</checkstyle.plugin.version>
<pmd.plugin.version>3.24.0</pmd.plugin.version>
<pmd.plugin.version>3.26.0</pmd.plugin.version>
<ant.version>1.10.14</ant.version>
<javadoc.plugin.version>3.6.3</javadoc.plugin.version>
<antlr.version>4.9.3</antlr.version>
@ -179,7 +179,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.6.0</version>
<version>3.7.1</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
@ -202,7 +202,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-clean-plugin</artifactId>
<version>3.3.2</version>
<version>3.4.0</version>
</plugin>
<!-- adds kotlin source directories for checkstyle header checks -->
@ -592,11 +592,11 @@
<artifactId>pmd-build-tools-config</artifactId>
<version>${pmd.build-tools.version}</version>
</dependency>
<!-- Allow to build PMD with Java 23 -->
<!-- Allow to build PMD with Java 24 -->
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm</artifactId>
<version>9.7</version>
<version>9.7.1</version>
</dependency>
</dependencies>
</plugin>
@ -828,7 +828,7 @@
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm</artifactId>
<version>9.7</version>
<version>9.7.1</version>
</dependency>
<dependency>
<groupId>org.pcollections</groupId>