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

wusheng pushed a commit to branch feature/mal-closure-companion-class
in repository https://gitbox.apache.org/repos/asf/skywalking.git

commit 6bb6236fef96fd2f8224e8a114e842af5754c7f3
Author: Wu Sheng <[email protected]>
AuthorDate: Thu Mar 19 15:15:57 2026 +0800

    MAL v2: replace LambdaMetafactory with companion class pattern for closures
    
    Closure functional interface instances (TagFunction, ForEachFunction,
    PropertiesExtractor, DecorateFunction) were previously wired via
    LambdaMetafactory after class loading, requiring a static helper method
    on the main class and reflective method lookup at runtime.
    
    Replace with a companion class per closure (e.g. MainClass$_tag) that
    directly implements the functional interface with the closure body
    inlined in the SAM method. The main class holds a public static final
    field initialized in a static{} block via new CompanionClass() —
    no reflection, no LambdaMetafactory, no indirection.
    
    Key fix: TagFunction and PropertiesExtractor extend Function<Map,Map>
    whose erased SAM is apply(Object)Object, not apply(Map)Map. The companion
    SAM method uses Object parameter/return with a cast to avoid 
AbstractMethodError.
    
    Remove addClosureMethod(), addStaticLocalVariableTable(), the isStatic
    parameter on addLocalVariableTable(), and generateCompanionMethod() —
    all replaced by generateCompanionBody() which inlines the closure logic
    directly into the companion's SAM method.
---
 oap-server/analyzer/meter-analyzer/CLAUDE.md       |  30 ++-
 .../analyzer/v2/compiler/MALClassGenerator.java    |  77 +++---
 .../analyzer/v2/compiler/MALClosureCodegen.java    | 273 +++++++++++----------
 .../analyzer/v2/compiler/MALCodegenHelper.java     | 121 ---------
 .../v2/compiler/MALClassGeneratorTest.java         |  45 ++++
 5 files changed, 250 insertions(+), 296 deletions(-)

diff --git a/oap-server/analyzer/meter-analyzer/CLAUDE.md 
b/oap-server/analyzer/meter-analyzer/CLAUDE.md
index f954a52e34..6d9056489d 100644
--- a/oap-server/analyzer/meter-analyzer/CLAUDE.md
+++ b/oap-server/analyzer/meter-analyzer/CLAUDE.md
@@ -9,12 +9,13 @@ MAL expression string
   → MALScriptParser.parse(expression)          [ANTLR4 lexer/parser → visitor]
   → MALExpressionModel.Expr (immutable AST)
   → MALClassGenerator.compileFromModel(name, ast)
-      1. collectClosures(ast)          — pre-scan for closure arguments
-      2. addClosureMethod()            — add closure body as method on main 
class
+      1. collectClosures(ast)          — pre-scan AST for closure arguments
+      2. makeCompanionClass()          — one companion per closure, implements 
functional interface
+                                         with closure body inlined directly in 
SAM method
       3. classPool.makeClass()         — create main class implementing 
MalExpression
       4. generateRunMethod()           — emit Java source for 
