diff --git a/src/coreclr/jit/async.cpp b/src/coreclr/jit/async.cpp index 74ea909ca86135..68684d62c28b61 100644 --- a/src/coreclr/jit/async.cpp +++ b/src/coreclr/jit/async.cpp @@ -797,6 +797,9 @@ PhaseStatus AsyncTransformation::Run() INDEBUG(m_compiler->mostRecentlyActivePhase = PHASE_ASYNC); VarSetOps::AssignNoCopy(m_compiler, m_compiler->compCurLife, VarSetOps::MakeEmpty(m_compiler)); + // The analyses below use BlockPredsWithEH. Clear its cache in case it was used before this. + m_compiler->m_blockToEHPreds = nullptr; + // Compute locals unchanged from their default values DefaultValueAnalysis defaultValues(m_compiler); defaultValues.Run(); diff --git a/src/coreclr/jit/asyncanalysis.cpp b/src/coreclr/jit/asyncanalysis.cpp index 59bc64d8bd63c5..b0e035996758d6 100644 --- a/src/coreclr/jit/asyncanalysis.cpp +++ b/src/coreclr/jit/asyncanalysis.cpp @@ -38,13 +38,9 @@ class MutationDataFlowCallback void MergeHandler(BasicBlock* block, BasicBlock* firstTryBlock, BasicBlock* lastTryBlock) { - // A handler can be reached from any point in the try region. - // A local is mutated at handler entry if it was mutated at try - // entry or mutated anywhere within the try region. - for (BasicBlock* tryBlock = firstTryBlock; tryBlock != lastTryBlock->Next(); tryBlock = tryBlock->Next()) + for (FlowEdge* pred = m_compiler->BlockPredsWithEH(block); pred != nullptr; pred = pred->getNextPredEdge()) { - VarSetOps::UnionD(m_compiler, m_mutatedVarsIn[block->bbNum], m_mutatedVarsIn[tryBlock->bbNum]); - VarSetOps::UnionD(m_compiler, m_mutatedVarsIn[block->bbNum], m_mutatedVars[tryBlock->bbNum]); + Merge(block, pred->getSourceBlock(), pred->getDupCount()); } } diff --git a/src/tests/async/regression/132015.cs b/src/tests/async/regression/132015.cs new file mode 100644 index 00000000000000..9c344da229a963 --- /dev/null +++ b/src/tests/async/regression/132015.cs @@ -0,0 +1,87 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Runtime.CompilerServices; +using System.Threading.Tasks; +using Xunit; + +public class Runtime_132015 +{ + [Fact] + public static void AssignInFilterIsVisibleAfterSuspension() + { + Assert.Equal("success", AssignInFilter().GetAwaiter().GetResult()); + } + + [Fact] + public static void AssignInNestedFinallyIsVisibleAfterSuspension() + { + Assert.Equal("success", AssignInNestedFinally().GetAwaiter().GetResult()); + Assert.Equal("success", AssignInNestedFinallyWithFilter().GetAwaiter().GetResult()); + } + + // The filter assigns the exception variable, which must survive the + // suspension inside the handler. + [MethodImpl(MethodImplOptions.NoInlining)] + private static async Task AssignInFilter() + { + try + { + throw new Exception("thrown"); + } + catch (Exception caught) when ((caught = new Exception("success")) != null) + { + await Task.Yield(); + return caught.Message; + } + } + + // The nested finally runs during the second pass, before the handler is + // entered, so its assignment must survive the suspension in the handler. + [MethodImpl(MethodImplOptions.NoInlining)] + private static async Task AssignInNestedFinally() + { + string s = null; + try + { + try + { + throw new Exception("thrown"); + } + finally + { + s = "success"; + } + } + catch (Exception) + { + await Task.Yield(); + return s ?? ""; + } + } + + // Same as above, but a filter runs before the nested finally, so the + // handler must not assume the value that the filter observed. + [MethodImpl(MethodImplOptions.NoInlining)] + private static async Task AssignInNestedFinallyWithFilter() + { + string s = null; + try + { + try + { + throw new Exception("thrown"); + } + finally + { + s = "success"; + } + } + catch (Exception) when (s is null) + { + await Task.Yield(); + return s ?? ""; + } + } +} diff --git a/src/tests/async/regression/132015.csproj b/src/tests/async/regression/132015.csproj new file mode 100644 index 00000000000000..197767e2c4e249 --- /dev/null +++ b/src/tests/async/regression/132015.csproj @@ -0,0 +1,5 @@ + + + + + diff --git a/src/tests/async/regression/132016.cs b/src/tests/async/regression/132016.cs new file mode 100644 index 00000000000000..030f9be14d28af --- /dev/null +++ b/src/tests/async/regression/132016.cs @@ -0,0 +1,72 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Threading.Tasks; +using Xunit; + +public class Runtime_132016 +{ + private static readonly List s_log = new List(); + + [Fact] + public static void TestEntryPoint() + { + s_log.Clear(); + new Runtime_132016().M().GetAwaiter().GetResult(); + + Assert.Equal(new[] { "outer", "outer", "inner", "inner", "outer", "inner" }, s_log); + } + + private static bool Filter(Exception ex, bool result) + { + s_log.Add(ex.Message); + return result; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private async Task M() + { + try + { + throw new Exception("outer"); + } + catch (Exception ex) when (Filter(ex, false)) + { + await Print(ex); + } + catch (Exception ex) when (Filter(ex, true)) + { + try + { + throw new Exception("inner"); + } + catch (Exception ex2) when (Filter(ex2, false)) + { + await Print(ex); + await Print(ex2); + } + catch (Exception ex2) when (Filter(ex2, true)) + { + try + { + throw new Exception(); + } + catch + { + await Print(ex); + await Print(ex2); + } + } + } + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static async Task Print(Exception ex) + { + await Task.Yield(); + s_log.Add(ex.Message); + } +} diff --git a/src/tests/async/regression/132016.csproj b/src/tests/async/regression/132016.csproj new file mode 100644 index 00000000000000..197767e2c4e249 --- /dev/null +++ b/src/tests/async/regression/132016.csproj @@ -0,0 +1,5 @@ + + + + + diff --git a/src/tests/async/regression/132017.cs b/src/tests/async/regression/132017.cs new file mode 100644 index 00000000000000..367fd48fd5f6f2 --- /dev/null +++ b/src/tests/async/regression/132017.cs @@ -0,0 +1,74 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Threading.Tasks; +using Xunit; + +public class Runtime_132017 +{ + private static readonly List s_log = new List(); + private static int s_calls; + + // The nested catch starts this task without awaiting it; keep it around so + // that the test can wait for it deterministically. + private static Task s_pending; + + [Fact] + public static void TestEntryPoint() + { + s_log.Clear(); + s_calls = 0; + s_pending = null; + + var test = new Runtime_132017(); + test.M(false).GetAwaiter().GetResult(); + test.M(true).GetAwaiter().GetResult(); + s_pending.GetAwaiter().GetResult(); + + Assert.Equal(new[] { "filter", "filter", "outer", "filter", "outer" }, s_log); + Assert.Equal(2, s_calls); + } + + private static bool Filter(bool result) + { + s_log.Add("filter"); + return result; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private async Task M(bool first) + { + try + { + throw new Exception("outer"); + } + catch (Exception ex) when (Filter(first)) + { + await Print(ex); + } + catch (Exception ex) when (Filter(true)) + { + try + { + await Task.Yield(); + throw new Exception(); + } + catch + { + s_pending = Print(ex); + } + } + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static async Task Print(Exception ex) + { + string message = ex.Message; + s_log.Add(message); + await Task.Yield(); + s_calls++; + } +} diff --git a/src/tests/async/regression/132017.csproj b/src/tests/async/regression/132017.csproj new file mode 100644 index 00000000000000..197767e2c4e249 --- /dev/null +++ b/src/tests/async/regression/132017.csproj @@ -0,0 +1,5 @@ + + + + +