This is an automated email from the ASF dual-hosted git repository. sunlan pushed a commit to branch GROOVY-11263 in repository https://gitbox.apache.org/repos/asf/groovy.git
commit 81b323ed3e429021e48eb5b7a9a539272e52633f Author: Daniel Sun <sun...@apache.org> AuthorDate: Mon Jan 1 02:43:29 2024 +0800 GROOVY-11263: Analyze dead code --- .../codehaus/groovy/classgen/DeadCodeAnalyzer.java | 66 +++++++ .../org/codehaus/groovy/classgen/Verifier.java | 17 ++ .../codehaus/groovy/control/CompilationUnit.java | 2 +- src/test/groovy/bugs/Groovy11263.groovy | 215 +++++++++++++++++++++ 4 files changed, 299 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/codehaus/groovy/classgen/DeadCodeAnalyzer.java b/src/main/java/org/codehaus/groovy/classgen/DeadCodeAnalyzer.java new file mode 100644 index 0000000000..010c42a5ed --- /dev/null +++ b/src/main/java/org/codehaus/groovy/classgen/DeadCodeAnalyzer.java @@ -0,0 +1,66 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF 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 org.codehaus.groovy.classgen; + +import org.codehaus.groovy.ast.ClassCodeVisitorSupport; +import org.codehaus.groovy.ast.stmt.BlockStatement; +import org.codehaus.groovy.ast.stmt.ReturnStatement; +import org.codehaus.groovy.ast.stmt.Statement; +import org.codehaus.groovy.control.SourceUnit; + +/** + * Analyze AST for dead code + * + * @since 5.0.0 + */ +public class DeadCodeAnalyzer extends ClassCodeVisitorSupport { + + private final SourceUnit sourceUnit; + + public DeadCodeAnalyzer(final SourceUnit sourceUnit) { + this.sourceUnit = sourceUnit; + } + + @Override + protected SourceUnit getSourceUnit() { + return sourceUnit; + } + @Override + public void visitBlockStatement(BlockStatement statement) { + analyzeDeadCode(statement); + super.visitBlockStatement(statement); + } + + private void analyzeDeadCode(BlockStatement block) { + int foundCnt = 0; + for (Statement statement : block.getStatements()) { + if (statement instanceof ReturnStatement +// || statement instanceof BreakStatement // TODO groovy.BreakContinueLabelTest.testLabelCanOccurMultipleTimesInSameScope +// || statement instanceof ContinueStatement + ) { + foundCnt++; + if (1 == foundCnt) continue; + } + + if (foundCnt > 0) { + addError("Unreachable statement found", statement); + } + } + } +} diff --git a/src/main/java/org/codehaus/groovy/classgen/Verifier.java b/src/main/java/org/codehaus/groovy/classgen/Verifier.java index b93b9e24c0..5298c39f89 100644 --- a/src/main/java/org/codehaus/groovy/classgen/Verifier.java +++ b/src/main/java/org/codehaus/groovy/classgen/Verifier.java @@ -64,6 +64,7 @@ import org.codehaus.groovy.classgen.asm.BytecodeHelper; import org.codehaus.groovy.classgen.asm.MopWriter; import org.codehaus.groovy.classgen.asm.OptimizingStatementWriter.ClassNodeSkip; import org.codehaus.groovy.classgen.asm.WriterController; +import org.codehaus.groovy.control.SourceUnit; import org.codehaus.groovy.reflection.ClassInfo; import org.codehaus.groovy.syntax.RuntimeParserException; import org.codehaus.groovy.syntax.Token; @@ -167,6 +168,15 @@ public class Verifier implements GroovyClassVisitor, Opcodes { private ClassNode classNode; private MethodNode methodNode; + private SourceUnit source; + + public Verifier() { + } + + public Verifier(SourceUnit source) { + this.source = source; + } + public ClassNode getClassNode() { return classNode; } @@ -267,6 +277,7 @@ public class Verifier implements GroovyClassVisitor, Opcodes { addCovariantMethods(node); detectNonSealedClasses(node); checkFinalVariables(node); + checkDeadCode(node); } private static final String[] INVALID_COMPONENTS = {"clone", "finalize", "getClass", "hashCode", "notify", "notifyAll", "toString", "wait"}; @@ -302,6 +313,12 @@ public class Verifier implements GroovyClassVisitor, Opcodes { visitor.visitClass(node); } + private void checkDeadCode(final ClassNode node) { + if (null == source) return; + GroovyClassVisitor visitor = new DeadCodeAnalyzer(source); + visitor.visitClass(node); + } + protected FinalVariableAnalyzer.VariableNotFinalCallback getFinalVariablesCallback() { return new FinalVariableAnalyzer.VariableNotFinalCallback() { @Override diff --git a/src/main/java/org/codehaus/groovy/control/CompilationUnit.java b/src/main/java/org/codehaus/groovy/control/CompilationUnit.java index 071f6e3a0c..1d3be4e3f7 100644 --- a/src/main/java/org/codehaus/groovy/control/CompilationUnit.java +++ b/src/main/java/org/codehaus/groovy/control/CompilationUnit.java @@ -750,7 +750,7 @@ public class CompilationUnit extends ProcessingUnit { public void call(final SourceUnit source, final GeneratorContext context, final ClassNode classNode) throws CompilationFailedException { new OptimizerVisitor(CompilationUnit.this).visitClass(classNode, source); // GROOVY-4272: repositioned from static import visitor - GroovyClassVisitor visitor = new Verifier(); + GroovyClassVisitor visitor = new Verifier(source); try { visitor.visitClass(classNode); } catch (RuntimeParserException rpe) { diff --git a/src/test/groovy/bugs/Groovy11263.groovy b/src/test/groovy/bugs/Groovy11263.groovy new file mode 100644 index 0000000000..67dd8f2182 --- /dev/null +++ b/src/test/groovy/bugs/Groovy11263.groovy @@ -0,0 +1,215 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF 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 bugs + +import org.junit.Test + +import static groovy.test.GroovyAssert.assertScript +import static groovy.test.GroovyAssert.shouldFail + +final class Groovy11263 { + + @Test + void testUnreachableStatementAfterReturn1() { + def err = shouldFail '''\ + def m() { + return + def a = 1 + } + ''' + + assert err ==~ /(?s)(.+)\s+Unreachable statement found\s+@ line 3, column 17\.\s+def a = 1\s+(.+)/ + } + + @Test + void testUnreachableStatementAfterReturn2() { + def err = shouldFail '''\ + class X { + X() { + return + def a = 1 + } + } + ''' + + assert err ==~ /(?s)(.+)\s+Unreachable statement found\s+@ line 4, column 21\.\s+def a = 1\s+(.+)/ + } + + @Test + void testUnreachableStatementAfterReturn3() { + def err = shouldFail '''\ + { + return + def a = 1 + } + ''' + + assert err ==~ /(?s)(.+)\s+Unreachable statement found\s+@ line 3, column 17\.\s+def a = 1\s+(.+)/ + } + + @Test + void testUnreachableStatementAfterReturn4() { + def err = shouldFail '''\ + for (var i in [1, 2, 3]) { + return + def a = 1 + } + ''' + + assert err ==~ /(?s)(.+)\s+Unreachable statement found\s+@ line 3, column 17\.\s+def a = 1\s+(.+)/ + } + + @Test + void testUnreachableStatementAfterReturn5() { + def err = shouldFail '''\ + while (true) { + return + def a = 1 + } + ''' + + assert err ==~ /(?s)(.+)\s+Unreachable statement found\s+@ line 3, column 17\.\s+def a = 1\s+(.+)/ + } + + @Test + void testUnreachableStatementAfterReturn6() { + def err = shouldFail '''\ + do { + return + def a = 1 + } while (true) + ''' + + assert err ==~ /(?s)(.+)\s+Unreachable statement found\s+@ line 3, column 17\.\s+def a = 1\s+(.+)/ + } + + @Test + void testUnreachableStatementAfterReturn7() { + def err = shouldFail '''\ + if (true) { + return + def a = 1 + } + ''' + + assert err ==~ /(?s)(.+)\s+Unreachable statement found\s+@ line 3, column 17\.\s+def a = 1\s+(.+)/ + } + + @Test + void testUnreachableStatementAfterReturn8() { + def err = shouldFail '''\ + if (true) { + } else { + return + def a = 1 + } + ''' + + assert err ==~ /(?s)(.+)\s+Unreachable statement found\s+@ line 4, column 17\.\s+def a = 1\s+(.+)/ + } + + @Test + void testUnreachableStatementAfterReturn9() { + def err = shouldFail '''\ + try { + return + def a = 1 + } catch (e) { + } + ''' + + assert err ==~ /(?s)(.+)\s+Unreachable statement found\s+@ line 3, column 17\.\s+def a = 1\s+(.+)/ + } + + @Test + void testUnreachableStatementAfterReturn10() { + def err = shouldFail '''\ + try { + } catch (e) { + return + def a = 1 + } + ''' + + assert err ==~ /(?s)(.+)\s+Unreachable statement found\s+@ line 4, column 17\.\s+def a = 1\s+(.+)/ + } + + @Test + void testUnreachableStatementAfterReturn11() { + def err = shouldFail '''\ + try { + } finally { + return + def a = 1 + } + ''' + + assert err ==~ /(?s)(.+)\s+Unreachable statement found\s+@ line 4, column 17\.\s+def a = 1\s+(.+)/ + } + + @Test + void testUnreachableStatementAfterReturn12() { + def err = shouldFail '''\ + switch(1) { + case 1: + return + def a = 1 + } + ''' + + assert err ==~ /(?s)(.+)\s+Unreachable statement found\s+@ line 4, column 21\.\s+def a = 1\s+(.+)/ + } + + @Test + void testUnreachableStatementAfterReturn13() { + def err = shouldFail '''\ + switch(1) { + default: + return + def a = 1 + } + ''' + + assert err ==~ /(?s)(.+)\s+Unreachable statement found\s+@ line 4, column 21\.\s+def a = 1\s+(.+)/ + } + + @Test + void testUnreachableStatementAfterReturn14() { + def err = shouldFail '''\ + [1, 2, 3].each { + return + def a = 1 + } + ''' + + assert err ==~ /(?s)(.+)\s+Unreachable statement found\s+@ line 3, column 17\.\s+def a = 1\s+(.+)/ + } + + @Test + void testUnreachableStatementAfterReturn15() { + def err = shouldFail '''\ + [1, 2, 3].each(e -> { + return + def a = 1 + }) + ''' + + assert err ==~ /(?s)(.+)\s+Unreachable statement found\s+@ line 3, column 17\.\s+def a = 1\s+(.+)/ + } +}