This is an automated email from the ASF dual-hosted git repository.

paulk-asert pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/groovy.git


The following commit(s) were added to refs/heads/master by this push:
     new 538f618b3d GROOVY-12161: pin catch-variable debug metadata for 
try/catch without finally
538f618b3d is described below

commit 538f618b3d817f1bb0e1035646e94e64c4d81144
Author: Paul King <[email protected]>
AuthorDate: Thu Jul 16 16:14:21 2026 +1000

    GROOVY-12161: pin catch-variable debug metadata for try/catch without 
finally
    
    Backfills the LocalVariableTable coverage relaxed in GROOVY-11362 when the
    identity catch-all was omitted: assert the catch parameter stays named,
    typed Exception (not Object), with a non-degenerate range — without
    depending on brittle label numbers.
---
 src/test/groovy/bugs/Groovy12161.groovy | 36 +++++++++++++++++++++++++++++++++
 1 file changed, 36 insertions(+)

diff --git a/src/test/groovy/bugs/Groovy12161.groovy 
b/src/test/groovy/bugs/Groovy12161.groovy
index f91120f578..15d6948888 100644
--- a/src/test/groovy/bugs/Groovy12161.groovy
+++ b/src/test/groovy/bugs/Groovy12161.groovy
@@ -248,6 +248,29 @@ final class Groovy12161 extends AbstractBytecodeTestCase {
         ''') == 'check call count: 1'
     }
 
+    @Test
+    void testCatchVariableKeepsNamedTypedValidRangeWithoutFinally() {
+        // The plain try/catch path omits the identity catch-all, but the 
user's catch
+        // parameter must remain a properly named, correctly typed local with 
a non-degenerate
+        // LocalVariableTable range. GROOVY-11362 had to stop asserting this 
once label numbers
+        // became unstable; pin it here without depending on brittle label 
numbers.
+        compile method: 'm', '''
+            int m(int x) {
+                try {
+                    return x
+                } catch (Exception e) {
+                    return -1
+                }
+            }
+        '''
+
+        def locals = localVariables('m')
+        def e = locals.find { it.name == 'e' }
+        assert e != null: "catch variable 'e' missing from LocalVariableTable: 
${locals*.name}"
+        assert e.desc == 'Ljava/lang/Exception;' // not Ljava/lang/Object;
+        assert e.start < e.end: "degenerate LVT range for 'e': [${e.start}, 
${e.end})"
+    }
+
     
//--------------------------------------------------------------------------
 
     private List<TryCatchBlockNode> tryCatchBlocks(final String methodName) {
@@ -278,4 +301,17 @@ final class Groovy12161 extends AbstractBytecodeTestCase {
                     token.startsWith('FRAME') ? 'FRAME' : token
                 }
     }
+
+    /** LocalVariableTable entries as name/desc plus instruction-index 
start/end (valid iff start < end). */
+    private List<Map> localVariables(final String methodName) {
+        assert classBytes != null: 'compile(...) first'
+        def cn = new ClassNode()
+        new ClassReader(classBytes).accept(cn, 0) // keep debug attributes 
(unlike tryCatchBlocks)
+        MethodNode mn = cn.methods.find { it.name == methodName }
+        assert mn != null: "method ${methodName} not found"
+        def insns = mn.instructions
+        (mn.localVariables ?: []).collect { lv ->
+            [name: lv.name, desc: lv.desc, start: insns.indexOf(lv.start), 
end: insns.indexOf(lv.end)]
+        }
+    }
 }

Reply via email to