run(Map<String,SampleFamily>)
-      5. ctClass.toClass(MalExpressionPackageHolder.class)  — load via package 
anchor
-      6. wire closure fields via LambdaMetafactory (no extra .class files)
+      5. toClass() companions first    — static initializer on main class 
references companion ctors
+      6. ctClass.toClass(MalExpressionPackageHolder.class)  — load main class
   → MalExpression instance
 ```
 
@@ -35,7 +36,7 @@ oap-server/analyzer/meter-analyzer/
     MALScriptParser.java              — ANTLR4 facade: expression → AST
     MALExpressionModel.java           — Immutable AST model classes
     MALClassGenerator.java            — Public API, run method codegen, 
metadata extraction
-    MALClosureCodegen.java            — Closure method codegen (inlined on 
main class via LambdaMetafactory)
+    MALClosureCodegen.java            — Companion class codegen: closure body 
inlined in SAM method
     MALCodegenHelper.java             — Static utility methods and shared 
constants
     rt/
       MalExpressionPackageHolder.java — Class loading anchor (empty marker)
@@ -54,6 +55,7 @@ All v2 classes live under 
`org.apache.skywalking.oap.meter.analyzer.v2.*` to avo
 |-----------|---------------|
 | Parser/Model/Generator | 
`org.apache.skywalking.oap.meter.analyzer.v2.compiler` |
 | Generated classes | 
`org.apache.skywalking.oap.meter.analyzer.v2.compiler.rt.{yamlName}_L{lineNo}_{ruleName}`
 |
+| Companion classes | 
`org.apache.skywalking.oap.meter.analyzer.v2.compiler.rt.{yamlName}_L{lineNo}_{ruleName}$_{closureField}`
 |
 | Filter classes | 
`org.apache.skywalking.oap.meter.analyzer.v2.compiler.rt.{yamlName}_L{lineNo}_filter`
 |
 | Package holder | 
`org.apache.skywalking.oap.meter.analyzer.v2.compiler.rt.MalExpressionPackageHolder`
 |
 | Runtime helper | 
`org.apache.skywalking.oap.meter.analyzer.v2.compiler.rt.MalRuntimeHelper` |
@@ -67,7 +69,7 @@ Falls back to `MalExpr_<N>` (global counter) when no hint is 
set.
 
 - **No anonymous inner classes**: Javassist cannot compile `new Consumer() { 
... }` or `new Function() { ... }` in method bodies.
 - **No lambda expressions**: Javassist has no lambda support.
-- **Closure approach**: Closure bodies are compiled as methods on the main 
class (e.g., `_tag_apply(Map)`), then wrapped via `LambdaMetafactory` into 
functional interface instances. No extra `.class` files are produced — the JVM 
creates hidden classes internally (same mechanism `javac` uses for lambdas).
+- **Closure approach**: Each closure becomes a companion class (e.g., 
`MainClass$_tag`) that directly implements the functional interface. The 
closure body is inlined in the SAM method. The main class holds a `public 
static final` field for each closure, initialized in a `static {}` block via 
`new CompanionClass()`. No reflection or `LambdaMetafactory` at runtime. One 
extra `.class` file is produced per closure.
 - **Inner class notation**: Use `$` not `.` for nested classes (e.g., 
`SampleFamilyFunctions$TagFunction`).
 - **`isPresent()`/`get()` instead of `ifPresent()`**: `ifPresent(Consumer)` 
would require an anonymous class. Use `Optional.isPresent()` + `Optional.get()` 
pattern.
 - **Closure interface dispatch**: Different closure call sites use different 
functional interfaces:
@@ -99,10 +101,15 @@ public ExpressionMetadata metadata() {
 
 **Input with closure**: `metric.tag({ tags -> tags['k'] = 'v' })`
 
-One class is generated (e.g., `vm_L5_my_metric` when `yamlSource=vm.yaml:5`):
-- Method `_tag_apply(Map tags)` — contains `tags.put("k", "v"); return tags;`
-- Field `_tag` — typed as `TagFunction`, wired via `LambdaMetafactory` after 
class loading
-- `run()` body calls `metric.tag(this._tag)`
+Two classes are generated (e.g., `vm_L5_my_metric` when 
`yamlSource=vm.yaml:5`):
+
+Main class `vm_L5_my_metric`:
+- `public static final TagFunction _tag;`
+- `static { _tag = new vm_L5_my_metric$_tag(); }`
+- `run()` body calls `sf = ((SampleFamily) samples.getOrDefault("metric", 
EMPTY)).tag(_tag);`
+
+Companion class `vm_L5_my_metric$_tag implements TagFunction`:
+- `public Object apply(Object _raw) { Map tags = (Map) _raw; tags.put("k", 
"v"); return tags; }`
 
 ## ExpressionMetadata (replaces ExpressionParsingContext)
 
@@ -114,7 +121,8 @@ When `SW_DYNAMIC_CLASS_ENGINE_DEBUG=true` environment 
variable is set, generated
 
 ```
 {skywalking}/mal-rt/
