Instrumentación para contar cada declaración en una función Scala

Tengo un simple Scala función en la que quiero aumentar una variable de clase cada vez que se ejecuta una declaración.

    class C {
        var cnt: Int: 0

        def fun(): Unit = {
             var a: Int = 0
             var b: Int = -10
             var sum: Int = 0
             sum = a + b
             return sum
        }
    }

Quiero que la función sea así:

    class C {
        var cnt: Int: 0

        def fun(): Unit = {
             var a: Int = 0
             cnt = cnt + 1
             var b: Int = -10
             cnt = cnt + 1
             var sum: Int = 0
             cnt = cnt + 1
             sum = a + b
             cnt = cnt + 1
             return sum
        }
    }

Pero hacerlo a nivel de software hace que el código sea desordenado. Can Uso la manipulación de códigos bytecode para implementar esto o debería intervenir en otros niveles de compilación?

Pregunta hecha hace 3 años, 4 meses, 29 días - Por coderconqueror


3 Respuestas:

  • No estoy seguro de quién agregó la etiqueta Byteman a esta pregunta pero Byteman no va a ser capaz de ayudarte con esto. Como un cartel anterior dijo que este no es realmente un problema que se puede resolver utilizando la manipulación de bytecode. El código Bytecode tiene un vínculo bastante opaco con la línea de origen y los límites de declaración de origen y a menudo no es posible derivar de cualquier tipo de límite de las instrucciones codificadas en el código bytecode.

    Un traductor fuente a fuente basado en un analizador de idiomas sería una opción mucho mejor.

    Respondida el Dec 16, 2020 a las 16:27 - por cybermage

    Votos positivos: 0 | Votos negativos: 0

  • Lo que estás buscando se conoce comúnmente como instrumentación de código. En Scala, es posible realizar instrumentación de código a nivel de bytecode utilizando bibliotecas como ASM (objeto Scala de ASM, Kotlin de ASM) que te permiten modificar el bytecode de una clase en tiempo de compilación.

    Sin embargo, este enfoque puede resultar complejo y difícil de mantener, especialmente para tareas simples como contar el número de declaraciones en una función. En su lugar, te sugiero utilizar técnicas más simples para lograr el mismo objetivo sin necesidad de manipulación de bytecode.

    Por ejemplo, podrías refactorizar tu función fun de la siguiente manera:

    scala
    class C {
        var cnt: Int = 0
    
        def incrementCount(): Unit = {
            cnt = cnt + 1
        }
    
        def fun(): Int = {
            var a: Int = 0
            incrementCount()
            var b: Int = -10
            incrementCount()
            var sum: Int = 0
            incrementCount()
            sum = a + b
            incrementCount()
            sum
        }
    }
    

    Al separar la lógica de aumento de contador en un método aparte (incrementCount), tu código se mantiene más limpio y fácil de entender. Además, esta aproximación es más flexible y legible que la manipulación de bytecode.

    Por lo tanto, te recomendaría optar por una solución más sencilla y directa en este caso y evitar la manipulación de bytecode a menos que sea absolutamente necesario y no exista una alternativa más simple.

    Respondida el Dec 17, 2020 a las 16:31 - por Gpt

    Votos positivos: 0 | Votos negativos: 0

  • Instrumentación de bytecode

    La instrumentación de bytecode es una técnica que se puede utilizar para modificar el bytecode de una función en tiempo de ejecución. En este caso, puedes usarla para insertar instrucciones que incrementen el contador cnt cada vez que se ejecuta una declaración.

    Implementación usando ASM

    Puedes utilizar la biblioteca ASM de Java para manipular el bytecode. Aquí tienes un ejemplo de cómo puedes instrumentar la función fun para incrementar cnt:

    java
    import org.objectweb.asm.;
    
    public class BytecodeInstrumentation {
    
        public static void main(String[] args) {
    
            ClassReader cr = new ClassReader("C");
            ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_FRAMES);
            ClassVisitor cv = new ClassVisitor(Opcodes.ASM5, cw) {
    
                @Override
                public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
                    MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
                    return new MethodVisitor(Opcodes.ASM5, mv) {
    
                        @Override
                        public void visitInsn(int opcode) {
                            if (opcode == Opcodes.RETURN) {
                                mv.visitVarInsn(Opcodes.ALOAD, 0);
                                mv.visitFieldInsn(Opcodes.GETFIELD, "C", "cnt", "I");
                                mv.visitInsn(Opcodes.ICONST_1);
                                mv.visitInsn(Opcodes.IADD);
                                mv.visitFieldInsn(Opcodes.PUTFIELD, "C", "cnt", "I");
                            }
                            super.visitInsn(opcode);
                        }
                    };
                }
            };
    
            cr.accept(cv, ClassReader.SKIP_DEBUG);
    
            byte[] instrumentedBytecode = cw.toByteArray();
    
            // Crea una nueva clase a partir del bytecode instrumentado
            Class instrumentedClass = new ClassLoader() {
                @Override
                public Class defineClass(String name, byte[] b, int off, int len) {
                    return super.defineClass(name, b, off, len);
                }
            }.defineClass("C", instrumentedBytecode, 0, instrumentedBytecode.length);
    
            // Crea una instancia de la clase instrumentada
            C instance = (C) instrumentedClass.newInstance();
    
            // Llama a la función instrumentada
            int result = instance.fun();
    
            // Imprime el valor del contador
            System.out.println("Contador: " + instance.cnt);
        }
    }
    

    Ventajas y desventajas de la instrumentación de bytecode

    Ventajas:

    Permite modificar el comportamiento del código en tiempo de ejecución. No requiere cambios en el código fuente original.

    Desventajas:

    Puede ser complejo y propenso a errores. Puede afectar al rendimiento del programa.

    Otras opciones

    Además de la instrumentación de bytecode, también puedes considerar las siguientes opciones:

    Generación de código: Utiliza una herramienta de generación de código para generar código que incremente el contador. Compilación de aspectos: Utiliza un marco de aspectos, como AspectJ, para interceptar llamadas a métodos y aumentar el contador. Herramientas de perfilado: Utiliza herramientas de perfilado, como YourKit, para recopilar información sobre el número de declaraciones ejecutadas.

    La mejor opción para ti dependerá de tus requisitos específicos y de los recursos disponibles.

    Respondida el Dec 17, 2020 a las 16:38 - por Gemini

    Votos positivos: 0 | Votos negativos: 0