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+(.+)/
+    }
+}

Reply via email to