Some Eclipse Foundation services are deprecated, or will be soon. Please ensure you've read this important communication.
Bug 422515 - [1.8][compiler] "Missing code implementation in the compiler" when lambda body accesses array variable
Summary: [1.8][compiler] "Missing code implementation in the compiler" when lambda bod...
Status: RESOLVED FIXED
Alias: None
Product: JDT
Classification: Eclipse Project
Component: Core (show other bugs)
Version: 4.4   Edit
Hardware: PC Windows 7
: P3 normal (vote)
Target Milestone: BETA J8   Edit
Assignee: Srikanth Sankaran CLA
QA Contact:
URL:
Whiteboard:
Keywords:
Depends on:
Blocks:
 
Reported: 2013-11-25 14:53 EST by Markus Keller CLA
Modified: 2013-11-28 11:26 EST (History)
2 users (show)

See Also:


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Markus Keller CLA 2013-11-25 14:53:47 EST
"Missing code implementation in the compiler" compile error when a lambda body accesses an array-typed variable:

package jsr335;

public class LambdaAccessArray {
    public static void main(String[] args) throws InterruptedException {
        final int[] result= { 0 };
        Thread t = new Thread(() -> {
            result[0]= 42;
        });
        t.start();
        t.join();
        System.out.println(result[0]);
    }
}

The error doesn't occur when the lambda body is just an expression like this:

        Thread t = new Thread(() -> result[0]= 42);

The problem is reported here: 

org.eclipse.jdt.internal.compiler.problem.ProblemReporter.needImplementation(ProblemReporter.java:6178)
	at org.eclipse.jdt.internal.compiler.codegen.CodeStream.generateOuterAccess(CodeStream.java:2286)
	at org.eclipse.jdt.internal.compiler.codegen.StackMapFrameCodeStream.generateOuterAccess(StackMapFrameCodeStream.java:364)
	at org.eclipse.jdt.internal.compiler.ast.SingleNameReference.generateCode(SingleNameReference.java:487)
	at org.eclipse.jdt.internal.compiler.ast.ArrayReference.generatePostIncrement(ArrayReference.java:166)
	at org.eclipse.jdt.internal.compiler.ast.PostfixExpression.generateCode(PostfixExpression.java:40)
	at org.eclipse.jdt.internal.compiler.ast.Expression.generateCode(Expression.java:695)
	at org.eclipse.jdt.internal.compiler.ast.Block.generateCode(Block.java:75)
	at org.eclipse.jdt.internal.compiler.ast.LambdaExpression.generateCode(LambdaExpression.java:751)
	at org.eclipse.jdt.internal.compiler.ast.LambdaExpression.generateCode(LambdaExpression.java:702)
	at org.eclipse.jdt.internal.compiler.ClassFile.addSpecialMethods(ClassFile.java:923)
	at org.eclipse.jdt.internal.compiler.ast.TypeDeclaration.generateCode(TypeDeclaration.java:573)
	at org.eclipse.jdt.internal.compiler.ast.TypeDeclaration.generateCode(TypeDeclaration.java:638)
	at org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration.generateCode(CompilationUnitDeclaration.java:369)
	at org.eclipse.jdt.internal.compiler.Compiler.process(Compiler.java:781)
	at org.eclipse.jdt.internal.compiler.ProcessTaskManager.run(ProcessTaskManager.java:137)
Comment 1 Srikanth Sankaran CLA 2013-11-26 10:31:11 EST
This is the side effect of running control/data flow analysis on the
lambda body without it first being run on the enclosing method(s). We create
a parallel universe as far as AST and FlowContext and FlowInfo abstractions
go, but for scopes and bindings - we reuse. This is bad. Basically, during
lambda's flow analysis, we find outer local array "result" as being
uninitialized and set a bit on the binding. This is because this bit is
required during code generation and flow info has evaporated at that point.

I'll investigate.
Comment 2 Srikanth Sankaran CLA 2013-11-26 12:27:25 EST
(In reply to Markus Keller from comment #0)
> "Missing code implementation in the compiler" compile error when a lambda
> body accesses an array-typed variable:

[...]

> The error doesn't occur when the lambda body is just an expression like this:
> 
>         Thread t = new Thread(() -> result[0]= 42);

For an expression bodied lambda we don't have to do control flow analysis.
We know its value compatible if the expression is not of void type and it
is void compatible if the expression can be standalone statement - so things
are simple there.
Comment 3 Srikanth Sankaran CLA 2013-11-26 13:36:20 EST
Fix and tests released here: http://git.eclipse.org/c/jdt/eclipse.jdt.core.git/commit/?h=BETA_JAVA8&id=9cd17ece7cc1ac607f4f1302fc1a6d286dff9da1

We basically start the (overload resolution trial) flow analysis for lambda
bodies against a perfect world: all outer locals are definitely assigned. It
is not pertinent if they are otherwise - the eventual analysis will catch
the errors.
Comment 4 Stephan Herrmann CLA 2013-11-28 08:07:17 EST
I see that flow analysis is performed with null annotation analysis disabled. For my education: what results of flow analysis are actually needed in this context?
Comment 5 Srikanth Sankaran CLA 2013-11-28 08:31:06 EST
(In reply to Stephan Herrmann from comment #4)

> I see that flow analysis is performed with null annotation analysis
> disabled. For my education: what results of flow analysis are actually
> needed in this context?

This is the trial evaluation on a clone and not the original lambda. Since
the null analysis switch is restored properly in the finally block, the 
eventual resolution would carry out null analysis if requested.

Basically, during resolve stage, we need information on whether a lambda 
expression is value compatible or value compatible or both or none. This 
cannot be determined by say an ASTVisitor that looks for return statements.

e.g: 

interface I {
	void foo (int x);
}
public class X {
	static void foo(I i) {}
	public static void main(String[] args) {
		boolean constantequalstotrue;
		constantequalstotrue = true;
		I i = (x) -> { if (x > 0) return x; };  // neither
		i =   (x) -> { throw new RuntimeException(); }; // both
		i =   (x) -> { while (constantequalstotrue); }; // both
		i =   (x) -> { System.out.println(x); }; // void
		i =   (x) -> { return x; };  // value
	}
} 

LE.isCompatibleWith is implementing 15.12.2.1 and actually a bit more than
that, we don't distinguish between potential compatibility and applicability
and merge them together into compatibility.

---

A lambda expression (15.27) is potentially compatible with a functional 
interface type (9.8) if all of the following are true:
The arity of the targeted type's function type is the same as the arity of the 
lambda expression.
If the targeted type's function type has a void return, then the lambda body is either a statement expression (14.8) or a void-compatible block (15.27.2).
If the targeted type's function type has a (non-void) return type, then the 
lambda body is either an expression or a valuecompatible block (15.27.2).

--

For part F, it used to be that the lambda had to be resolved + analyzed against
every target type candidate. draft 0.7 has done away with this requirement.
So we need at most two resolves + analyze irrespective of the number of
candidate methods: one to discover the shape and one ultimate resolve. 

My cursory reading of part G, tells me we need to resolve + analyze n+1 times
for inference - not sure, but we have the machinery in place.
Comment 6 Stephan Herrmann CLA 2013-11-28 11:26:21 EST
Thanks for explaining.
So, applicability check needs to know about UNREACHABLE, right? Makes sense.
Good we separated UNREACHABLE_OR_DEAD from UNREACHABLE_BY_NULLANALYSIS :)