From 109f458dbf0d6b8c64d77943f9523d98700162d9 Mon Sep 17 00:00:00 2001 From: Andreas Dangel Date: Fri, 22 Dec 2017 12:03:20 +0100 Subject: [PATCH] Fixes #793 [java] Parser error with private method in nested classes in interfaces * Remember old state to allow nesting * Fix ASTMethodDeclaration.isInterfaceMember * Extended tests --- pmd-java/etc/grammar/Java.jjt | 19 ++++++++--- .../lang/java/ast/ASTMethodDeclaration.java | 9 +++-- .../pmd/lang/java/ast/JDKVersionTest.java | 25 +++++++++++--- ...ivate_method_in_inner_class_interface.java | 28 --------------- ...vate_method_in_inner_class_interface1.java | 34 +++++++++++++++++++ ...vate_method_in_inner_class_interface2.java | 34 +++++++++++++++++++ 6 files changed, 110 insertions(+), 39 deletions(-) delete mode 100644 pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/ast/jdkversiontests/private_method_in_inner_class_interface.java create mode 100644 pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/ast/jdkversiontests/private_method_in_inner_class_interface1.java create mode 100644 pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/ast/jdkversiontests/private_method_in_inner_class_interface2.java diff --git a/pmd-java/etc/grammar/Java.jjt b/pmd-java/etc/grammar/Java.jjt index 46f04e8fb8..7b47f33085 100644 --- a/pmd-java/etc/grammar/Java.jjt +++ b/pmd-java/etc/grammar/Java.jjt @@ -343,7 +343,7 @@ public class JavaParser { * Keeps track whether we are dealing with an interface or not. Needed since the tree is * is not fully constructed yet, when we check for private interface methods. * The flag is updated, if entering ClassOrInterfaceDeclaration and reset when leaving. - * The flag is also reset, if entering a anonymous inner class. + * The flag is also reset, if entering a anonymous inner class or enums. */ private boolean inInterface = false; private void checkForBadPrivateInterfaceMethod(ASTMethodDeclaration node) { @@ -1417,6 +1417,7 @@ void ClassOrInterfaceDeclaration(int modifiers): { Token t = null; jjtThis.setModifiers(modifiers); + boolean inInterfaceOld = inInterface; inInterface = false; } { @@ -1427,7 +1428,7 @@ void ClassOrInterfaceDeclaration(int modifiers): [ ExtendsList() ] [ ImplementsList() ] ClassOrInterfaceBody() - { inInterface = false; } // always reset the flag after leaving the node + { inInterface = inInterfaceOld; } // always restore the flag after leaving the node } void ExtendsList(): @@ -1468,13 +1469,18 @@ jjtThis.setModifiers(modifiers); } void EnumBody(): -{} +{ + boolean inInterfaceOld = inInterface; + inInterface = false; +} { "{" [( Annotation() )* EnumConstant() ( LOOKAHEAD(2) "," ( Annotation() )* EnumConstant() )* ] [ "," ] [ ";" ( ClassOrInterfaceBodyDeclaration() )* ] "}" + + { inInterface = inInterfaceOld; } // always restore the flag after leaving the node } void EnumConstant(): @@ -2013,7 +2019,12 @@ void AllocationExpression(): ( ArrayDimsAndInits() | - Arguments() [ {inInterface = false;} ClassOrInterfaceBody() ] + Arguments() + [ + { boolean inInterfaceOld = inInterface; inInterface = false; /* a anonymous class is not a interface */ } + ClassOrInterfaceBody() + { inInterface = inInterfaceOld; } // always restore the flag after leaving the node + ] ) ) { checkForBadAnonymousDiamondUsage(); } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTMethodDeclaration.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTMethodDeclaration.java index c85e9f24c7..67eda3cc39 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTMethodDeclaration.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTMethodDeclaration.java @@ -75,9 +75,12 @@ public class ASTMethodDeclaration extends AbstractJavaAccessNode implements DFAG public boolean isInterfaceMember() { - ASTClassOrInterfaceBody body = getFirstParentOfType(ASTClassOrInterfaceBody.class); - if (body != null && body.jjtGetParent() instanceof ASTClassOrInterfaceDeclaration) { - return ((ASTClassOrInterfaceDeclaration) body.jjtGetParent()).isInterface(); + // for a real class/interface the 3rd parent is a ClassOrInterfaceDeclaration, + // for anonymous classes, the parent is e.g. a AllocationExpression + Node potentialTypeDeclaration = getNthParent(3); + + if (potentialTypeDeclaration instanceof ASTClassOrInterfaceDeclaration) { + return ((ASTClassOrInterfaceDeclaration) potentialTypeDeclaration).isInterface(); } return false; } diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/JDKVersionTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/JDKVersionTest.java index 71e49f2cd4..11771f620e 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/JDKVersionTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/JDKVersionTest.java @@ -10,10 +10,14 @@ import static net.sourceforge.pmd.lang.java.ParserTstUtil.parseJava15; import static net.sourceforge.pmd.lang.java.ParserTstUtil.parseJava17; import static net.sourceforge.pmd.lang.java.ParserTstUtil.parseJava18; import static net.sourceforge.pmd.lang.java.ParserTstUtil.parseJava9; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import java.io.IOException; import java.nio.charset.StandardCharsets; +import java.util.List; import org.apache.commons.io.IOUtils; import org.junit.Test; @@ -270,9 +274,22 @@ public class JDKVersionTest { } @Test - public final void jdk7PrivateMethodInnerClassInterface() { - ASTCompilationUnit acu = parseJava17(loadSource("private_method_in_inner_class_interface.java")); - ASTMethodDeclaration privateMethod = acu.getFirstDescendantOfType(ASTMethodDeclaration.class); - assertFalse(privateMethod.isInterfaceMember()); + public final void jdk7PrivateMethodInnerClassInterface1() { + ASTCompilationUnit acu = parseJava17(loadSource("private_method_in_inner_class_interface1.java")); + List methods = acu.findDescendantsOfType(ASTMethodDeclaration.class); + assertEquals(3, methods.size()); + for (ASTMethodDeclaration method : methods) { + assertFalse(method.isInterfaceMember()); + } + } + + @Test + public final void jdk7PrivateMethodInnerClassInterface2() { + try { + ASTCompilationUnit acu = parseJava17(loadSource("private_method_in_inner_class_interface2.java")); + fail("Expected exception"); + } catch (ParseException e) { + assertTrue(e.getMessage().startsWith("Line 19")); + } } } diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/ast/jdkversiontests/private_method_in_inner_class_interface.java b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/ast/jdkversiontests/private_method_in_inner_class_interface.java deleted file mode 100644 index 8cc67a7e62..0000000000 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/ast/jdkversiontests/private_method_in_inner_class_interface.java +++ /dev/null @@ -1,28 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.java.ast.testdata; - -/** - * With Java9, private methods in interfaces are possible. - * But they must not be confused with private methods in inner classes of interfaces - * when using older java version. - * - * @see https://github.com/pmd/pmd/issues/793 - */ -public interface InterfaceWithInnerClass { - - Object FOO = new Object() { - private void privateMethod() { } - }; - - class InnerClass { - private void privateMethod() { } - } - - enum InnerEnum { - VALUE; - private void privateMethod() { } - } -} diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/ast/jdkversiontests/private_method_in_inner_class_interface1.java b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/ast/jdkversiontests/private_method_in_inner_class_interface1.java new file mode 100644 index 0000000000..df2809d14f --- /dev/null +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/ast/jdkversiontests/private_method_in_inner_class_interface1.java @@ -0,0 +1,34 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.ast.testdata; + +/** + * With Java9, private methods in interfaces are possible. + * But they must not be confused with private methods in inner classes of interfaces + * when using older java version. + * + * @see https://github.com/pmd/pmd/issues/793 + */ +public class PrivateMethodsInInterface1 { + + public interface Interface1 { + Object FOO = new Object() { + private void privateMethod() { } + }; + } + + public interface Interface2 { + class InnerClass { + private void privateMethod() { } + } + } + + public interface Interface3 { + enum InnerEnum { + VALUE; + private void privateMethod() { } + } + } +} diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/ast/jdkversiontests/private_method_in_inner_class_interface2.java b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/ast/jdkversiontests/private_method_in_inner_class_interface2.java new file mode 100644 index 0000000000..06ec5637ab --- /dev/null +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/ast/jdkversiontests/private_method_in_inner_class_interface2.java @@ -0,0 +1,34 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.ast.testdata; + +/** + * Prior to Java9, private methods are not possible. + * Make sure, they are detected. + * + * @see https://github.com/pmd/pmd/issues/793 + */ +public class PrivateMethodsInInterface2 { + + public interface Interface1 { + Object FOO = new Object() { + private void privateMethod() { } + }; + private void privateMethodInInterface1() { } // note: this will be a parser error! + } + + public interface Interface2 { + class InnerClass { + private void privateMethod() { } + } + } + + public interface Interface3 { + enum InnerEnum { + VALUE; + private void privateMethod() { } + } + } +}