From a0e810724964867932eb1d370c658b7480175428 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Thu, 4 Mar 2021 07:44:05 +0100 Subject: [PATCH 1/4] Fix #3145 - parse exception with local records --- pmd-java/etc/grammar/Java.jjt | 5 ++- .../jdkversiontests/java15p/LocalRecords.java | 6 +++ .../jdkversiontests/java15p/LocalRecords.txt | 45 +++++++++++++++++++ 3 files changed, 55 insertions(+), 1 deletion(-) diff --git a/pmd-java/etc/grammar/Java.jjt b/pmd-java/etc/grammar/Java.jjt index 43eaed8593..a1284157d7 100644 --- a/pmd-java/etc/grammar/Java.jjt +++ b/pmd-java/etc/grammar/Java.jjt @@ -1,4 +1,7 @@ /** + * Fix #3145 - parse exception with local records + * Clément Fournier 03/2021 + *==================================================================== * Remove support for Java 14 preview language features * JEP 397: Sealed Classes (Second Preview) for Java16 Preview * JEP 395: Records for Java16 @@ -594,7 +597,7 @@ public class JavaParser { return next.kind == CLASS || isRecordTypeSupported() && next.kind == INTERFACE || isRecordTypeSupported() && next.kind == IDENTIFIER && next.image.equals("enum") - || isRecordTypeSupported() && next.kind == IDENTIFIER && next.image.equals("record"); + || isRecordTypeSupported() && next.kind == IDENTIFIER && next.image.equals("record") && isToken(2, IDENTIFIER); } /** diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/ast/jdkversiontests/java15p/LocalRecords.java b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/ast/jdkversiontests/java15p/LocalRecords.java index c359b99419..b860e99136 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/ast/jdkversiontests/java15p/LocalRecords.java +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/ast/jdkversiontests/java15p/LocalRecords.java @@ -31,6 +31,12 @@ public class LocalRecords { final @Deprecated static record MyRecord4(String a) {} } + void statementThatStartsWithRecordAsRegularIdent() { + // https://github.com/pmd/pmd/issues/3145 + final Map record = new HashMap<>(); + record.put("key", "value"); + } + void methodWithLocalClass() { class MyLocalClass {} } diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/ast/jdkversiontests/java15p/LocalRecords.txt b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/ast/jdkversiontests/java15p/LocalRecords.txt index c274834840..afcb2d7def 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/ast/jdkversiontests/java15p/LocalRecords.txt +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/ast/jdkversiontests/java15p/LocalRecords.txt @@ -215,6 +215,51 @@ | | +- VariableDeclaratorId[@Array = false, @ArrayDepth = 0, @ArrayType = false, @ExceptionBlockParameter = false, @ExplicitReceiverParameter = false, @Field = false, @Final = true, @FormalParameter = false, @Image = "a", @LambdaParameter = false, @LocalVariable = false, @Name = "a", @PatternBinding = false, @ResourceDeclaration = false, @TypeInferred = false, @VariableName = "a"] | +- RecordBody[] +- ClassOrInterfaceBodyDeclaration[@AnonymousInnerClass = false, @EnumChild = false, @Kind = DeclarationKind.METHOD] + | +- MethodDeclaration[@Abstract = false, @Arity = 0, @Default = false, @Final = false, @InterfaceMember = false, @Kind = MethodLikeKind.METHOD, @MethodName = "statementThatStartsWithRecordAsRegularIdent", @Modifiers = 0, @Name = "statementThatStartsWithRecordAsRegularIdent", @Native = false, @PackagePrivate = true, @Private = false, @Protected = false, @Public = false, @Static = false, @Strictfp = false, @Synchronized = false, @SyntacticallyAbstract = false, @SyntacticallyPublic = false, @Transient = false, @Void = true, @Volatile = false] + | +- ResultType[@Void = true, @returnsArray = false] + | +- MethodDeclarator[@Image = "statementThatStartsWithRecordAsRegularIdent", @ParameterCount = 0] + | | +- FormalParameters[@ParameterCount = 0, @Size = 0] + | +- Block[@containsComment = false] + | +- BlockStatement[@Allocation = true] + | | +- LocalVariableDeclaration[@Abstract = false, @Array = false, @ArrayDepth = 0, @Default = false, @Final = true, @Modifiers = 32, @Native = false, @PackagePrivate = true, @Private = false, @Protected = false, @Public = false, @Static = false, @Strictfp = false, @Synchronized = false, @Transient = false, @TypeInferred = false, @VariableName = "record", @Volatile = false] + | | +- Type[@Array = false, @ArrayDepth = 0, @ArrayType = false, @TypeImage = "Map"] + | | | +- ReferenceType[@Array = false, @ArrayDepth = 0] + | | | +- ClassOrInterfaceType[@AnonymousClass = false, @Array = false, @ArrayDepth = 0, @Image = "Map", @ReferenceToClassSameCompilationUnit = false] + | | | +- TypeArguments[@Diamond = false] + | | | +- TypeArgument[@Wildcard = false] + | | | | +- ReferenceType[@Array = false, @ArrayDepth = 0] + | | | | +- ClassOrInterfaceType[@AnonymousClass = false, @Array = false, @ArrayDepth = 0, @Image = "String", @ReferenceToClassSameCompilationUnit = false] + | | | +- TypeArgument[@Wildcard = false] + | | | +- ReferenceType[@Array = false, @ArrayDepth = 0] + | | | +- ClassOrInterfaceType[@AnonymousClass = false, @Array = false, @ArrayDepth = 0, @Image = "String", @ReferenceToClassSameCompilationUnit = false] + | | +- VariableDeclarator[@Initializer = true, @Name = "record"] + | | +- VariableDeclaratorId[@Array = false, @ArrayDepth = 0, @ArrayType = false, @ExceptionBlockParameter = false, @ExplicitReceiverParameter = false, @Field = false, @Final = true, @FormalParameter = false, @Image = "record", @LambdaParameter = false, @LocalVariable = true, @Name = "record", @PatternBinding = false, @ResourceDeclaration = false, @TypeInferred = false, @VariableName = "record"] + | | +- VariableInitializer[] + | | +- Expression[@StandAlonePrimitive = false] + | | +- PrimaryExpression[] + | | +- PrimaryPrefix[@SuperModifier = false, @ThisModifier = false] + | | +- AllocationExpression[@AnonymousClass = false] + | | +- ClassOrInterfaceType[@AnonymousClass = false, @Array = false, @ArrayDepth = 0, @Image = "HashMap", @ReferenceToClassSameCompilationUnit = false] + | | | +- TypeArguments[@Diamond = true] + | | +- Arguments[@ArgumentCount = 0, @Size = 0] + | +- BlockStatement[@Allocation = false] + | +- Statement[] + | +- StatementExpression[] + | +- PrimaryExpression[] + | +- PrimaryPrefix[@SuperModifier = false, @ThisModifier = false] + | | +- Name[@Image = "record.put"] + | +- PrimarySuffix[@ArgumentCount = 2, @Arguments = true, @ArrayDereference = false] + | +- Arguments[@ArgumentCount = 2, @Size = 2] + | +- ArgumentList[@Size = 2] + | +- Expression[@StandAlonePrimitive = false] + | | +- PrimaryExpression[] + | | +- PrimaryPrefix[@SuperModifier = false, @ThisModifier = false] + | | +- Literal[@CharLiteral = false, @DoubleLiteral = false, @EscapedStringLiteral = ""key"", @FloatLiteral = false, @Image = ""key"", @IntLiteral = false, @LongLiteral = false, @SingleCharacterStringLiteral = false, @StringLiteral = true, @TextBlock = false, @TextBlockContent = ""key"", @ValueAsDouble = NaN, @ValueAsFloat = NaN, @ValueAsInt = 0, @ValueAsLong = 0] + | +- Expression[@StandAlonePrimitive = false] + | +- PrimaryExpression[] + | +- PrimaryPrefix[@SuperModifier = false, @ThisModifier = false] + | +- Literal[@CharLiteral = false, @DoubleLiteral = false, @EscapedStringLiteral = ""value"", @FloatLiteral = false, @Image = ""value"", @IntLiteral = false, @LongLiteral = false, @SingleCharacterStringLiteral = false, @StringLiteral = true, @TextBlock = false, @TextBlockContent = ""value"", @ValueAsDouble = NaN, @ValueAsFloat = NaN, @ValueAsInt = 0, @ValueAsLong = 0] + +- ClassOrInterfaceBodyDeclaration[@AnonymousInnerClass = false, @EnumChild = false, @Kind = DeclarationKind.METHOD] | +- MethodDeclaration[@Abstract = false, @Arity = 0, @Default = false, @Final = false, @InterfaceMember = false, @Kind = MethodLikeKind.METHOD, @MethodName = "methodWithLocalClass", @Modifiers = 0, @Name = "methodWithLocalClass", @Native = false, @PackagePrivate = true, @Private = false, @Protected = false, @Public = false, @Static = false, @Strictfp = false, @Synchronized = false, @SyntacticallyAbstract = false, @SyntacticallyPublic = false, @Transient = false, @Void = true, @Volatile = false] | +- ResultType[@Void = true, @returnsArray = false] | +- MethodDeclarator[@Image = "methodWithLocalClass", @ParameterCount = 0] From 4bfd72e6586cdd8adb8b748585c937040ddc3f40 Mon Sep 17 00:00:00 2001 From: Andreas Dangel Date: Thu, 4 Mar 2021 20:16:37 +0100 Subject: [PATCH 2/4] [java] Infinite loop when parsing invalid code nested in lambdas - fixes #3117 - backports part of StatementExpression production from pmd7 --- docs/pages/release_notes.md | 3 ++ pmd-java/etc/grammar/Java.jjt | 11 ++++++-- .../pmd/lang/java/ast/ParserCornersTest.java | 7 +++++ .../java/ast/InfiniteLoopInLookahead.java | 28 +++++++++++++++++++ 4 files changed, 46 insertions(+), 3 deletions(-) create mode 100644 pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/ast/InfiniteLoopInLookahead.java diff --git a/docs/pages/release_notes.md b/docs/pages/release_notes.md index b8f8783555..c2ba2e8b10 100644 --- a/docs/pages/release_notes.md +++ b/docs/pages/release_notes.md @@ -16,6 +16,9 @@ This is a {{ site.pmd.release_type }} release. ### Fixed Issues +* java + * [#3117](https://github.com/pmd/pmd/issues/3117): \[java] Infinite loop when parsing invalid code nested in lambdas + ### API Changes ### External Contributions diff --git a/pmd-java/etc/grammar/Java.jjt b/pmd-java/etc/grammar/Java.jjt index 43eaed8593..be6a803151 100644 --- a/pmd-java/etc/grammar/Java.jjt +++ b/pmd-java/etc/grammar/Java.jjt @@ -237,6 +237,11 @@ options { TRACK_TOKENS = true; NODE_PACKAGE="net.sourceforge.pmd.lang.java.ast"; + // disable the calculation of expected tokens when a parse error occurs + // depending on the possible allowed next tokens, this + // could be expensive (see https://github.com/pmd/pmd/issues/3117) + //ERROR_REPORTING = false; + //DEBUG_PARSER = true; //DEBUG_LOOKAHEAD = true; //DEBUG_TOKEN_MANAGER = true; @@ -2131,9 +2136,9 @@ void StatementExpression() : | PreDecrementExpression() | - LOOKAHEAD( PrimaryExpression() AssignmentOperator() ) PrimaryExpression() AssignmentOperator() Expression() -| - PostfixExpression() + // using PostfixExpression here allows us to skip the part of the production tree + // between Expression() and PostfixExpression() + PostfixExpression() [ AssignmentOperator() Expression() ] } void SwitchStatement(): diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/ParserCornersTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/ParserCornersTest.java index 16f1c4786b..f545446d84 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/ParserCornersTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/ParserCornersTest.java @@ -226,6 +226,13 @@ public class ParserCornersTest { java8.parseResource("GitHubBug309.java"); } + @Test(timeout = 30000) + public void testInfiniteLoopInLookahead() { + expect.expect(ParseException.class); + // https://github.com/pmd/pmd/issues/3117 + java8.parseResource("InfiniteLoopInLookahead.java"); + } + /** * This triggered bug #1484 UnusedLocalVariable - false positive - * parenthesis diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/ast/InfiniteLoopInLookahead.java b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/ast/InfiniteLoopInLookahead.java new file mode 100644 index 0000000000..55552bd48a --- /dev/null +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/ast/InfiniteLoopInLookahead.java @@ -0,0 +1,28 @@ +/* + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +import java.util.*; + +public class InfiniteLoopInLookahead { + + public void exam1(List resList) { + resList.forEach(a -> { + resList.forEach(b -> { + resList.forEach(c -> { + resList.forEach(d -> { + resList.forEach(e -> { + resList.forEach(f -> { + resList.forEach(g -> { + resList.forEach(h -> { + resList // note: missing semicolon -> parse error here... + }); + }); + }); + }); + }); + }); + }); + }); + } +} From 3ec49a2b040c95a6201f6cd5adea5e2815b5e788 Mon Sep 17 00:00:00 2001 From: Andreas Dangel Date: Fri, 5 Mar 2021 10:14:51 +0100 Subject: [PATCH 3/4] [doc] Update release notes (#3145, #3150) --- docs/pages/release_notes.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/pages/release_notes.md b/docs/pages/release_notes.md index b8f8783555..c8daca1bf9 100644 --- a/docs/pages/release_notes.md +++ b/docs/pages/release_notes.md @@ -16,6 +16,9 @@ This is a {{ site.pmd.release_type }} release. ### Fixed Issues +* java + * [3145](https://github.com/pmd/pmd/issues/3145): \[java] Parse exception when using "record" as variable name + ### API Changes ### External Contributions From d815c5d173aa4f0d60f1cc068200c37bc55330f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Fri, 5 Mar 2021 11:48:05 +0100 Subject: [PATCH 4/4] Update grammar changelog --- pmd-java/etc/grammar/Java.jjt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pmd-java/etc/grammar/Java.jjt b/pmd-java/etc/grammar/Java.jjt index 4e17336cba..a5756cad3e 100644 --- a/pmd-java/etc/grammar/Java.jjt +++ b/pmd-java/etc/grammar/Java.jjt @@ -1,4 +1,7 @@ /** + * Fix #3117 - infinite loop when parsing invalid code nested in lambdas + * Andreas Dangel 03/2021 + *==================================================================== * Fix #3145 - parse exception with local records * Clément Fournier 03/2021 *====================================================================