[FIR] FirExpressionEvaluator: do not modify `evaluatedInitializer`

This modification violates the contract that the transformer
can modify only declarations which it owns.
As declaration attributes are not thread-safe `evaluatedInitializer`
modification leads to exceptions in the concurrent environment
(the Analysis API mode)

^KT-67707 Fixed
diff --git a/compiler/fir/providers/src/org/jetbrains/kotlin/fir/expressions/FirExpressionEvaluator.kt b/compiler/fir/providers/src/org/jetbrains/kotlin/fir/expressions/FirExpressionEvaluator.kt
index 446467e..f1b625c 100644
--- a/compiler/fir/providers/src/org/jetbrains/kotlin/fir/expressions/FirExpressionEvaluator.kt
+++ b/compiler/fir/providers/src/org/jetbrains/kotlin/fir/expressions/FirExpressionEvaluator.kt
@@ -11,7 +11,6 @@
 import org.jetbrains.kotlin.fir.*
 import org.jetbrains.kotlin.fir.FirEvaluatorResult.*
 import org.jetbrains.kotlin.fir.declarations.FirProperty
-import org.jetbrains.kotlin.fir.declarations.utils.evaluatedInitializer
 import org.jetbrains.kotlin.fir.declarations.utils.isConst
 import org.jetbrains.kotlin.fir.expressions.builder.*
 import org.jetbrains.kotlin.fir.expressions.impl.FirResolvedArgumentList
@@ -39,6 +38,32 @@
 annotation class PrivateConstantEvaluatorAPI
 
 object FirExpressionEvaluator {
+    /**
+     * This property cannot be converted into a non-thread-local as we cannot control the entire stack of the call.
+     * For instance, we may jump through a Java class, so there is no other way to restore the previous stack.
+     * [evaluatedInitializer][org.jetbrains.kotlin.fir.declarations.utils.evaluatedInitializer]
+     * cannot be used for this purpose as the
+     * [CONSTANT_EVALUATION][org.jetbrains.kotlin.fir.declarations.FirResolvePhase.CONSTANT_EVALUATION]
+     * is non-jumping phase, so it cannot modify unrelated declarations.
+     *
+     * Code example:
+     * ```
+     * // FILE: KotlinClass.kt
+     * class KotlinClass {
+     *   companion object {
+     *     const val foo: Int = JavaClass.javaField + 1
+     *     const val bar: Int = foo + 1
+     *   }
+     * }
+     *
+     * // FILE: JavaClass.java
+     * public class JavaClass {
+     *     public static int javaField = KotlinClass.bar + 1;
+     * }
+     * ```
+     */
+    private val visitedCallables: ThreadLocal<HashSet<FirCallableSymbol<*>>> = ThreadLocal.withInitial(::hashSetOf)
+
     fun evaluatePropertyInitializer(property: FirProperty, session: FirSession): FirEvaluatorResult? {
         if (!property.isConst) {
             return null
@@ -84,22 +109,21 @@
         return visitor.evaluate(this)
     }
 
-    private fun <T> FirCallableSymbol<*>.visit(block: () -> T): T {
-        val firProperty = this.fir as? FirProperty ?: return block()
-
-        val oldEvaluatedResult = firProperty.evaluatedInitializer
-        firProperty.evaluatedInitializer = DuringEvaluation
-        try {
-            return block()
+    private inline fun <T> FirCallableSymbol<*>.visit(block: () -> T): T {
+        val visited = visitedCallables.get()
+        visited.add(this)
+        return try {
+            block()
         } finally {
-            firProperty.evaluatedInitializer = oldEvaluatedResult
+            visited.remove(this)
+            if (visited.isEmpty()) {
+                // to avoid keeping large empty collections in memory
+                visitedCallables.remove()
+            }
         }
     }
 
-    private fun FirCallableSymbol<*>.wasVisited(): Boolean {
-        val firProperty = this.fir as? FirProperty ?: return false
-        return firProperty.evaluatedInitializer == DuringEvaluation
-    }
+    private fun FirCallableSymbol<*>.wasVisited(): Boolean = this in visitedCallables.get()
 
     private class EvaluationVisitor(val session: FirSession) : FirVisitor<FirEvaluatorResult, Nothing?>() {
         fun evaluate(expression: FirExpression?): FirEvaluatorResult {