diff --git a/docs/release-notes/.FSharp.Core/11.0.100.md b/docs/release-notes/.FSharp.Core/11.0.100.md index 9a89e373ae1..5ed1fcffee3 100644 --- a/docs/release-notes/.FSharp.Core/11.0.100.md +++ b/docs/release-notes/.FSharp.Core/11.0.100.md @@ -5,6 +5,7 @@ * Fix `Array.exists2` documentation examples to use equal-length arrays; the previous examples would throw `ArgumentException` at runtime instead of returning the documented `false`/`true` values. ([PR #19672](https://github.com/dotnet/fsharp/pull/19672)) * Move `Async.StartChild` to the "Starting Async Computations" docs category alongside `Async.StartChildAsTask`. ([Issue #19667](https://github.com/dotnet/fsharp/issues/19667)) * Add `InlineIfLambda` to `Array.init` ([PR #19869](https://github.com/dotnet/fsharp/pull/19869)) +* Fix array and string slices with extreme reversed bounds to return correctly shaped empty results. ([Issue #20530](https://github.com/dotnet/fsharp/issues/20530), [PR #20557](https://github.com/dotnet/fsharp/pull/20557)) * Fix printf handling of -0.0 (negative zero) values for float, float32, and decimal values ([Issue #15557](https://github.com/dotnet/fsharp/issues/15557) and [Issue #15558](https://github.com/dotnet/fsharp/issues/15558), [PR #18147](https://github.com/dotnet/fsharp/pull/18147)) diff --git a/src/FSharp.Core/prim-types.fs b/src/FSharp.Core/prim-types.fs index 11e3ff1cb6a..55ba452deb3 100644 --- a/src/FSharp.Core/prim-types.fs +++ b/src/FSharp.Core/prim-types.fs @@ -6274,9 +6274,27 @@ namespace Microsoft.FSharp.Core low, high + let inline ComputeSliceRange bound start finish length = + let low = + match start with + | Some n when n >= bound -> n + | _ -> bound + let count = + if length = 0 then + 0 + else + let upper = bound + (length - 1) + let high = + match finish with + | Some n when n < upper -> n + | _ -> upper + if high < low then 0 else high - low + 1 + + low, count + let inline GetArraySlice (source: _ array) start finish = - let start, finish = ComputeSlice 0 start finish source.Length - GetArraySub source start (finish - start + 1) + let start, len = ComputeSliceRange 0 start finish source.Length + GetArraySub source start len let inline SetArraySlice (target: _ array) start finish (source: _ array) = let start = (match start with None -> 0 | Some n -> n) @@ -6286,16 +6304,13 @@ namespace Microsoft.FSharp.Core let inline GetArraySlice2D (source: _[,]) start1 finish1 start2 finish2 = let bound1 = source.GetLowerBound(0) let bound2 = source.GetLowerBound(1) - let start1, finish1 = ComputeSlice bound1 start1 finish1 (GetArray2DLength1 source) - let start2, finish2 = ComputeSlice bound2 start2 finish2 (GetArray2DLength2 source) - let len1 = (finish1 - start1 + 1) - let len2 = (finish2 - start2 + 1) + let start1, len1 = ComputeSliceRange bound1 start1 finish1 (GetArray2DLength1 source) + let start2, len2 = ComputeSliceRange bound2 start2 finish2 (GetArray2DLength2 source) GetArray2DSub source start1 start2 len1 len2 let inline GetArraySlice2DFixed (source: _[,]) start finish index nonFixedDim = let bound = source.GetLowerBound(nonFixedDim) - let start, finish = ComputeSlice bound start finish (GetArray2DLength source nonFixedDim) - let len = (finish - start + 1) + let start, len = ComputeSliceRange bound start finish (GetArray2DLength source nonFixedDim) let dst = zeroCreate (if len < 0 then 0 else len) let getArrayElem = match nonFixedDim with @@ -6339,21 +6354,16 @@ namespace Microsoft.FSharp.Core let bound1 = source.GetLowerBound(0) let bound2 = source.GetLowerBound(1) let bound3 = source.GetLowerBound(2) - let start1, finish1 = ComputeSlice bound1 start1 finish1 (GetArray3DLength1 source) - let start2, finish2 = ComputeSlice bound2 start2 finish2 (GetArray3DLength2 source) - let start3, finish3 = ComputeSlice bound3 start3 finish3 (GetArray3DLength3 source) - let len1 = (finish1 - start1 + 1) - let len2 = (finish2 - start2 + 1) - let len3 = (finish3 - start3 + 1) + let start1, len1 = ComputeSliceRange bound1 start1 finish1 (GetArray3DLength1 source) + let start2, len2 = ComputeSliceRange bound2 start2 finish2 (GetArray3DLength2 source) + let start3, len3 = ComputeSliceRange bound3 start3 finish3 (GetArray3DLength3 source) GetArray3DSub source start1 start2 start3 len1 len2 len3 let inline GetArraySlice3DFixedSingle (source: _[,,]) start1 finish1 start2 finish2 index nonFixedDim1 nonFixedDim2 = let bound1 = source.GetLowerBound(nonFixedDim1) let bound2 = source.GetLowerBound(nonFixedDim2) - let start1, finish1 = ComputeSlice bound1 start1 finish1 (GetArray3DLength source nonFixedDim1) - let start2, finish2 = ComputeSlice bound2 start2 finish2 (GetArray3DLength source nonFixedDim2) - let len1 = (finish1 - start1 + 1) - let len2 = (finish2 - start2 + 1) + let start1, len1 = ComputeSliceRange bound1 start1 finish1 (GetArray3DLength source nonFixedDim1) + let start2, len2 = ComputeSliceRange bound2 start2 finish2 (GetArray3DLength source nonFixedDim2) let dst = Array2DZeroCreate (max 0 len1) (max 0 len2) let getArrayElem = @@ -6377,8 +6387,7 @@ namespace Microsoft.FSharp.Core let inline GetArraySlice3DFixedDouble (source: _[,,]) start finish index1 index2 nonFixedDim = let bound = source.GetLowerBound(nonFixedDim) - let start, finish = ComputeSlice bound start finish (GetArray3DLength source nonFixedDim) - let len = (finish - start + 1) + let start, len = ComputeSliceRange bound start finish (GetArray3DLength source nonFixedDim) let dst = zeroCreate (if len < 0 then 0 else len) let getArrayElem = match nonFixedDim with @@ -6465,26 +6474,19 @@ namespace Microsoft.FSharp.Core let bound2 = source.GetLowerBound(1) let bound3 = source.GetLowerBound(2) let bound4 = source.GetLowerBound(3) - let start1, finish1 = ComputeSlice bound1 start1 finish1 (GetArray4DLength1 source) - let start2, finish2 = ComputeSlice bound2 start2 finish2 (GetArray4DLength2 source) - let start3, finish3 = ComputeSlice bound3 start3 finish3 (GetArray4DLength3 source) - let start4, finish4 = ComputeSlice bound4 start4 finish4 (GetArray4DLength4 source) - let len1 = (finish1 - start1 + 1) - let len2 = (finish2 - start2 + 1) - let len3 = (finish3 - start3 + 1) - let len4 = (finish4 - start4 + 1) + let start1, len1 = ComputeSliceRange bound1 start1 finish1 (GetArray4DLength1 source) + let start2, len2 = ComputeSliceRange bound2 start2 finish2 (GetArray4DLength2 source) + let start3, len3 = ComputeSliceRange bound3 start3 finish3 (GetArray4DLength3 source) + let start4, len4 = ComputeSliceRange bound4 start4 finish4 (GetArray4DLength4 source) GetArray4DSub source start1 start2 start3 start4 len1 len2 len3 len4 let inline GetArraySlice4DFixedSingle (source: _[,,,]) start1 finish1 start2 finish2 start3 finish3 index nonFixedDim1 nonFixedDim2 nonFixedDim3 = let bound1 = source.GetLowerBound(nonFixedDim1) let bound2 = source.GetLowerBound(nonFixedDim2) let bound3 = source.GetLowerBound(nonFixedDim3) - let start1, finish1 = ComputeSlice bound1 start1 finish1 (GetArray4DLength source nonFixedDim1) - let start2, finish2 = ComputeSlice bound2 start2 finish2 (GetArray4DLength source nonFixedDim2) - let start3, finish3 = ComputeSlice bound3 start3 finish3 (GetArray4DLength source nonFixedDim3) - let len1 = (finish1 - start1 + 1) - let len2 = (finish2 - start2 + 1) - let len3 = (finish3 - start3 + 1) + let _, len1 = ComputeSliceRange bound1 start1 finish1 (GetArray4DLength source nonFixedDim1) + let _, len2 = ComputeSliceRange bound2 start2 finish2 (GetArray4DLength source nonFixedDim2) + let _, len3 = ComputeSliceRange bound3 start3 finish3 (GetArray4DLength source nonFixedDim3) let dst = Array3DZeroCreate (max len1 0) (max len2 0) (max len3 0) let getArrayElem = @@ -6516,10 +6518,8 @@ namespace Microsoft.FSharp.Core let inline GetArraySlice4DFixedDouble (source: _[,,,]) start1 finish1 start2 finish2 index1 index2 nonFixedDim1 nonFixedDim2 = let bound1 = source.GetLowerBound(nonFixedDim1) let bound2 = source.GetLowerBound(nonFixedDim2) - let start1, finish1 = ComputeSlice bound1 start1 finish1 (GetArray4DLength source nonFixedDim1) - let start2, finish2 = ComputeSlice bound2 start2 finish2 (GetArray4DLength source nonFixedDim2) - let len1 = (finish1 - start1 + 1) - let len2 = (finish2 - start2 + 1) + let _, len1 = ComputeSliceRange bound1 start1 finish1 (GetArray4DLength source nonFixedDim1) + let _, len2 = ComputeSliceRange bound2 start2 finish2 (GetArray4DLength source nonFixedDim2) let dst = Array2DZeroCreate (max len1 0) (max len2 0) let getArrayElem = @@ -6557,8 +6557,7 @@ namespace Microsoft.FSharp.Core let inline GetArraySlice4DFixedTriple (source: _[,,,]) start1 finish1 index1 index2 index3 nonFixedDim1 = let bound1 = source.GetLowerBound(nonFixedDim1) - let start1, finish1 = ComputeSlice bound1 start1 finish1 (GetArray4DLength source nonFixedDim1) - let len1 = (finish1 - start1 + 1) + let _, len1 = ComputeSliceRange bound1 start1 finish1 (GetArray4DLength source nonFixedDim1) let dst = zeroCreate (max len1 0) let getArrayElem = match nonFixedDim1 with @@ -6702,8 +6701,7 @@ namespace Microsoft.FSharp.Core SetArraySlice4DFixedTriple target source index1 index2 index3 start4 finish4 3 let inline GetStringSlice (source: string) start finish = - let start, finish = ComputeSlice 0 start finish source.Length - let len = finish-start+1 + let start, len = ComputeSliceRange 0 start finish source.Length if len <= 0 then String.Empty else source.Substring(start, len) diff --git a/tests/FSharp.Core.UnitTests/FSharp.Core/OperatorsModule1.fs b/tests/FSharp.Core.UnitTests/FSharp.Core/OperatorsModule1.fs index 0032fc36032..24c9817201f 100644 --- a/tests/FSharp.Core.UnitTests/FSharp.Core/OperatorsModule1.fs +++ b/tests/FSharp.Core.UnitTests/FSharp.Core/OperatorsModule1.fs @@ -93,6 +93,123 @@ type OperatorsModule1() = // null CheckThrowsNullRefException(fun () -> Operators.OperatorIntrinsics.GetStringSlice null param1 param2 |> ignore) + static member GetterSlicingOverflowCases() = + let hi, lo = Int32.MaxValue, Int32.MinValue + let a1 = [|1;2;3|] + let a2 = Array2D.zeroCreate 2 3 + let a3 = Array3D.zeroCreate 2 3 4 + let a4 = Array4D.zeroCreate 2 3 4 5 + let positive = Array2D.zeroCreateBased 3 5 2 3 + let negative = Array2D.zeroCreateBased -3 5 2 3 + let empty = Array.CreateInstance(typeof, [|0;2|], [|lo;0|]) :?> int[,] + let body name (args: obj[]) () = + let m = typeof.Assembly.GetType("Microsoft.FSharp.Core.Operators+OperatorIntrinsics").GetMethod(name) + Assert.NotNull m + m.MakeGenericMethod(typeof).Invoke(null, args) :?> Array + let cases: (string * int list * (unit -> Array)) list = + [ + "syntax 1D count two", [0], (fun () -> a1[hi..lo]) + "syntax 1D count three", [0], (fun () -> a1[hi..(lo + 1)]) + "syntax 2D axis 0", [0;3], (fun () -> a2[hi..lo, *]) + "syntax 2D axis 1", [2;0], (fun () -> a2[*, hi..lo]) + "syntax 3D axis 0", [0;3;4], (fun () -> a3[hi..lo, *, *]) + "syntax 3D axis 1", [2;0;4], (fun () -> a3[*, hi..lo, *]) + "syntax 3D axis 2", [2;3;0], (fun () -> a3[*, *, hi..lo]) + "syntax 4D axis 0", [0;3;4;5], (fun () -> a4[hi..lo, *, *, *]) + "syntax 4D axis 1", [2;0;4;5], (fun () -> a4[*, hi..lo, *, *]) + "syntax 4D axis 2", [2;3;0;5], (fun () -> a4[*, *, hi..lo, *]) + "syntax 4D axis 3", [2;3;4;0], (fun () -> a4[*, *, *, hi..lo]) + "syntax 2D fixed", [0], (fun () -> a2[0, hi..lo]) + "syntax 3D fixed single", [0;4], (fun () -> a3[0, hi..lo, *]) + "syntax 3D fixed double", [0], (fun () -> a3[0, 0, hi..lo]) + "syntax 4D fixed single", [0;4;5], (fun () -> a4[0, hi..lo, *, *]) + "syntax 4D fixed double", [0;5], (fun () -> a4[0, 0, hi..lo, *]) + "syntax 4D fixed triple", [0], (fun () -> a4[0, 0, 0, hi..lo]) + "syntax 2D fixed loop", [0], (fun () -> a2[0, 1..lo]) + "syntax 3D fixed loop", [0;4], (fun () -> a3[0, 1..lo, *]) + "syntax 4D fixed loop", [0;4;5], (fun () -> a4[0, 1..lo, *, *]) + "positive based", [0;3], (fun () -> positive[hi..lo, *]) + "negative based", [0;3], (fun () -> negative[hi..lo, *]) +#if NETCOREAPP + "inclusive endpoint", [0;2], (fun () -> + let endpoint = Array.CreateInstance(typeof, [|1;2|], [|hi;0|]) :?> int[,] + endpoint[..lo, *]) +#endif + "empty based explicit finish", [0;2], (fun () -> empty[hi..lo, *]) + "empty based omitted finish", [0;2], (fun () -> empty[hi.., *]) + "body 1D count two", [0], body "GetArraySlice" [|a1; Some hi; Some lo|] + "body 1D count three", [0], body "GetArraySlice" [|a1; Some hi; Some(lo + 1)|] + "body 2D", [0;3], body "GetArraySlice2D" [|a2; Some hi; Some lo; None; None|] + "body 3D", [0;3;4], body "GetArraySlice3D" [|a3; Some hi; Some lo; None; None; None; None|] + "body 4D", [0;3;4;5], body "GetArraySlice4D" [|a4; Some hi; Some lo; None; None; None; None; None; None|] + "body 2D fixed", [0], body "GetArraySlice2DFixed1" [|a2; 0; Some hi; Some lo|] + "body 3D fixed single", [0;4], body "GetArraySlice3DFixedSingle1" [|a3; 0; Some hi; Some lo; None; None|] + "body 3D fixed double", [0], body "GetArraySlice3DFixedDouble1" [|a3; 0; 0; Some hi; Some lo|] + "body 4D fixed single", [0;4;5], body "GetArraySlice4DFixedSingle1" [|a4; 0; Some hi; Some lo; None; None; None; None|] + "body 4D fixed double", [0;5], body "GetArraySlice4DFixedDouble1" [|a4; 0; 0; Some hi; Some lo; None; None|] + "body 4D fixed triple", [0], body "GetArraySlice4DFixedTriple4" [|a4; 0; 0; 0; Some hi; Some lo|] + "body 2D fixed loop", [0], body "GetArraySlice2DFixed1" [|a2; 0; Some 1; Some lo|] + "body 3D fixed loop", [0;4], body "GetArraySlice3DFixedSingle1" [|a3; 0; Some 1; Some lo; None; None|] + "body 4D fixed loop", [0;4;5], body "GetArraySlice4DFixedSingle1" [|a4; 0; Some 1; Some lo; None; None; None; None|] + ] + cases |> Seq.map (fun (name, shape, slice) -> [|box name; box shape; box slice|]) + + static member private CheckSliceShape(expected: int list, actual: Array) = + Assert.AreEqual(expected.Length, actual.Rank) + expected |> List.iteri (fun d length -> + Assert.AreEqual(length, actual.GetLength(d)) + Assert.AreEqual(0, actual.GetLowerBound(d))) + + [] + member _.GetterSlicingOverflowShape(_name: string, expected: int list, slice: unit -> Array) = + OperatorsModule1.CheckSliceShape(expected, slice()) + + [] + [] + [] + [] + [] + member _.GetterSlicingOverflowString(start: int, callableBody: bool) = + let actual = + if callableBody then + let m = typeof.Assembly.GetType("Microsoft.FSharp.Core.Operators+OperatorIntrinsics").GetMethod("GetStringSlice") + Assert.NotNull m + m.Invoke(null, [|"hello"; Some start; Some Int32.MinValue|]) :?> string + else + "hello"[start..Int32.MinValue] + Assert.AreEqual(String.Empty, actual) + +#if NETCOREAPP + [] + member _.GetterSlicingOverflowFinishBeforeUpperEndpoint() = + let start = Int32.MaxValue - 1 + let source = Array.CreateInstance(typeof, [|2;2|], [|start;0|]) :?> int[,] + source[start, 0] <- 42 + source[start, 1] <- 43 + let actual = source[start..start, *] + OperatorsModule1.CheckSliceShape([1;2], actual) + Assert.AreEqual(42, actual[0, 0]) + Assert.AreEqual(43, actual[0, 1]) +#endif + + [] + member _.GetterSlicingControls() = + let hi, lo = Int32.MaxValue, Int32.MinValue + Assert.AreEqual([], [1..5][3..lo]) + OperatorsModule1.CheckSliceShape([0], [|1;2;3|][3..(lo + 10)]) + let source = Array2D.initBased -3 5 2 3 (fun i j -> 100 * i + j) + let actual = source[-3..-2, *] + OperatorsModule1.CheckSliceShape([2;3], actual) + for i in 0..1 do + for j in 0..2 do + Assert.AreEqual(source[i - 3, j + 5], actual[i, j]) + let a2 = Array2D.zeroCreate 2 3 + OperatorsModule1.CheckSliceShape([0], a2[2, 1..0]) + CheckThrowsIndexOutRangException(fun () -> a2[2, 0..0] |> ignore) + CheckThrowsNullRefException(fun () -> Operators.OperatorIntrinsics.GetArraySlice (null: int[]) (Some hi) (Some lo) |> ignore) + CheckThrowsNullRefException(fun () -> Operators.OperatorIntrinsics.GetArraySlice2DFixed1 (null: int[,]) 0 (Some hi) (Some lo) |> ignore) + CheckThrowsNullRefException(fun () -> Operators.OperatorIntrinsics.GetStringSlice null (Some hi) (Some lo) |> ignore) + [] member _.OptimizedRangesSetArraySlice() = let param1 = Some(1)