diff --git a/compiler/tests/org/jetbrains/kotlin/storage/StorageManagerTest.java b/compiler/tests/org/jetbrains/kotlin/storage/StorageManagerTest.java index 5ef7efb779d..290aa357b42 100644 --- a/compiler/tests/org/jetbrains/kotlin/storage/StorageManagerTest.java +++ b/compiler/tests/org/jetbrains/kotlin/storage/StorageManagerTest.java @@ -163,7 +163,8 @@ public class StorageManagerTest extends TestCase { fail(); } catch (AssertionError e) { - assertTrue(e.getMessage().startsWith("Recursion detected on input: !!!")); + String message = e.getMessage(); + assertTrue("Expected message starting with \"Recursion detected\", got: " + message, message.startsWith("Recursion detected on input: !!!")); } } @@ -213,7 +214,7 @@ public class StorageManagerTest extends TestCase { new C().rec.invoke(); fail(); } - catch (IllegalStateException e) { + catch (AssertionError e) { // OK } } @@ -232,7 +233,7 @@ public class StorageManagerTest extends TestCase { new C().rec.invoke(); fail(); } - catch (IllegalStateException e) { + catch (AssertionError e) { // OK } } @@ -284,7 +285,7 @@ public class StorageManagerTest extends TestCase { new C().rec.invoke(); fail(); } - catch (IllegalStateException e) { + catch (AssertionError e) { // OK } } diff --git a/core/util.runtime/src/org/jetbrains/kotlin/storage/LockBasedStorageManager.java b/core/util.runtime/src/org/jetbrains/kotlin/storage/LockBasedStorageManager.java index 5ae93d8ca57..14aad06748c 100644 --- a/core/util.runtime/src/org/jetbrains/kotlin/storage/LockBasedStorageManager.java +++ b/core/util.runtime/src/org/jetbrains/kotlin/storage/LockBasedStorageManager.java @@ -19,6 +19,7 @@ package org.jetbrains.kotlin.storage; import kotlin.Unit; import kotlin.jvm.functions.Function0; import kotlin.jvm.functions.Function1; +import kotlin.jvm.functions.Function2; import kotlin.text.StringsKt; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -56,7 +57,7 @@ public class LockBasedStorageManager implements StorageManager { public static final StorageManager NO_LOCKS = new LockBasedStorageManager("NO_LOCKS", ExceptionHandlingStrategy.THROW, EmptySimpleLock.INSTANCE) { @NotNull @Override - protected RecursionDetectedResult recursionDetectedDefault() { + protected RecursionDetectedResult recursionDetectedDefault(@NotNull String source, K input) { return RecursionDetectedResult.fallThrough(); } }; @@ -123,6 +124,15 @@ public class LockBasedStorageManager implements StorageManager { return createMemoizedFunction(compute, LockBasedStorageManager.createConcurrentHashMap()); } + @NotNull + @Override + public MemoizedFunctionToNotNull createMemoizedFunction( + @NotNull Function1 compute, + @NotNull Function2 onRecursiveCall + ) { + return createMemoizedFunction(compute, onRecursiveCall, LockBasedStorageManager.createConcurrentHashMap()); + } + @NotNull @Override public MemoizedFunctionToNotNull createMemoizedFunction( @@ -132,6 +142,22 @@ public class LockBasedStorageManager implements StorageManager { return new MapBasedMemoizedFunctionToNotNull(this, map, compute); } + @NotNull + @Override + public MemoizedFunctionToNotNull createMemoizedFunction( + @NotNull Function1 compute, + @NotNull final Function2 onRecursiveCall, + @NotNull ConcurrentMap map + ) { + return new MapBasedMemoizedFunctionToNotNull(this, map, compute) { + @NotNull + @Override + protected RecursionDetectedResult recursionDetected(K input, boolean firstTime) { + return RecursionDetectedResult.value(onRecursiveCall.invoke(input, firstTime)); + } + }; + } + @NotNull @Override public MemoizedFunctionToNullable createMemoizedFunctionWithNullableValues(@NotNull Function1 compute) { @@ -278,8 +304,15 @@ public class LockBasedStorageManager implements StorageManager { } @NotNull - protected RecursionDetectedResult recursionDetectedDefault() { - throw sanitizeStackTrace(new IllegalStateException("Recursive call in a lazy value under " + this)); + protected RecursionDetectedResult recursionDetectedDefault(@NotNull String source, K input) { + throw sanitizeStackTrace( + new AssertionError("Recursion detected " + source + + (input == null + ? "" + : "on input: " + input + ) + " under " + this + ) + ); } private static class RecursionDetectedResult { @@ -406,7 +439,7 @@ public class LockBasedStorageManager implements StorageManager { */ @NotNull protected RecursionDetectedResult recursionDetected(boolean firstTime) { - return storageManager.recursionDetectedDefault(); + return storageManager.recursionDetectedDefault("in a lazy value", null); } protected void postCompute(T value) { @@ -521,9 +554,22 @@ public class LockBasedStorageManager implements StorageManager { storageManager.lock.lock(); try { value = cache.get(input); + if (value == NotValue.COMPUTING) { - throw recursionDetected(input); + value = NotValue.RECURSION_WAS_DETECTED; + RecursionDetectedResult result = recursionDetected(input, /*firstTime = */ true); + if (!result.isFallThrough()) { + return result.getValue(); + } } + + if (value == NotValue.RECURSION_WAS_DETECTED) { + RecursionDetectedResult result = recursionDetected(input, /*firstTime = */ false); + if (!result.isFallThrough()) { + return result.getValue(); + } + } + if (value != null) return WrappedValues.unescapeExceptionOrNull(value); AssertionError error = null; @@ -567,10 +613,8 @@ public class LockBasedStorageManager implements StorageManager { } @NotNull - private AssertionError recursionDetected(K input) { - return sanitizeStackTrace( - new AssertionError("Recursion detected on input: " + input + " under " + storageManager) - ); + protected RecursionDetectedResult recursionDetected(K input, boolean firstTime) { + return storageManager.recursionDetectedDefault("", input); } @NotNull diff --git a/core/util.runtime/src/org/jetbrains/kotlin/storage/ObservableStorageManager.kt b/core/util.runtime/src/org/jetbrains/kotlin/storage/ObservableStorageManager.kt index 24cf4933923..141299df18d 100644 --- a/core/util.runtime/src/org/jetbrains/kotlin/storage/ObservableStorageManager.kt +++ b/core/util.runtime/src/org/jetbrains/kotlin/storage/ObservableStorageManager.kt @@ -26,6 +26,10 @@ abstract class ObservableStorageManager(private val delegate: StorageManager) : return delegate.createMemoizedFunction(compute.observable) } + override fun createMemoizedFunction(compute: (K) -> V, onRecursiveCall: (K, Boolean) -> V): MemoizedFunctionToNotNull { + return delegate.createMemoizedFunction(compute.observable, onRecursiveCall) + } + override fun createMemoizedFunctionWithNullableValues(compute: (K) -> V?): MemoizedFunctionToNullable { return delegate.createMemoizedFunctionWithNullableValues(compute.observable) } @@ -34,6 +38,10 @@ abstract class ObservableStorageManager(private val delegate: StorageManager) : return delegate.createMemoizedFunction(compute.observable, map) } + override fun createMemoizedFunction(compute: (K) -> V, onRecursiveCall: (K, Boolean) -> V, map: ConcurrentMap): MemoizedFunctionToNotNull { + return delegate.createMemoizedFunction(compute.observable, onRecursiveCall, map) + } + override fun createMemoizedFunctionWithNullableValues(compute: (K) -> V, map: ConcurrentMap): MemoizedFunctionToNullable { return delegate.createMemoizedFunctionWithNullableValues(compute.observable, map) } diff --git a/core/util.runtime/src/org/jetbrains/kotlin/storage/StorageManager.kt b/core/util.runtime/src/org/jetbrains/kotlin/storage/StorageManager.kt index 83e8f11d6eb..b517f0a5063 100644 --- a/core/util.runtime/src/org/jetbrains/kotlin/storage/StorageManager.kt +++ b/core/util.runtime/src/org/jetbrains/kotlin/storage/StorageManager.kt @@ -29,6 +29,8 @@ interface StorageManager { */ fun createMemoizedFunction(compute: (K) -> V): MemoizedFunctionToNotNull + fun createMemoizedFunction(compute: (K) -> V, onRecursiveCall: (K, Boolean) -> V): MemoizedFunctionToNotNull + fun createMemoizedFunctionWithNullableValues(compute: (K) -> V?): MemoizedFunctionToNullable fun createCacheWithNullableValues(): CacheWithNullableValues @@ -36,6 +38,8 @@ interface StorageManager { fun createMemoizedFunction(compute: (K) -> V, map: ConcurrentMap): MemoizedFunctionToNotNull + fun createMemoizedFunction(compute: (K) -> V, onRecursiveCall: (K, Boolean) -> V, map: ConcurrentMap): MemoizedFunctionToNotNull + fun createMemoizedFunctionWithNullableValues(compute: (K) -> V, map: ConcurrentMap): MemoizedFunctionToNullable fun createLazyValue(computable: () -> T): NotNullLazyValue