Uploaded image for project: 'Clover'
  1. Clover
  2. CLOV-1465

Instrumentation of expression-like lambdas with no compilation errors in all cases

    • Our product teams collect and evaluate feedback from a number of different sources. To learn more about how we use customer feedback in the planning process, check out our new feature policy.

      Problem and workaround:

      https://confluence.atlassian.com/display/CLOVERKB/Java+8+code+instrumented+by+Clover+fails+to+compile

      Expected solution:

      Instrumentation of lambda functions shall always work and shall not generate any compilation errors, no matter how generic types/methods are declared.

      Possible ways to achieve this:

      1) Implement some heuristics, by parsing entire source file as well as referring to information already present in Clover's database, trying to get more information about types used.

      This will not work in 100% of cases, but may reduce number of compilation errors.

      2) Implement byte code instrumentation.

      This could be a Clover's post-processing phase. Could be handled by Service Provider Interface (i.e. be more flexible solution).

      3) Inline instrumentation code of expression-like lambda instead of using lambdaInc()

            [CLOV-1465] Instrumentation of expression-like lambdas with no compilation errors in all cases

            I'll be working on CLOV-1596 (solving problems with compilation of JDK8 Streams), which I hope to deliver in the nearest Clover 4.0.4. Although the fix won't solve all compilation issues, it should solve the majority of cases (as usage of streams is very common).

            Marek Parfianowicz added a comment - I'll be working on CLOV-1596 (solving problems with compilation of JDK8 Streams), which I hope to deliver in the nearest Clover 4.0.4. Although the fix won't solve all compilation issues, it should solve the majority of cases (as usage of streams is very common).

            It appears that the workaround entails a lot of code changes on our end. It will be helpful if you guys fix the issue.

            Nilanjan Sengupta added a comment - It appears that the workaround entails a lot of code changes on our end. It will be helpful if you guys fix the issue.

            Marek Parfianowicz added a comment - - edited

            Would it possible to add a hack based on 1) that would probably world work 90 % of the time?

            I will consider whether to follow approach #1 or #2 when/if this issue will be put on the road map.

            I.e. no quick, dirty hack in the meantime ...

            Marek Parfianowicz added a comment - - edited Would it possible to add a hack based on 1) that would probably world work 90 % of the time? I will consider whether to follow approach #1 or #2 when/if this issue will be put on the road map. I.e. no quick, dirty hack in the meantime ...

            I can fully understand that solution 2) is a major task.

            Would it possible to add a hack based on 1) that would probably world work 90 % of the time?

            And simplify it by only supporting java.util.function.
            As well as no support of overloading lambda methods. It is generally already a bad idea to use overloading with
            lambdas (http://mail.openjdk.java.net/pipermail/lambda-spec-experts/2013-August/000340.html).

            Deleted Account (Inactive) added a comment - I can fully understand that solution 2) is a major task. Would it possible to add a hack based on 1) that would probably world work 90 % of the time? And simplify it by only supporting java.util.function. As well as no support of overloading lambda methods. It is generally already a bad idea to use overloading with lambdas ( http://mail.openjdk.java.net/pipermail/lambda-spec-experts/2013-August/000340.html ).

            Note: Honestly speaking, I don't see a possibility to put this issue on Clover's road map for 2014.

            Marek Parfianowicz added a comment - Note: Honestly speaking, I don't see a possibility to put this issue on Clover's road map for 2014.

            I personally opt for solution #2 - byte code instrumentation. Complexity:

            • create Service Provider Interface for post-processing phase - 13 SP
            • implement lambdaInc() equivalent in byte code using ASM5 library - 13 SP
            • create byte code instrumentation service - 5 SP
            • attach byte code instr service to all possible integrations - Ant, Maven, Grails, Eclipse, IDEA - 20 SP
            • solve problem of non-continuous indexes for a source file - 13 SP

            Marek Parfianowicz added a comment - I personally opt for solution #2 - byte code instrumentation. Complexity: create Service Provider Interface for post-processing phase - 13 SP implement lambdaInc() equivalent in byte code using ASM5 library - 13 SP create byte code instrumentation service - 5 SP attach byte code instr service to all possible integrations - Ant, Maven, Grails, Eclipse, IDEA - 20 SP solve problem of non-continuous indexes for a source file - 13 SP

            Marek Parfianowicz added a comment - - edited

            ad 3) Inline instrumentation code

            This would generate lot's of uncompilable code and it would require extra comments put by a developer. MUCH MORE. This is why lambdaInc() was invented.

            Instead of calling lambdaInc() we could just use Recorder.inc() inline, i.e. transform expression-like lambda into a block lambda.

            Obvious problem - return statements, which are implicit. Javac compiler knows about them, Clover not. So we'd need an extra keyword like "/* CLOVER:RETURN */"

            Example:

            // the following:
            foo(() -> 2 + 3)
            // would need adding a CLOVER:RETURN keyword by developer:
            foo(() -> /* CLOVER:RETURN */ 2 + 3)
            // so that it could be instrumented as:
            foo(() -> { Recorder.inc(123); return 2 + 3; })
            
            // the following log.debug() returns void so we don't have a return keyword:
            foo(() -> log.debug("Hello"))
            // so that Clover would instrument it as:
            foo(() -> { Recorder.inc(123); log.debug("Hello"); })
            

            Marek Parfianowicz added a comment - - edited ad 3) Inline instrumentation code This would generate lot's of uncompilable code and it would require extra comments put by a developer. MUCH MORE. This is why lambdaInc() was invented. Instead of calling lambdaInc() we could just use Recorder.inc() inline, i.e. transform expression-like lambda into a block lambda. Obvious problem - return statements, which are implicit. Javac compiler knows about them, Clover not. So we'd need an extra keyword like "/* CLOVER:RETURN */" Example: // the following: foo(() -> 2 + 3) // would need adding a CLOVER:RETURN keyword by developer: foo(() -> /* CLOVER:RETURN */ 2 + 3) // so that it could be instrumented as: foo(() -> { Recorder.inc(123); return 2 + 3; }) // the following log.debug() returns void so we don't have a return keyword: foo(() -> log.debug( "Hello" )) // so that Clover would instrument it as: foo(() -> { Recorder.inc(123); log.debug( "Hello" ); })

            ad 2) Bytecode instrumentation

            Prerequisite: CLOV-171

            We'd also need some Service Provider Interface, so that a bytecode instrumentation could be done as a post-processing phase (after a source code instrumentation). Why SPI? It would be more flexible and it would allow to attach other tools in future as well.

            In the bytecode instrumentation we've got a complete code and there's no need to call lambdaInc() method. Just insert an addition call of recorder.inc() plus register an index in a Clover's database. Problem: index of such recorder.inc() would be outside index range registered for a file (i.e. non contiguous range) -> needs an update how BitSets are manipulated for per-test coverage.

            Marek Parfianowicz added a comment - ad 2) Bytecode instrumentation Prerequisite: CLOV-171 We'd also need some Service Provider Interface, so that a bytecode instrumentation could be done as a post-processing phase (after a source code instrumentation). Why SPI? It would be more flexible and it would allow to attach other tools in future as well. In the bytecode instrumentation we've got a complete code and there's no need to call lambdaInc() method. Just insert an addition call of recorder.inc() plus register an index in a Clover's database. Problem: index of such recorder.inc() would be outside index range registered for a file (i.e. non contiguous range) -> needs an update how BitSets are manipulated for per-test coverage.

            ad 1) Heuristics.

            When Clover tries to wrap a lambda function which is passed as an argument of some generic method, which is defined in the same source file, then try to find argument type of the method and copy it to the lambdaInc() wrapper in order to make a class cast so that type inference is solved

            ... lookup all methods having the same name ...
            ... out of them try to find those which "seem" to have functional interfaces ...
            ... which means that name of types shall be either defined in the same source file or already registered in Clover's database (i.e. compiled before) or well known (such as java.function.*) ...
            ... out of them try to guess, which functional interface developer had in mind (there may be more than one positive match)
            ... etc

            Marek Parfianowicz added a comment - ad 1) Heuristics. When Clover tries to wrap a lambda function which is passed as an argument of some generic method, which is defined in the same source file, then try to find argument type of the method and copy it to the lambdaInc() wrapper in order to make a class cast so that type inference is solved ... lookup all methods having the same name ... ... out of them try to find those which "seem" to have functional interfaces ... ... which means that name of types shall be either defined in the same source file or already registered in Clover's database (i.e. compiled before) or well known (such as java.function.*) ... ... out of them try to guess, which functional interface developer had in mind (there may be more than one positive match) ... etc

            Related issues:

            Marek Parfianowicz added a comment - Related issues: CLOV-1464 CLOV-1463 CLOV-1399

              Unassigned Unassigned
              mparfianowicz Marek Parfianowicz
              Votes:
              12 Vote for this issue
              Watchers:
              7 Start watching this issue

                Created:
                Updated:
                Resolved: