Skip to content

Commit

Permalink
Issue checkstyle#14566: Improve lexer performance
Browse files Browse the repository at this point in the history
  • Loading branch information
nrmancuso committed Mar 4, 2024
1 parent 2ec6c25 commit 5d5f5b2
Show file tree
Hide file tree
Showing 11 changed files with 381 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5358,6 +5358,13 @@
</details>
</checkerFrameworkError>

<checkerFrameworkError unstable="false">
<fileName>src/main/java/com/puppycrawl/tools/checkstyle/grammar/CompositeLexerContextCache.java</fileName>
<specifier>dereference.of.nullable</specifier>
<message>dereference of possibly-null reference currentContext</message>
<lineContent>if (currentContext.getCurlyBraceDepth() == 0) {</lineContent>
</checkerFrameworkError>

<checkerFrameworkError unstable="false">
<fileName>src/main/java/com/puppycrawl/tools/checkstyle/gui/BaseCellEditor.java</fileName>
<specifier>return</specifier>
Expand Down
3 changes: 3 additions & 0 deletions config/import-control.xml
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,9 @@
<file name="CrAwareLexerSimulator">
<allow pkg="org.antlr.v4.runtime"/>
</file>
<file name="CompositeLexerContextCache">
<allow pkg="org.antlr.v4.runtime"/>
</file>
</subpackage>

<file name="Main">
Expand Down
4 changes: 4 additions & 0 deletions config/linkcheck-suppressions.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1341,3 +1341,7 @@
<a href="com/puppycrawl/tools/checkstyle/xpath/iterators/ReverseListIterator.html#%3Cinit%3E(java.util.Collection)">com/puppycrawl/tools/checkstyle/xpath/iterators/ReverseListIterator.html#%3Cinit%3E(java.util.Collection)</a>: doesn't exist.
<a href="com/puppycrawl/tools/checkstyle/utils/UnmodifiableCollectionUtil.html#copyOfArray(T%5B%5D,int)">com/puppycrawl/tools/checkstyle/utils/UnmodifiableCollectionUtil.html#copyOfArray(T%5B%5D,int)</a>: doesn't exist.
<a href="com/puppycrawl/tools/checkstyle/site/XdocsTemplateSink.CustomPrintWriter.html#%3Cinit%3E(java.io.Writer)">com/puppycrawl/tools/checkstyle/site/XdocsTemplateSink.CustomPrintWriter.html#%3Cinit%3E(java.io.Writer)</a>: doesn't exist.
<a href="com/puppycrawl/tools/checkstyle/grammar/CompositeLexerContextCache.html#%3Cinit%3E(org.antlr.v4.runtime.Lexer)">com/puppycrawl/tools/checkstyle/grammar/CompositeLexerContextCache.html#%3Cinit%3E(org.antlr.v4.runtime.Lexer)</a>: doesn't exist.
<a href="com/puppycrawl/tools/checkstyle/grammar/CompositeLexerContextCache.TemplateContext.html#%3Cinit%3E(int,int)">com/puppycrawl/tools/checkstyle/grammar/CompositeLexerContextCache.TemplateContext.html#%3Cinit%3E(int,int)</a>: doesn't exist.
<a href="apidocs/com/puppycrawl/tools/checkstyle/grammar/CompositeLexerContextCache.html#%3Cinit%3E(org.antlr.v4.runtime.Lexer)">#%3Cinit%3E(org.antlr.v4.runtime.Lexer)</a>: doesn't exist.
<a href="apidocs/com/puppycrawl/tools/checkstyle/grammar/CompositeLexerContextCache.TemplateContext.html#%3Cinit%3E(int,int)">#%3Cinit%3E(int,int)</a>: doesn't exist.
3 changes: 3 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4362,6 +4362,7 @@
<param>com.puppycrawl.tools.checkstyle.Checker*</param>
<param>com.puppycrawl.tools.checkstyle.ThreadModeSettings*</param>
<param>com.puppycrawl.tools.checkstyle.grammar.CrAwareLexerSimulator*</param>
<param>com.puppycrawl.tools.checkstyle.grammar.CompositeLexerContextCache*</param>
<!-- interfaces -->
<param>com.puppycrawl.tools.checkstyle.AuditEventFormatter*</param>
<param>com.puppycrawl.tools.checkstyle.XdocsPropertyType*</param>
Expand Down Expand Up @@ -4393,6 +4394,8 @@
<param>com.puppycrawl.tools.checkstyle.ThreadModeSettingsTest</param>
<param>com.puppycrawl.tools.checkstyle.grammar.CrAwareLexerSimulatorTest</param>
<param>com.puppycrawl.tools.checkstyle.grammar.javadoc.JavadocParseTreeTest</param>
<!-- this test is required for CompositeLexerContextCache -->
<param>com.puppycrawl.tools.checkstyle.grammar.java21.Java21AstRegressionTest</param>
<!-- this test is required for Checker -->
<param>com.puppycrawl.tools.checkstyle.filefilters.BeforeExecutionExclusionFileFilterTest</param>
<param>com.puppycrawl.tools.checkstyle.checks.TranslationCheckTest</param>
Expand Down
3 changes: 3 additions & 0 deletions src/main/java/com/puppycrawl/tools/checkstyle/JavaParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
import com.puppycrawl.tools.checkstyle.api.FileContents;
import com.puppycrawl.tools.checkstyle.api.FileText;
import com.puppycrawl.tools.checkstyle.api.TokenTypes;
import com.puppycrawl.tools.checkstyle.grammar.CompositeLexerContextCache;
import com.puppycrawl.tools.checkstyle.grammar.java.JavaLanguageLexer;
import com.puppycrawl.tools.checkstyle.grammar.java.JavaLanguageParser;
import com.puppycrawl.tools.checkstyle.utils.ParserUtil;
Expand Down Expand Up @@ -84,7 +85,9 @@ public static DetailAST parse(FileContents contents)
final String fullText = contents.getText().getFullText().toString();
final CharStream codePointCharStream = CharStreams.fromString(fullText);
final JavaLanguageLexer lexer = new JavaLanguageLexer(codePointCharStream, true);
final CompositeLexerContextCache contextCache = new CompositeLexerContextCache(lexer);
lexer.setCommentListener(contents);
lexer.setContextCache(contextCache);

final CommonTokenStream tokenStream = new CommonTokenStream(lexer);
final JavaLanguageParser parser =
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
///////////////////////////////////////////////////////////////////////////////////////////////
// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
// Copyright (C) 2001-2024 the original author or authors.
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
///////////////////////////////////////////////////////////////////////////////////////////////

package com.puppycrawl.tools.checkstyle.grammar;

import java.util.ArrayDeque;
import java.util.Deque;

import org.antlr.v4.runtime.Lexer;

/**
* This class is used to keep track of the lexer context to help us determine
* when to switch lexer modes.
*/
public final class CompositeLexerContextCache {

/** Stack for tracking string template contexts. */
private final Deque<TemplateContext> templateContextStack;

/** The lexer to use. */
private final Lexer lexer;

/**
* Creates a new CompositeLexerContextCache instance.
*
* @param lexer the lexer to use
*/
public CompositeLexerContextCache(Lexer lexer) {
templateContextStack = new ArrayDeque<>();
this.lexer = lexer;
}

/**
* Update the left curly brace context if we are in a string template.
*/
public void updateLeftCurlyBraceContext() {
if (isInTemplateContext()) {
final TemplateContext currentContext = templateContextStack.pop();
final TemplateContext newContext = new TemplateContext(currentContext.getMode(),
currentContext.getCurlyBraceDepth() + 1);
templateContextStack.push(newContext);
}
}

/**
* Update the right curly brace context if we are in a string template.
*/
public void updateRightCurlyBraceContext() {
if (isInTemplateContext()) {
final TemplateContext currentContext = templateContextStack.peek();
if (currentContext.getCurlyBraceDepth() == 0) {
// This right curly brace is the start delimiter
// of a template middle or end. We consume
// the right curly brace to be used as the first token
// in the appropriate lexer mode rule, enter
// the corresponding lexer mode, and keep consuming
// the rest of the template middle or end.
pushToModeStackWithMore(currentContext.getMode());
}
else {
// We've consumed a right curly brace within an embedded expression.
final TemplateContext newContext = new TemplateContext(currentContext.getMode(),
currentContext.getCurlyBraceDepth() - 1);
templateContextStack.push(newContext);
}
}
}

/**
* Enter a string template context.
*
* @param mode the lexer mode to enter
*/
public void enterTemplateContext(int mode) {
final TemplateContext newContext = new TemplateContext(mode, 0);
templateContextStack.push(newContext);
}

/**
* Exit a string template context.
*/
public void exitTemplateContext() {
templateContextStack.pop();
}

/**
* Push a mode to the mode stack and consume more input
* to complete the current token.
*
* @param mode the mode to push to the mode stack
*/
private void pushToModeStackWithMore(int mode) {
lexer.more();
lexer.pushMode(mode);
}

/**
* Check if we are in a string template context.
*
* @return true if we are in a string template context
*/
private boolean isInTemplateContext() {
return !templateContextStack.isEmpty();
}

/**
* A class to represent the context of a string template.
*/
private static final class TemplateContext {

/** The lexer mode of this context. */
private final int mode;

/** The depth of this context. */
private final int curlyBraceDepth;

/**
* Creates a new TemplateContext instance.
*
* @param mode the lexer mode of this context
* @param curlyBraceDepth the depth of this context
*/
private TemplateContext(int mode, int curlyBraceDepth) {
this.mode = mode;
this.curlyBraceDepth = curlyBraceDepth;
}

/**
* Get the lexer mode of this context.
*
* @return current lexer mode
*/
private int getMode() {
return mode;
}

/**
* Current depth of this context.
*
* @return current depth
*/
private int getCurlyBraceDepth() {
return curlyBraceDepth;
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ tokens {

@header {
import com.puppycrawl.tools.checkstyle.grammar.CommentListener;
import com.puppycrawl.tools.checkstyle.grammar.CompositeLexerContextCache;
import com.puppycrawl.tools.checkstyle.grammar.CrAwareLexerSimulator;
}

Expand All @@ -139,23 +140,27 @@ import com.puppycrawl.tools.checkstyle.grammar.CrAwareLexerSimulator;
}

private CommentListener commentListener = null;
private CompositeLexerContextCache contextCache = null;

/**
* Sets the CommentListener for the lexer.
*
* @param commentListener the commentListener to use in this lexer
*/
public void setCommentListener(CommentListener commentListener){
public void setCommentListener(CommentListener commentListener) {
this.commentListener = commentListener;
}

public void setContextCache(CompositeLexerContextCache contextCache) {
this.contextCache = contextCache;
}

/** Tracks the starting line of a block comment. */
int startLine = -1;

/** Tracks the starting column of a block comment. */
int startCol = -1;

private int stringTemplateDepth = 0;
}

// Keywords and restricted identifiers
Expand Down Expand Up @@ -251,7 +256,11 @@ CHAR_LITERAL: '\'' (EscapeSequence | ~['\\\r\n]) '\'';

fragment StringFragment: (EscapeSequence | ~["\\\r\n])*;
STRING_LITERAL: '"' StringFragment '"';
STRING_LITERAL: '"' StringFragment '"';
STRING_TEMPLATE_BEGIN: '"' StringFragment '\\' '{'
{ contextCache.enterTemplateContext(StringTemplate); }
;
TEXT_BLOCK_LITERAL_BEGIN: '"' '"' '"' -> pushMode(TextBlock);
Expand All @@ -261,8 +270,12 @@ LITERAL_NULL: 'null';
LPAREN: '(';
RPAREN: ')';
LCURLY: '{';
RCURLY: '}';
LCURLY: '{'
{ contextCache.updateLeftCurlyBraceContext(); }
;
RCURLY: '}'
{ contextCache.updateRightCurlyBraceContext(); }
;
LBRACK: '[';
RBRACK: ']';
SEMI: ';';
Expand Down Expand Up @@ -321,18 +334,7 @@ AT: '@';
ELLIPSIS: '...';
// String templates
STRING_TEMPLATE_BEGIN: '"' StringFragment '\\' '{'
{stringTemplateDepth++;}
;
STRING_TEMPLATE_MID: {stringTemplateDepth > 0}?
'}' StringFragment '\\' '{'
;
STRING_TEMPLATE_END: {stringTemplateDepth > 0}?
'}' StringFragment '"'
{stringTemplateDepth--;}
;
// Text block fragments
Expand Down Expand Up @@ -464,3 +466,13 @@ mode TextBlock;
TEXT_BLOCK_LITERAL_END
: '"' '"' '"' -> popMode
;
mode StringTemplate;
STRING_TEMPLATE_MID: StringFragment '\\' '{'
-> pushMode(DEFAULT_MODE), type(STRING_TEMPLATE_MID);
STRING_TEMPLATE_END: StringFragment '"'
{ contextCache.exitTemplateContext(); }
-> popMode, type(STRING_TEMPLATE_END);
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,13 @@
import com.puppycrawl.tools.checkstyle.api.FileContents;
import com.puppycrawl.tools.checkstyle.api.FileText;
import com.puppycrawl.tools.checkstyle.api.TokenTypes;
import com.puppycrawl.tools.checkstyle.grammar.CompositeLexerContextCache;
import com.puppycrawl.tools.checkstyle.grammar.java.JavaLanguageLexer;
import com.puppycrawl.tools.checkstyle.grammar.java.JavaLanguageParser;
import com.puppycrawl.tools.checkstyle.grammar.java.JavaLanguageParserBaseVisitor;
import com.puppycrawl.tools.checkstyle.internal.utils.TestUtil;

// -@cs[ClassDataAbstractionCoupling] No way to split up class usage.
public class JavaAstVisitorTest extends AbstractModuleTestSupport {

/**
Expand Down Expand Up @@ -240,7 +242,9 @@ public void testNoStackOverflowOnDeepStringConcat() throws Exception {
final String fullText = contents.getText().getFullText().toString();
final CharStream codePointCharStream = CharStreams.fromString(fullText);
final JavaLanguageLexer lexer = new JavaLanguageLexer(codePointCharStream, true);
final CompositeLexerContextCache contextCache = new CompositeLexerContextCache(lexer);
lexer.setCommentListener(contents);
lexer.setContextCache(contextCache);

final CommonTokenStream tokenStream = new CommonTokenStream(lexer);
final JavaLanguageParser parser = new JavaLanguageParser(tokenStream);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,15 @@ public void testBasicStringTemplate() throws Exception {
"InputStringTemplateBasic.java"));
}

@Test
public void testStringTemplateNested() throws Exception {
verifyAst(
getNonCompilablePath(
"ExpectedStringTemplateNested.txt"),
getNonCompilablePath(
"InputStringTemplateNested.java"));
}

/**
* Test for tabs instead of spaces in the input file.
* All node columns are -3 when compared to the above test. This is
Expand Down
Loading

0 comments on commit 5d5f5b2

Please sign in to comment.