Fix determining enclosing class for closure

Enclosing class for closure is a class whose instance is captured by
closure as an outer 'this', and stored in a field 'this$0'.
Usually enclosing class for closure is an immediate outer class,
including classes for nested closures. For example:

  class C {
    fun foo() {}
    val example1 = L1@ { foo() }
    // Enclosing class for lambda 'L1' is 'C'
    val example2 = L2a@ { L2b@ { foo() } }
    // Enclosing class for nested lambda 'L2b'
    // is a closure class for outer lambda 'L2a'
  }

However, if the closure is created in a super type constructor call for
the outer class, corresponding instance is considered "uninitialized",
and can't be used as a proper class instance, and can't be referenced:
corresponding code is rejected by front-end.

  class Outer {
    fun foo() {}
    inner class Inner : Base(L3@ { foo() })
    // Enclosing class for lambda 'L3' is 'Outer',
    // because 'Inner' is uninitialized in super type constructor call.
  }

In CodegenAnnotatingVisitor, we maintain a stack of currently
uninitialized classes, and chose enclosing class for closure
as an inner-most surrounding class with initialized instance.

When generating code for this or outer class instance, we skip
contexts corresponding to classes with uninitialized instances.

This fixes a number of bytecode verification errors caused by incorrect
enclosing class for closure.

 #KT-4174 Fixed Target versions 1.2.20
 #KT-13454 Fixed Target versions 1.2.20
 #KT-14148 Fixed Target versions 1.2.20
