diff --git a/src/coreclr/jit/loopcloning.cpp b/src/coreclr/jit/loopcloning.cpp index 5f903c4beb3935..81026a69ab660c 100644 --- a/src/coreclr/jit/loopcloning.cpp +++ b/src/coreclr/jit/loopcloning.cpp @@ -1406,7 +1406,16 @@ bool Compiler::optDeriveLoopCloningConditions(FlowGraphNaturalLoop* loop, LoopCl else if (iterInfo->HasArrayLengthLimit) { assert(limitArrIndex != nullptr); - ident = LC_Ident::CreateArrAccess(LC_Array(LC_Array::Jagged, limitArrIndex, LC_Array::ArrLen)); + if (isIncreasingLoop) + { + // For increasing loops the limit value is used as the per-access bound, so it + // must be compared against the indexed array length. For decreasing loops the + // upper end of the IV's range is the init value (already set as `ident` in the + // init-conditions section above) -- overwriting it with the loop test's limit + // array length would let the fast clone access an unrelated array out of bounds + // when init > accessArr.Length (see https://github.com/dotnet/runtime/issues/129176). + ident = LC_Ident::CreateArrAccess(LC_Array(LC_Array::Jagged, limitArrIndex, LC_Array::ArrLen)); + } } else { diff --git a/src/tests/JIT/Regression/JitBlue/Runtime_129176/Runtime_129176.cs b/src/tests/JIT/Regression/JitBlue/Runtime_129176/Runtime_129176.cs new file mode 100644 index 00000000000000..b9bdafb41021f3 --- /dev/null +++ b/src/tests/JIT/Regression/JitBlue/Runtime_129176/Runtime_129176.cs @@ -0,0 +1,120 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Runtime_129176; + +using System; +using System.Runtime.CompilerServices; +using Xunit; + +public class Runtime_129176 +{ + [Fact] + public static int TestEntryPoint() + { + int ok = 0; + + byte[] limitArr = new byte[1]; + byte[] accessArr = new byte[5]; + + // A decreasing loop whose limit array is different from the indexed array + // must not have its bounds checks elided by loop cloning when init is + // beyond the indexed array's length: the IV would visit OOB indices. + // Using accessArr.Length as the bad init value exercises the failure + // exactly one element past the end. + try + { + DecreasingGT_VarInit(limitArr, accessArr, accessArr.Length); + } + catch (IndexOutOfRangeException) + { + ok++; + } + + try + { + DecreasingGE_VarInit(limitArr, accessArr, accessArr.Length); + } + catch (IndexOutOfRangeException) + { + ok++; + } + + // Same shape with a constant init -- exercises the HasConstInit decreasing + // path that also previously had its `ident` overwritten by the limit array + // length. + try + { + DecreasingGT_ConstInit(limitArr, accessArr); + } + catch (IndexOutOfRangeException) + { + ok++; + } + + // Safe patterns must still produce correct results. + byte[] a = new byte[10]; + for (int i = 0; i < a.Length; i++) a[i] = (byte)i; + + if (DecreasingSelfGE(a) == 45) ok++; + if (DecreasingSelfGT(a) == 45) ok++; + if (DecreasingFromOffset(a, 5) == 1 + 2 + 3 + 4 + 5) ok++; + + return ok == 6 ? 100 : 1; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static int DecreasingGT_VarInit(byte[] limitArr, byte[] accessArr, int startIdx) + { + int sum = 0; + for (int i = startIdx; i > limitArr.Length; i--) + sum += accessArr[i]; + return sum; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static int DecreasingGE_VarInit(byte[] limitArr, byte[] accessArr, int startIdx) + { + int sum = 0; + for (int i = startIdx; i >= limitArr.Length; i--) + sum += accessArr[i]; + return sum; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static int DecreasingGT_ConstInit(byte[] limitArr, byte[] accessArr) + { + // accessArr.Length is 5; const init 5 is one past the end, must throw. + int sum = 0; + for (int i = 5; i > limitArr.Length; i--) + sum += accessArr[i]; + return sum; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static int DecreasingSelfGE(byte[] a) + { + int sum = 0; + for (int i = a.Length - 1; i >= 0; i--) + sum += a[i]; + return sum; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static int DecreasingSelfGT(byte[] a) + { + int sum = 0; + for (int i = a.Length - 1; i > -1; i--) + sum += a[i]; + return sum; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static int DecreasingFromOffset(byte[] a, int start) + { + int sum = 0; + for (int i = start; i > 0; i--) + sum += a[i]; + return sum; + } +} diff --git a/src/tests/JIT/Regression/Regression_ro_2.csproj b/src/tests/JIT/Regression/Regression_ro_2.csproj index 82e13ec3df7e5c..d4d6bd2ba5d72e 100644 --- a/src/tests/JIT/Regression/Regression_ro_2.csproj +++ b/src/tests/JIT/Regression/Regression_ro_2.csproj @@ -101,6 +101,7 @@ +