From 9dcb697f13549dab1bce1dac0d4c9b7fa5cfe215 Mon Sep 17 00:00:00 2001 From: Andreas Dangel Date: Thu, 7 Nov 2024 12:38:11 +0100 Subject: [PATCH] Improve DeadlockTest --- .../pmd/lang/java/symbols/DeadlockTest.java | 73 ++++++++++++++++++- 1 file changed, 72 insertions(+), 1 deletion(-) diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symbols/DeadlockTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symbols/DeadlockTest.java index 25e3de1948..15358be481 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symbols/DeadlockTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symbols/DeadlockTest.java @@ -4,12 +4,19 @@ 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; /** @@ -38,7 +45,21 @@ class DeadlockTest { /* 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[Ljava/lang/Object;],status=NOT_PARSED} + └─ t1 locked : ParseLock{name=LazyClassSignature:net/sourceforge/pmd/lang/java/symbols/DeadlockTest$Outer$Inner[Ljava/lang/Object;],status=NOT_PARSED} + └─ t1 waiting on : ParseLock{name=LazyClassSignature:net/sourceforge/pmd/lang/java/symbols/DeadlockTest$Outer[Ljava/lang/Object;Lnet/sourceforge/pmd/lang/java/symbols/DeadlockTest$GenericInterface;Lnet/sourceforge/pmd/lang/java/symbols/DeadlockTest$GenericClass;>;],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[Ljava/lang/Object;Lnet/sourceforge/pmd/lang/java/symbols/DeadlockTest$GenericInterface;Lnet/sourceforge/pmd/lang/java/symbols/DeadlockTest$GenericClass;>;],status=NOT_PARSED} + └─ t2 locked : ParseLock{name=LazyClassSignature:net/sourceforge/pmd/lang/java/symbols/DeadlockTest$Outer[Ljava/lang/Object;Lnet/sourceforge/pmd/lang/java/symbols/DeadlockTest$GenericInterface;Lnet/sourceforge/pmd/lang/java/symbols/DeadlockTest$GenericClass;>;],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[Ljava/lang/Object;Lnet/sourceforge/pmd/lang/java/symbols/DeadlockTest$GenericInterface;Lnet/sourceforge/pmd/lang/java/symbols/DeadlockTest$GenericClass;>;],status=BEING_PARSED} + ├─ t2 locked : ParseLock{name=LazyClassSignature:net/sourceforge/pmd/lang/java/symbols/DeadlockTest$Outer[Ljava/lang/Object;Lnet/sourceforge/pmd/lang/java/symbols/DeadlockTest$GenericInterface;Lnet/sourceforge/pmd/lang/java/symbols/DeadlockTest$GenericClass;>;],status=BEING_PARSED} + ├─ t2 released : ParseLock{name=LazyClassSignature:net/sourceforge/pmd/lang/java/symbols/DeadlockTest$Outer[Ljava/lang/Object;Lnet/sourceforge/pmd/lang/java/symbols/DeadlockTest$GenericInterface;Lnet/sourceforge/pmd/lang/java/symbols/DeadlockTest$GenericClass;>;],status=BEING_PARSED} + └─ t2 waiting on : ParseLock{name=LazyClassSignature:net/sourceforge/pmd/lang/java/symbols/DeadlockTest$GenericClass[Lnet/sourceforge/pmd/lang/java/symbols/DeadlockTest$GenericBaseClass;>;],status=NOT_PARSED} + ├─ t2 locked : ParseLock{name=LazyClassSignature:net/sourceforge/pmd/lang/java/symbols/DeadlockTest$GenericClass[Lnet/sourceforge/pmd/lang/java/symbols/DeadlockTest$GenericBaseClass;>;],status=NOT_PARSED} + └─ t2 waiting on : ParseLock{name=LazyClassSignature:net/sourceforge/pmd/lang/java/symbols/DeadlockTest$Outer$Inner[Ljava/lang/Object;],status=NOT_PARSED} In order to reproduce the deadlock reliably, add the following piece into ParseLock, just at the beginning @@ -51,20 +72,50 @@ class DeadlockTest { } 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 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 Outer.Inner newInner(Outer grid) {\n" + + " public static Outer.Inner newInner(Outer grid) {\n" + " return null;\n" + " }\n" + " }\n" ); assertNotNull(class1); + + // Outer.Inner = return type of method "newInner" + List classTypes = class1.descendants(ASTClassType.class).toList(); + ASTClassType outerInner = classTypes.get(0); + assertGenericClassType(outerInner, "Inner", "X", "T"); + + // Outer = qualifier of Outer.Inner + ASTClassType outer = classTypes.get(1); + assertEquals("Outer", outer.getSimpleName()); + assertNull(outer.getTypeArguments()); + + // Outer = 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( @@ -75,12 +126,32 @@ class DeadlockTest { + " }\n" ); assertNotNull(class2); + + // Outer = 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()); } }