JDK-8170887 : 18.2.1: Capture variables should not escape lambda bodies
  • Type: Bug
  • Component: specification
  • Sub-Component: language
  • Affected Version: 8,9
  • Priority: P4
  • Status: Open
  • Resolution: Unresolved
  • Submitted: 2016-12-08
  • Updated: 2017-05-30
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
Other
tbd_majorUnresolved
Related Reports
Blocks :  
Relates :  
Description
If a lambda expression's result type mentions a capture variable that was generated in the body, it is unsound for that capture variable to escape into inference bounds. This is because the soundness of capture depends on the corresponding expression being evaluated only once; lambda bodies can be evaluated multiple times.

An example which exploits this bug to achieve heap pollution:

import java.util.*;
import java.util.stream.Collectors;
import java.util.function.Function;

public class CaptureLambda {

   <S,T> List<T> map(List<S> orig, Function<S,T> f) {
       return orig.stream().map(f).collect(Collectors.toList());
   }

   <T> List<List<T>> swap(List<List<T>> lists) {
       List<T> l1 = lists.get(0);
       List<T> l2 = lists.get(1);
       T t1 = l1.remove(0);
       T t2 = l2.remove(0);
       l1.add(t2);
       l2.add(t1);
       return lists;
   }

   List<String> strings = new ArrayList<>();
   { strings.add("x"); }

   List<Integer> ints = new ArrayList<>();
   { ints.add(1); }

   List<?> pickList(boolean b) { return b ? strings : ints; }

   void test() {
       List<Boolean> l1 = Arrays.asList(true, false);
       List<? extends List<?>> l2 = swap(map(l1, b -> pickList(b)));
       System.out.printf("l2=%s, strings=%s, ints=%s%n", l2, strings, ints);
   }

   public static void main(String... args) {
       new CaptureLambda().test();
   }

}

Proposed solution is for upward projection (see JDK-8016196) to be applied to the result type before comparing to the target function type's return type. This will take some spec work, since the constraints produced by 18.2.1 are expression compatibility constraints, not type compatibility constraints.

Method references also need to be handled. In this case, a shortcut is to just avoid capturing the referenced method's return type in the first place.