This commit is contained in:
Dmitry Petrov
2017-10-30 11:15:02 +03:00
parent 6648657e65
commit bbb0389c51
24 changed files with 730 additions and 10 deletions
@@ -2664,7 +2664,7 @@ public class ExpressionCodegen extends KtVisitor<StackValue, StackValue> impleme
result = cur.getOuterExpression(result, false);
}
cur = cur.getParentContext();
cur = cur.getEnclosingClassContext();
}
throw new UnsupportedOperationException();
@@ -81,6 +81,7 @@ class CodegenAnnotatingVisitor extends KtVisitorVoid {
private final Stack<ClassDescriptor> classStack = new Stack<>();
private final Stack<String> nameStack = new Stack<>();
private final Set<ClassDescriptor> uninitializedClasses = new HashSet<>();
private final BindingTrace bindingTrace;
private final BindingContext bindingContext;
@@ -394,7 +395,18 @@ class CodegenAnnotatingVisitor extends KtVisitorVoid {
@NotNull
private MutableClosure recordClosure(@NotNull ClassDescriptor classDescriptor, @NotNull String name) {
return CodegenBinding.recordClosure(bindingTrace, classDescriptor, peekFromStack(classStack), Type.getObjectType(name));
return CodegenBinding.recordClosure(bindingTrace, classDescriptor, getProperEnclosingClass(), Type.getObjectType(name));
}
@Nullable
private ClassDescriptor getProperEnclosingClass() {
for (int i = classStack.size() - 1; i >= 0; i--) {
ClassDescriptor fromStack = classStack.get(i);
if (!uninitializedClasses.contains(fromStack)) {
return fromStack;
}
}
return null;
}
private void recordLocalVariablePropertyMetadata(LocalVariableDescriptor variableDescriptor) {
@@ -627,10 +639,42 @@ class CodegenAnnotatingVisitor extends KtVisitorVoid {
@Override
public void visitSuperTypeCallEntry(@NotNull KtSuperTypeCallEntry call) {
super.visitSuperTypeCallEntry(call);
// Closures in super type constructor calls for anonymous objects are created in outer context
if (!isSuperTypeCallForAnonymousObject(call)) {
withinUninitializedClass(call, () -> super.visitSuperTypeCallEntry(call));
}
else {
super.visitSuperTypeCallEntry(call);
}
checkSamCall(call);
}
private static boolean isSuperTypeCallForAnonymousObject(@NotNull KtSuperTypeCallEntry call) {
PsiElement parent = call.getParent();
if (!(parent instanceof KtSuperTypeList)) return false;
parent = parent.getParent();
if (!(parent instanceof KtObjectDeclaration)) return false;
parent = parent.getParent();
if (!(parent instanceof KtObjectLiteralExpression)) return false;
return true;
}
@Override
public void visitConstructorDelegationCall(@NotNull KtConstructorDelegationCall call) {
withinUninitializedClass(call, () -> super.visitConstructorDelegationCall(call));
}
private void withinUninitializedClass(@NotNull KtElement element, @NotNull Runnable operation) {
ClassDescriptor currentClass = peekFromStack(classStack);
assert currentClass != null : element.getClass().getSimpleName() + " should be inside a class: " + element.getText();
assert !uninitializedClasses.contains(currentClass) : "Class entered twice: " + currentClass;
uninitializedClasses.add(currentClass);
operation.run();
boolean removed = uninitializedClasses.remove(currentClass);
assert removed : "Inconsistent uninitialized class stack: " + currentClass;
}
private void recordSamConstructorIfNeeded(@NotNull KtCallElement expression, @NotNull ResolvedCall<?> call) {
CallableDescriptor callableDescriptor = call.getResultingDescriptor();
if (!(callableDescriptor.getOriginal() instanceof SamConstructorDescriptor)) return;
@@ -215,13 +215,13 @@ public abstract class CodegenContext<T extends DeclarationDescriptor> {
private StackValue getOuterExpression(@Nullable StackValue prefix, boolean ignoreNoOuter, boolean captureThis) {
if (outerExpression.invoke() == null) {
if (!ignoreNoOuter) {
throw new UnsupportedOperationException("Don't know how to generate outer expression for " + getContextDescriptor());
throw new UnsupportedOperationException("Don't know how to generate outer expression: " + this);
}
return null;
}
if (captureThis) {
if (closure == null) {
throw new IllegalStateException("Can't capture this for context without closure: " + getContextDescriptor());
throw new IllegalStateException("Can't capture this for context without closure: " + this);
}
closure.setCaptureThis();
}
@@ -348,13 +348,34 @@ public abstract class CodegenContext<T extends DeclarationDescriptor> {
return parentContext;
}
public ClassDescriptor getEnclosingClass() {
@Nullable
public CodegenContext getEnclosingClassContext() {
CodegenContext cur = getParentContext();
while (cur != null && !(cur.getContextDescriptor() instanceof ClassDescriptor)) {
while (cur != null) {
if (cur instanceof ConstructorContext && !(((ConstructorContext) cur).isThisInitialized())) {
// If the current context is a constructor with uninitialized 'this',
// skip it and the corresponding class context
CodegenContext parent = cur.getParentContext();
assert parent != null : "Context " + cur + " should have a parent";
cur = parent;
}
else {
DeclarationDescriptor curDescriptor = cur.getContextDescriptor();
if (curDescriptor instanceof ClassDescriptor) {
return cur;
}
}
cur = cur.getParentContext();
}
return null;
}
return cur == null ? null : (ClassDescriptor) cur.getContextDescriptor();
@Nullable
public ClassDescriptor getEnclosingClass() {
// TODO store enclosing context class in the context itself
CodegenContext enclosingClassContext = getEnclosingClassContext();
if (enclosingClassContext == null) return null;
return (ClassDescriptor) enclosingClassContext.getContextDescriptor();
}
@Nullable
@@ -474,7 +495,8 @@ public abstract class CodegenContext<T extends DeclarationDescriptor> {
);
}
else {
throw new UnsupportedOperationException("Do not know how to create accessor for descriptor " + descriptor);
throw new UnsupportedOperationException("Do not know how to create accessor for descriptor " + descriptor +
" in context " + this);
}
accessors.put(key, accessor);
@@ -61,6 +61,6 @@ public class ConstructorContext extends MethodContext {
@Override
public String toString() {
return "Constructor: " + getContextDescriptor();
return "Constructor: " + (isThisInitialized() ? "" : "UNINITIALIZED ") + getContextDescriptor();
}
}