-  *.class          - Generated MalExpression .class files (one per expression, 
no separate closure classes)
+  *.class          — Main MalExpression class per expression
+  *$_tag.class     — Companion class per closure (one per 
tag/forEach/instance/decorate call)
 ```
 
 This is the same env variable used by OAL. Useful for debugging code 
generation issues or comparing V1 vs V2 output. In tests, use 
`setClassOutputDir(dir)` instead.
diff --git 
a/oap-server/analyzer/meter-analyzer/src/main/java/org/apache/skywalking/oap/meter/analyzer/v2/compiler/MALClassGenerator.java
 
b/oap-server/analyzer/meter-analyzer/src/main/java/org/apache/skywalking/oap/meter/analyzer/v2/compiler/MALClassGenerator.java
index 465cbddbac..857ff2640f 100644
--- 
a/oap-server/analyzer/meter-analyzer/src/main/java/org/apache/skywalking/oap/meter/analyzer/v2/compiler/MALClassGenerator.java
+++ 
b/oap-server/analyzer/meter-analyzer/src/main/java/org/apache/skywalking/oap/meter/analyzer/v2/compiler/MALClassGenerator.java
@@ -20,8 +20,6 @@ package org.apache.skywalking.oap.meter.analyzer.v2.compiler;
 import java.io.DataOutputStream;
 import java.io.File;
 import java.io.FileOutputStream;
-import java.lang.invoke.MethodHandle;
-import java.lang.invoke.MethodHandles;
 import java.util.ArrayList;
 import java.util.LinkedHashSet;
 import java.util.List;
@@ -161,7 +159,7 @@ public final class MALClassGenerator {
         try (DataOutputStream out = new DataOutputStream(new 
FileOutputStream(file))) {
             ctClass.toBytecode(out);
         } catch (Exception e) {
-            log.warn("Failed to write class file {}: {}", file, 
e.getMessage());
+            log.warn("Failed to write class file {}: {}", file, 
e.getMessage(), e);
         }
     }
 
@@ -285,13 +283,14 @@ public final class MALClassGenerator {
 
             final javassist.bytecode.LocalVariableAttribute lva =
                 new javassist.bytecode.LocalVariableAttribute(cp);
+            int slot = 0;
             lva.addEntry(0, len,
                 cp.addUtf8Info("this"),
-                cp.addUtf8Info("L" + className.replace('.', '/') + ";"), 0);
-            for (int i = 0; i < vars.length; i++) {
+                cp.addUtf8Info("L" + className.replace('.', '/') + ";"), 
slot++);
+            for (final String[] var : vars) {
                 lva.addEntry(0, len,
-                    cp.addUtf8Info(vars[i][0]),
-                    cp.addUtf8Info(vars[i][1]), i + 1);
+                    cp.addUtf8Info(var[0]),
+                    cp.addUtf8Info(var[1]), slot++);
             }
             code.getAttributes().add(lva);
         } catch (Exception e) {
@@ -446,19 +445,34 @@ public final class MALClassGenerator {
         ctClass.addInterface(classPool.get(
             "org.apache.skywalking.oap.meter.analyzer.v2.dsl.MalExpression"));
 
-        // Add closure fields typed as functional interfaces (not concrete 
closure classes)
+        // Generate companion classes — one per closure.
+        // Each companion directly implements the functional interface with the
+        // closure body inlined, so there is no static helper method on the 
main class.
+        final List<CtClass> companionClasses = new ArrayList<>();
+        for (int i = 0; i < closures.size(); i++) {
+            final CtClass companion = cc.makeCompanionClass(
+                ctClass, closureFieldNames.get(i), closures.get(i));
+            companionClasses.add(companion);
+        }
+
+        // Add public static final fields, one per closure
         for (int i = 0; i < closures.size(); i++) {
             ctClass.addField(javassist.CtField.make(
-                "public " + closureInterfaceTypes.get(i) + " "
+                "public static final " + closureInterfaceTypes.get(i) + " "
                     + closureFieldNames.get(i) + ";", ctClass));
         }
 
-        // Add closure bodies as methods on the main class
-        final List<String> closureMethodNames = new ArrayList<>();
-        for (int i = 0; i < closures.size(); i++) {
-            final String methodName = cc.addClosureMethod(
-                ctClass, closureFieldNames.get(i), closures.get(i));
-            closureMethodNames.add(methodName);
+        // Static initializer: explicitly instantiate each companion class.
+        // No method lookup or LambdaMetafactory — the compiler guarantees
+        // method existence because it generates both sides in the same pass.
+        if (!closures.isEmpty()) {
+            final StringBuilder staticInit = new StringBuilder();
+            for (int i = 0; i < closures.size(); i++) {
+                staticInit.append(closureFieldNames.get(i))
+                          .append(" = new 
").append(companionClasses.get(i).getName())
+                          .append("();\n");
+            }
+            ctClass.makeClassInitializer().setBody("{ " + staticInit + "}");
         }
 
         ctClass.addConstructor(CtNewConstructor.defaultConstructor(ctClass));
@@ -490,31 +504,20 @@ public final class MALClassGenerator {
         });
         setSourceFile(ctClass, formatSourceFileName(metricName));
 
+        // Load companions before main class — main class static initializer
+        // references companion constructors, so companions must be loaded 
first.
+        for (final CtClass companion : companionClasses) {
+            writeClassFile(companion);
+            companion.toClass(MalExpressionPackageHolder.class);
+            companion.detach();
+        }
+
         writeClassFile(ctClass);
 
         final Class<?> clazz = 
ctClass.toClass(MalExpressionPackageHolder.class);
         ctClass.detach();
-        final MalExpression instance = (MalExpression) 
clazz.getDeclaredConstructor()
-            .newInstance();
-
-        // Wire closure fields via LambdaMetafactory — creates functional 
interface
-        // instances from method handles pointing to the closure methods on 
this class.
-        // No separate .class files are produced (same mechanism as javac 
lambdas).
-        if (!closures.isEmpty()) {
-            final MethodHandles.Lookup lookup = MethodHandles.privateLookupIn(
-                clazz, MethodHandles.lookup());
-            for (int i = 0; i < closures.size(); i++) {
-                final MALCodegenHelper.ClosureTypeInfo typeInfo =
-                    
MALCodegenHelper.getClosureTypeInfo(closureInterfaceTypes.get(i));
-                final MethodHandle mh = lookup.findVirtual(
-                    clazz, closureMethodNames.get(i), typeInfo.methodType);
-                final Object func = MALCodegenHelper.createLambda(
-                    lookup, typeInfo, mh, clazz, instance);
-                clazz.getField(closureFieldNames.get(i)).set(instance, func);
-            }
-        }
 
-        return instance;
+        return (MalExpression) clazz.getDeclaredConstructor().newInstance();
     }
 
     private static final String RUN_VAR = "sf";
@@ -988,8 +991,8 @@ public final class MALClassGenerator {
 
     private void generateClosureArgument(final StringBuilder sb,
                                          final 
MALExpressionModel.ClosureArgument closure) {
-        // Reference pre-compiled closure field
-        sb.append("this.").append(closureFieldNames.get(closureFieldIndex++));
+        // Reference static closure field (no `this.` — fields are static 
final)
+        sb.append(closureFieldNames.get(closureFieldIndex++));
     }
 
     // Closure statement/expr/condition generation delegated to 
MALClosureCodegen.
diff --git 
a/oap-server/analyzer/meter-analyzer/src/main/java/org/apache/skywalking/oap/meter/analyzer/v2/compiler/MALClosureCodegen.java
 
b/oap-server/analyzer/meter-analyzer/src/main/java/org/apache/skywalking/oap/meter/analyzer/v2/compiler/MALClosureCodegen.java
index 254289b4f5..b41d5d7be7 100644
--- 
a/oap-server/analyzer/meter-analyzer/src/main/java/org/apache/skywalking/oap/meter/analyzer/v2/compiler/MALClosureCodegen.java
+++ 
b/oap-server/analyzer/meter-analyzer/src/main/java/org/apache/skywalking/oap/meter/analyzer/v2/compiler/MALClosureCodegen.java
@@ -20,6 +20,7 @@ package org.apache.skywalking.oap.meter.analyzer.v2.compiler;
 import java.util.List;
 import javassist.ClassPool;
 import javassist.CtClass;
+import javassist.CtNewConstructor;
 import javassist.CtNewMethod;
 import lombok.extern.slf4j.Slf4j;
 
@@ -119,133 +120,6 @@ final class MALClosureCodegen {
         }
     }
 
-    /**
-     * Adds a closure method to the main class instead of creating a separate 
class.
-     * Returns the generated method name.
-     */
-    String addClosureMethod(final CtClass mainClass,
-                            final String fieldName,
-                            final ClosureInfo info) throws Exception {
-        final String className = mainClass.getName();
-        final MALExpressionModel.ClosureArgument closure = info.closure;
-        final List<String> params = closure.getParams();
-        final boolean isForEach = 
MALCodegenHelper.FOR_EACH_FUNCTION_TYPE.equals(info.interfaceType);
-        final boolean isPropertiesExtractor =
-            
MALCodegenHelper.PROPERTIES_EXTRACTOR_TYPE.equals(info.interfaceType);
-
-        if (isForEach) {
-            final String methodName = fieldName + "_accept";
-            final String elementParam = params.size() >= 1 ? params.get(0) : 
"element";
-            final String tagsParam = params.size() >= 2 ? params.get(1) : 
"tags";
-
-            final StringBuilder sb = new StringBuilder();
-            sb.append("public void ").append(methodName).append("(String ")
-              .append(elementParam).append(", java.util.Map 
").append(tagsParam)
-              .append(") {\n");
-            for (final MALExpressionModel.ClosureStatement stmt : 
closure.getBody()) {
-                generateClosureStatement(sb, stmt, tagsParam);
-            }
-            sb.append("}\n");
-
-            if (log.isDebugEnabled()) {
-                log.debug("ForEach closure method:\n{}", sb);
-            }
-            final javassist.CtMethod m = CtNewMethod.make(sb.toString(), 
mainClass);
-            mainClass.addMethod(m);
-            generator.addLocalVariableTable(m, className, new String[][]{
-                {elementParam, "Ljava/lang/String;"},
-                {tagsParam, "Ljava/util/Map;"}
-            });
-            generator.addLineNumberTable(m, 3); // slot 0=this, 1=element, 
2=tags
-            return methodName;
-        } else if (isPropertiesExtractor) {
-            final String methodName = fieldName + "_apply";
-            final String paramName = params.isEmpty() ? "it" : params.get(0);
-
-            final StringBuilder sb = new StringBuilder();
-            sb.append("public java.util.Map ").append(methodName)
-              .append("(java.util.Map ").append(paramName).append(") {\n");
-
-            final List<MALExpressionModel.ClosureStatement> body = 
closure.getBody();
-            if (body.size() == 1
-                    && body.get(0) instanceof 
MALExpressionModel.ClosureExprStatement
-                    && ((MALExpressionModel.ClosureExprStatement) 
body.get(0)).getExpr()
-                        instanceof MALExpressionModel.ClosureMapLiteral) {
-                final MALExpressionModel.ClosureMapLiteral mapLit =
-                    (MALExpressionModel.ClosureMapLiteral)
-                        ((MALExpressionModel.ClosureExprStatement) 
body.get(0)).getExpr();
-                sb.append("  java.util.Map _result = new 
java.util.HashMap();\n");
-                for (final MALExpressionModel.MapEntry entry : 
mapLit.getEntries()) {
-                    sb.append("  _result.put(\"")
-                      
.append(MALCodegenHelper.escapeJava(entry.getKey())).append("\", ");
-                    generateClosureExpr(sb, entry.getValue(), paramName);
-                    sb.append(");\n");
-                }
-                sb.append("  return _result;\n");
-            } else {
-                for (final MALExpressionModel.ClosureStatement stmt : body) {
-                    generateClosureStatement(sb, stmt, paramName);
-                }
-                sb.append("  return ").append(paramName).append(";\n");
-            }
-            sb.append("}\n");
-
-            final javassist.CtMethod m = CtNewMethod.make(sb.toString(), 
mainClass);
-            mainClass.addMethod(m);
-            generator.addLocalVariableTable(m, className, new String[][]{
-                {paramName, "Ljava/util/Map;"}
-            });
-            generator.addLineNumberTable(m, 2); // slot 0=this, 1=it/param
-            return methodName;
-        } else if 
(MALCodegenHelper.DECORATE_FUNCTION_TYPE.equals(info.interfaceType)) {
-            final String methodName = fieldName + "_accept";
-            final String paramName = params.isEmpty() ? "it" : params.get(0);
-
-            final StringBuilder sb = new StringBuilder();
-            sb.append("public void ").append(methodName).append("(Object _arg) 
{\n");
-            sb.append("  
").append(MALCodegenHelper.METER_ENTITY_FQCN).append(" ")
-              .append(paramName).append(" = 
(").append(MALCodegenHelper.METER_ENTITY_FQCN)
-              .append(") _arg;\n");
-            for (final MALExpressionModel.ClosureStatement stmt : 
closure.getBody()) {
-                generateClosureStatement(sb, stmt, paramName, true);
-            }
-            sb.append("}\n");
-
-            if (log.isDebugEnabled()) {
-                log.debug("Decorate closure method:\n{}", sb);
-            }
-            final javassist.CtMethod m = CtNewMethod.make(sb.toString(), 
mainClass);
-            mainClass.addMethod(m);
-            generator.addLocalVariableTable(m, className, new String[][]{
-                {"_arg", "Ljava/lang/Object;"},
-                {paramName, "L" + 
MALCodegenHelper.METER_ENTITY_FQCN.replace('.', '/') + ";"}
-            });
-            generator.addLineNumberTable(m, 2); // slot 0=this, 1=_arg
-            return methodName;
-        } else {
-            // TagFunction: Map<String,String> apply(Map<String,String> tags)
-            final String methodName = fieldName + "_apply";
-            final String paramName = params.isEmpty() ? "it" : params.get(0);
-
-            final StringBuilder sb = new StringBuilder();
-            sb.append("public java.util.Map ").append(methodName)
-              .append("(java.util.Map ").append(paramName).append(") {\n");
-            for (final MALExpressionModel.ClosureStatement stmt : 
closure.getBody()) {
-                generateClosureStatement(sb, stmt, paramName);
-            }
-            sb.append("  return ").append(paramName).append(";\n");
-            sb.append("}\n");
-
-            final javassist.CtMethod m = CtNewMethod.make(sb.toString(), 
mainClass);
-            mainClass.addMethod(m);
-            generator.addLocalVariableTable(m, className, new String[][]{
-                {paramName, "Ljava/util/Map;"}
-            });
-            generator.addLineNumberTable(m, 2); // slot 0=this, 1=it/param
-            return methodName;
-        }
-    }
-
     void generateClosureStatement(final StringBuilder sb,
                                   final MALExpressionModel.ClosureStatement 
stmt,
                                   final String paramName) {
@@ -708,6 +582,151 @@ final class MALClosureCodegen {
         }
     }
 
+    /**
+     * Generates a companion class that implements the given functional 
interface
+     * by delegating directly to the static closure method on the main class.
+     * No reflection or method lookup — the compiler guarantees both exist.
+     *
+     * <p>Example output for a TagFunction:
+     * <pre>
+     *   class MainClass$_tag implements TagFunction {
+     *     public java.util.Map apply(java.util.Map tags) {
+     *       return MainClass._tag_apply(tags);
+     *     }
+     *   }
+     * </pre>
+     */
+    CtClass makeCompanionClass(final CtClass mainClass,
+                               final String fieldName,
+                               final ClosureInfo info) throws Exception {
+        final String companionName = mainClass.getName() + "$" + fieldName;
+        final CtClass companion = classPool.makeClass(companionName);
+        companion.addInterface(classPool.get(info.interfaceType));
+        
companion.addConstructor(CtNewConstructor.defaultConstructor(companion));
+
+        final String methodBody = generateCompanionBody(fieldName, info);
+        if (log.isDebugEnabled()) {
+            log.debug("Companion class [{}] apply():\n{}", companionName, 
methodBody);
+        }
+        final javassist.CtMethod m = CtNewMethod.make(methodBody, companion);
+        companion.addMethod(m);
+        addCompanionLocalVariableTable(m, info);
+        generator.addLineNumberTable(m, firstResultSlot(info));
+        return companion;
+    }
+
+    private String generateCompanionBody(final String fieldName,
+                                         final ClosureInfo info) {
+        final MALExpressionModel.ClosureArgument closure = info.closure;
+        final List<String> params = closure.getParams();
+        final StringBuilder sb = new StringBuilder();
+
+        if 
(MALCodegenHelper.FOR_EACH_FUNCTION_TYPE.equals(info.interfaceType)) {
+            // ForEachFunction: void accept(String element, Map tags)  — no 
erasure issue
+            final String elementParam = params.size() >= 1 ? params.get(0) : 
"element";
+            final String tagsParam = params.size() >= 2 ? params.get(1) : 
"tags";
+            sb.append("public void accept(String ").append(elementParam)
+              .append(", java.util.Map ").append(tagsParam).append(") {\n");
+            for (final MALExpressionModel.ClosureStatement stmt : 
closure.getBody()) {
+                generateClosureStatement(sb, stmt, tagsParam);
+            }
+            sb.append("}\n");
+
+        } else if 
(MALCodegenHelper.DECORATE_FUNCTION_TYPE.equals(info.interfaceType)) {
+            // DecorateFunction extends Consumer<MeterEntity> — erased SAM: 
accept(Object)void
+            final String paramName = params.isEmpty() ? "it" : params.get(0);
+            sb.append("public void accept(Object _arg) {\n");
+            sb.append("  
").append(MALCodegenHelper.METER_ENTITY_FQCN).append(" ")
+              .append(paramName).append(" = 
(").append(MALCodegenHelper.METER_ENTITY_FQCN)
+              .append(") _arg;\n");
+            for (final MALExpressionModel.ClosureStatement stmt : 
closure.getBody()) {
+                generateClosureStatement(sb, stmt, paramName, true);
+            }
+            sb.append("}\n");
+
+        } else if 
(MALCodegenHelper.PROPERTIES_EXTRACTOR_TYPE.equals(info.interfaceType)) {
+            // PropertiesExtractor extends Function<Map,Map> — erased SAM: 
apply(Object)Object
+            final String paramName = params.isEmpty() ? "it" : params.get(0);
+            sb.append("public Object apply(Object _raw) {\n");
+            sb.append("  java.util.Map ").append(paramName)
+              .append(" = (java.util.Map) _raw;\n");
+            final List<MALExpressionModel.ClosureStatement> body = 
closure.getBody();
+            if (body.size() == 1
+                    && body.get(0) instanceof 
MALExpressionModel.ClosureExprStatement
+                    && ((MALExpressionModel.ClosureExprStatement) 
body.get(0)).getExpr()
+                        instanceof MALExpressionModel.ClosureMapLiteral) {
+                final MALExpressionModel.ClosureMapLiteral mapLit =
+                    (MALExpressionModel.ClosureMapLiteral)
+                        ((MALExpressionModel.ClosureExprStatement) 
body.get(0)).getExpr();
+                sb.append("  java.util.Map _result = new 
java.util.HashMap();\n");
+                for (final MALExpressionModel.MapEntry entry : 
mapLit.getEntries()) {
+                    sb.append("  _result.put(\"")
+                      
.append(MALCodegenHelper.escapeJava(entry.getKey())).append("\", ");
+                    generateClosureExpr(sb, entry.getValue(), paramName);
+                    sb.append(");\n");
+                }
+                sb.append("  return _result;\n");
+            } else {
+                for (final MALExpressionModel.ClosureStatement stmt : body) {
+                    generateClosureStatement(sb, stmt, paramName);
+                }
+                sb.append("  return ").append(paramName).append(";\n");
+            }
+            sb.append("}\n");
+
+        } else {
+            // TagFunction extends Function<Map,Map> — erased SAM: 
apply(Object)Object
+            final String paramName = params.isEmpty() ? "it" : params.get(0);
+            sb.append("public Object apply(Object _raw) {\n");
+            sb.append("  java.util.Map ").append(paramName)
+              .append(" = (java.util.Map) _raw;\n");
+            for (final MALExpressionModel.ClosureStatement stmt : 
closure.getBody()) {
+                generateClosureStatement(sb, stmt, paramName);
+            }
+            sb.append("  return ").append(paramName).append(";\n");
+            sb.append("}\n");
+        }
+        return sb.toString();
+    }
+
+    private void addCompanionLocalVariableTable(final javassist.CtMethod m,
+                                                final ClosureInfo info) {
+        final List<String> params = info.closure.getParams();
+        if 
(MALCodegenHelper.FOR_EACH_FUNCTION_TYPE.equals(info.interfaceType)) {
+            final String elementParam = params.size() >= 1 ? params.get(0) : 
"element";
+            final String tagsParam = params.size() >= 2 ? params.get(1) : 
"tags";
+            // instance method: slot 0=this, 1=element, 2=tags
+            generator.addLocalVariableTable(m, 
m.getDeclaringClass().getName(), new String[][]{
+                {elementParam, "Ljava/lang/String;"},
+                {tagsParam, "Ljava/util/Map;"}
+            });
+        } else if 
(MALCodegenHelper.DECORATE_FUNCTION_TYPE.equals(info.interfaceType)) {
+            final String paramName = params.isEmpty() ? "it" : params.get(0);
+            // instance method: slot 0=this, 1=_arg, 2=paramName
+            generator.addLocalVariableTable(m, 
m.getDeclaringClass().getName(), new String[][]{
+                {"_arg", "Ljava/lang/Object;"},
+                {paramName, "L" + 
MALCodegenHelper.METER_ENTITY_FQCN.replace('.', '/') + ";"}
+            });
+        } else {
+            final String paramName = params.isEmpty() ? "it" : params.get(0);
+            // instance method: slot 0=this, 1=_raw, 2=paramName
+            generator.addLocalVariableTable(m, 
m.getDeclaringClass().getName(), new String[][]{
+                {"_raw", "Ljava/lang/Object;"},
+                {paramName, "Ljava/util/Map;"}
+            });
+        }
+    }
+
+    private int firstResultSlot(final ClosureInfo info) {
+        if 
(MALCodegenHelper.FOR_EACH_FUNCTION_TYPE.equals(info.interfaceType)) {
+            return 3; // slot 0=this, 1=element, 2=tags, 3+=locals
+        } else if 
(MALCodegenHelper.DECORATE_FUNCTION_TYPE.equals(info.interfaceType)) {
+            return 3; // slot 0=this, 1=_arg, 2=paramName, 3+=locals
+        } else {
+            return 3; // slot 0=this, 1=_raw, 2=paramName, 3+=locals
+        }
+    }
+
     void generateClosureCondition(final StringBuilder sb,
                                   final MALExpressionModel.ClosureCondition 
cond,
                                   final String paramName) {
diff --git 
a/oap-server/analyzer/meter-analyzer/src/main/java/org/apache/skywalking/oap/meter/analyzer/v2/compiler/MALCodegenHelper.java
 
b/oap-server/analyzer/meter-analyzer/src/main/java/org/apache/skywalking/oap/meter/analyzer/v2/compiler/MALCodegenHelper.java
index 8f02be1df5..e85138b3d8 100644
--- 
a/oap-server/analyzer/meter-analyzer/src/main/java/org/apache/skywalking/oap/meter/analyzer/v2/compiler/MALCodegenHelper.java
+++ 
b/oap-server/analyzer/meter-analyzer/src/main/java/org/apache/skywalking/oap/meter/analyzer/v2/compiler/MALCodegenHelper.java
@@ -17,11 +17,6 @@
 
 package org.apache.skywalking.oap.meter.analyzer.v2.compiler;
 
-import java.lang.invoke.CallSite;
-import java.lang.invoke.LambdaMetafactory;
-import java.lang.invoke.MethodHandle;
-import java.lang.invoke.MethodHandles;
-import java.lang.invoke.MethodType;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -172,122 +167,6 @@ final class MALCodegenHelper {
         return null;
     }
 
-    // ---- LambdaMetafactory wiring for closure methods ----
-
-    /**
-     * Closure type metadata for each functional interface used in MAL 
closures.
-     * Used to create LambdaMetafactory-based wrappers from methods on the 
main class.
-     */
-    static final class ClosureTypeInfo {
-        final Class<?> interfaceClass;
-        final String samName;
-        final MethodType samType;
-        final MethodType instantiatedType;
-        final MethodType methodType;
-
-        ClosureTypeInfo(final Class<?> interfaceClass,
-                        final String samName,
-                        final MethodType samType,
-                        final MethodType instantiatedType,
-                        final MethodType methodType) {
-            this.interfaceClass = interfaceClass;
-            this.samName = samName;
-            this.samType = samType;
-            this.instantiatedType = instantiatedType;
-            this.methodType = methodType;
-        }
-    }
-
-    private static final Map<String, ClosureTypeInfo> CLOSURE_TYPE_INFO;
-
-    static {
-        CLOSURE_TYPE_INFO = new HashMap<>();
-
-        // TagFunction extends Function<Map, Map>
-        // SAM: apply(Object) → Object (erased), instantiated: apply(Map) → Map
-        CLOSURE_TYPE_INFO.put(
-            "org.apache.skywalking.oap.meter.analyzer.v2.dsl"
-                + ".SampleFamilyFunctions$TagFunction",
-            new ClosureTypeInfo(
-                org.apache.skywalking.oap.meter.analyzer.v2.dsl
-                    .SampleFamilyFunctions.TagFunction.class,
-                "apply",
-                MethodType.methodType(Object.class, Object.class),
-                MethodType.methodType(Map.class, Map.class),
-                MethodType.methodType(Map.class, Map.class)));
-
-        // ForEachFunction — not generic, SAM = instantiated
-        CLOSURE_TYPE_INFO.put(FOR_EACH_FUNCTION_TYPE,
-            new ClosureTypeInfo(
-                org.apache.skywalking.oap.meter.analyzer.v2.dsl
-                    .SampleFamilyFunctions.ForEachFunction.class,
-                "accept",
-                MethodType.methodType(void.class, String.class, Map.class),
-                MethodType.methodType(void.class, String.class, Map.class),
-                MethodType.methodType(void.class, String.class, Map.class)));
-
-        // PropertiesExtractor extends Function<Map, Map>
-        CLOSURE_TYPE_INFO.put(PROPERTIES_EXTRACTOR_TYPE,
-            new ClosureTypeInfo(
-                org.apache.skywalking.oap.meter.analyzer.v2.dsl
-                    .SampleFamilyFunctions.PropertiesExtractor.class,
-                "apply",
-                MethodType.methodType(Object.class, Object.class),
-                MethodType.methodType(Map.class, Map.class),
-                MethodType.methodType(Map.class, Map.class)));
-
-        // DecorateFunction extends Consumer<MeterEntity>
-        // SAM: accept(Object) → void (erased), instantiated: accept(Object) → 
void
-        CLOSURE_TYPE_INFO.put(DECORATE_FUNCTION_TYPE,
-            new ClosureTypeInfo(
-                org.apache.skywalking.oap.meter.analyzer.v2.dsl
-                    .SampleFamilyFunctions.DecorateFunction.class,
-                "accept",
-                MethodType.methodType(void.class, Object.class),
-                MethodType.methodType(void.class, Object.class),
-                MethodType.methodType(void.class, Object.class)));
-    }
-
-    static ClosureTypeInfo getClosureTypeInfo(final String interfaceType) {
-        return CLOSURE_TYPE_INFO.get(interfaceType);
-    }
-
-    /**
-     * Creates a functional interface instance from a method handle using
-     * {@link LambdaMetafactory}. This is the same mechanism {@code javac}
-     * uses to compile lambda expressions — the JIT can fully inline through
-     * the callsite. No separate {@code .class} file is produced on disk.
-     */
-    /**
-     * Creates a functional interface instance from a direct (unbound) method 
handle
-     * using {@link LambdaMetafactory}, capturing the target instance.
-     *
-     * @param target a direct method handle (not bound via bindTo)
-     * @param receiverClass the class of the instance to capture
-     * @param receiver the instance to capture as the lambda's receiver
-     */
-    static Object createLambda(final MethodHandles.Lookup lookup,
-                               final ClosureTypeInfo typeInfo,
-                               final MethodHandle target,
-                               final Class<?> receiverClass,
-                               final Object receiver) throws Exception {
-        try {
-            // The factory type captures the receiver: (ReceiverClass) → 
InterfaceType
-            final CallSite site = LambdaMetafactory.metafactory(
-                lookup,
-                typeInfo.samName,
-                MethodType.methodType(typeInfo.interfaceClass, receiverClass),
-                typeInfo.samType,
-                target,
-                typeInfo.instantiatedType);
-            return site.getTarget().invoke(receiver);
-        } catch (final Exception e) {
-            throw e;
-        } catch (final Throwable t) {
-            throw new RuntimeException("Failed to create lambda for " + 
typeInfo.samName, t);
-        }
-    }
-
     /**
      * Checks whether a closure expression returns {@code boolean} by 
inspecting
      * the last method call in the chain against {@link String} method 
signatures.
diff --git 
a/oap-server/analyzer/meter-analyzer/src/test/java/org/apache/skywalking/oap/meter/analyzer/v2/compiler/MALClassGeneratorTest.java
 
b/oap-server/analyzer/meter-analyzer/src/test/java/org/apache/skywalking/oap/meter/analyzer/v2/compiler/MALClassGeneratorTest.java
index 8bd2d4c179..017558eea5 100644
--- 
a/oap-server/analyzer/meter-analyzer/src/test/java/org/apache/skywalking/oap/meter/analyzer/v2/compiler/MALClassGeneratorTest.java
+++ 
b/oap-server/analyzer/meter-analyzer/src/test/java/org/apache/skywalking/oap/meter/analyzer/v2/compiler/MALClassGeneratorTest.java
@@ -181,6 +181,51 @@ class MALClassGeneratorTest {
 
     // ==================== Bytecode verification ====================
 
+    @Test
+    void closureCompanionClassWritten() throws Exception {
+        final java.io.File tmpDir = 
java.nio.file.Files.createTempDirectory("mal-companion").toFile();
+        try {
+            final ClassPool pool = new ClassPool(true);
+            final MALClassGenerator gen = new MALClassGenerator(pool);
+            gen.setClassOutputDir(tmpDir);
+            gen.setClassNameHint("test_closure");
+            gen.compile("test_closure", "metric.tag({ tags -> tags.service = 
'svc1' })");
+            final java.io.File[] all = tmpDir.listFiles((d, n) -> 
n.endsWith(".class"));
+            assertNotNull(all);
+            assertTrue(all.length >= 2, "Should have main + companion class, 
found: "
+                + java.util.Arrays.toString(all));
+            final boolean hasCompanion = java.util.Arrays.stream(all)
+                .anyMatch(f -> f.getName().contains("$"));
+            assertTrue(hasCompanion, "Companion class file with '$' should 
exist, found: "
+                + java.util.Arrays.toString(all));
+        } finally {
+            for (final java.io.File f : tmpDir.listFiles()) { f.delete(); }
+            tmpDir.delete();
+        }
+    }
+
+    @Test
+    void closureCompanionWithDefAndRegexWritten() throws Exception {
+        final java.io.File tmpDir = 
java.nio.file.Files.createTempDirectory("mal-companion-def").toFile();
+        try {
+            final ClassPool pool = new ClassPool(true);
+            final MALClassGenerator gen = new MALClassGenerator(pool);
+            gen.setClassOutputDir(tmpDir);
+            gen.setClassNameHint("test_closure_def");
+            gen.setYamlSource("envoy-ca.yaml:34");
+            // Replicates the envoy-ca.yaml closure with `def matcher` + regex
+            gen.compile("test_closure_def",
+                "metric.tag({ tags -> tags.k = tags.v }).service(['app'], 
Layer.MESH_DP)");
+            final java.io.File[] all = tmpDir.listFiles((d, n) -> 
n.endsWith(".class"));
+            assertNotNull(all);
+            assertTrue(all.length >= 2, "Should have main + companion, found: "
+                + java.util.Arrays.toString(all));
+        } finally {
+            for (final java.io.File f : tmpDir.listFiles()) { f.delete(); }
+            tmpDir.delete();
+        }
+    }
+
     @Test
     void runMethodHasLocalVariableTable() throws Exception {
         final java.io.File tmpDir = 
java.nio.file.Files.createTempDirectory("mal-lvt").toFile();


Reply via email to