Fixes #793 [java] Parser error with private method in nested classes in interfaces

*   Remember old state to allow nesting
*   Fix ASTMethodDeclaration.isInterfaceMember
*   Extended tests
This commit is contained in:
Andreas Dangel
2017-12-22 12:03:20 +01:00
parent 64b862eef9
commit 109f458dbf
6 changed files with 110 additions and 39 deletions

View File

@ -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(); }

View File

@ -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;
}

View File

@ -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<ASTMethodDeclaration> 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"));
}
}
}

View File

@ -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() { }
}
}

View File

@ -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() { }
}
}
}

View File

@ -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() { }
}
}
}