diff --git a/common/src/main/java/io/netty/build/checkstyle/StaticFinalBufferCheck.java b/common/src/main/java/io/netty/build/checkstyle/StaticFinalBufferCheck.java new file mode 100644 index 0000000..839983f --- /dev/null +++ b/common/src/main/java/io/netty/build/checkstyle/StaticFinalBufferCheck.java @@ -0,0 +1,79 @@ +/* + * Copyright 2021 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package io.netty.build.checkstyle; + +import com.puppycrawl.tools.checkstyle.api.AbstractCheck; +import com.puppycrawl.tools.checkstyle.api.DetailAST; +import com.puppycrawl.tools.checkstyle.api.FileContents; +import com.puppycrawl.tools.checkstyle.api.FullIdent; +import com.puppycrawl.tools.checkstyle.api.TokenTypes; + +import java.util.regex.Pattern; + +/** + * This check verifies that static final buffers are unreleasable and read-only. + * + * It aims to prevent corruption bugs like https://github.com/netty/netty/issues/11792 + * from happening in the future. + */ +public class StaticFinalBufferCheck extends AbstractCheck { + + // Pattern is not multiline because variable definition is flattened and trimmed before the match. + private static final Pattern pattern = Pattern.compile( + "(Unpooled\\s*\\.)?unreleasableBuffer\\(.*?\\)\\s*\\.asReadOnly\\(\\)"); + + @Override + public int[] getRequiredTokens() { + return new int[]{TokenTypes.VARIABLE_DEF}; + } + + @Override + public int[] getDefaultTokens() { + return getRequiredTokens(); + } + + @Override + public int[] getAcceptableTokens() { + return getRequiredTokens(); + } + + @Override + public void visitToken(DetailAST ast) { + DetailAST modifiersAST = ast.findFirstToken(TokenTypes.MODIFIERS); + boolean isStatic = modifiersAST.findFirstToken(TokenTypes.LITERAL_STATIC) != null; + boolean isFinal = modifiersAST.findFirstToken(TokenTypes.FINAL) != null; + FullIdent typeIdent = FullIdent.createFullIdentBelow(ast.findFirstToken(TokenTypes.TYPE)); + if (!isStatic || !isFinal || !typeIdent.getText().endsWith("Buf")) { + return; + } + DetailAST assignAST = ast.findFirstToken(TokenTypes.ASSIGN); + DetailAST semiAST = ast.findFirstToken(TokenTypes.SEMI); + if (assignAST == null || semiAST == null) { + log(ast.getLineNo(), "Missing assignment for static final buffer"); + return; + } + FileContents fc = getFileContents(); + StringBuilder sb = new StringBuilder(); + for (int i = assignAST.getLineNo(); i <= semiAST.getLineNo(); i++) { + // getLineNo returns 1-based line number, getLine expects 0-based. + sb.append(fc.getLine(i - 1).trim()); + } + if (!pattern.matcher(sb.toString()).find()) { + log(ast.getLineNo(), "static final buffer assignment should match pattern " + pattern); + } + } +} diff --git a/common/src/main/resources/io/netty/checkstyle.xml b/common/src/main/resources/io/netty/checkstyle.xml index aa40d65..77fd829 100644 --- a/common/src/main/resources/io/netty/checkstyle.xml +++ b/common/src/main/resources/io/netty/checkstyle.xml @@ -55,7 +55,7 @@ - + @@ -75,8 +75,12 @@ + + + +