From 81597c163f1c27e4049b738cc093c4eea79f41b9 Mon Sep 17 00:00:00 2001 From: David Fowler Date: Sun, 31 May 2020 01:36:19 -0700 Subject: [PATCH 1/2] Change JumpTable to take ReadOnlySpan - Small change while trying to get a better handle on the part of the code base. Today we pass a string and 2 pointers and instead we can pass a ReadOnlySpan. - Updates tests and IL generator to handle the new signature. --- .../JumpTableMultipleEntryBenchmark.cs | 10 ++--- .../Matching/JumpTableSingleEntryBenchmark.cs | 10 ++--- .../Matching/JumpTableZeroEntryBenchmark.cs | 5 ++- src/Http/Routing/src/Matching/DfaMatcher.cs | 3 +- .../Routing/src/Matching/DfaMatcherBuilder.cs | 33 +------------- .../src/Matching/DictionaryJumpTable.cs | 8 ++-- .../Routing/src/Matching/ILEmitTrieFactory.cs | 44 +++++++++---------- .../src/Matching/ILEmitTrieJumpTable.cs | 20 ++++----- src/Http/Routing/src/Matching/JumpTable.cs | 7 ++- .../src/Matching/LinearSearchJumpTable.cs | 15 ++----- .../src/Matching/SingleEntryAsciiJumpTable.cs | 8 ++-- .../src/Matching/SingleEntryJumpTable.cs | 15 ++----- .../src/Matching/ZeroEntryJumpTable.cs | 8 ++-- .../Matching/ILEmitTrieJumpTableTest.cs | 9 ++-- .../Matching/MultipleEntryJumpTableTest.cs | 13 +++--- .../Matching/SingleEntryJumpTableTestBase.cs | 11 ++--- .../Matching/ZeroEntryJumpTableTest.cs | 7 +-- 17 files changed, 96 insertions(+), 130 deletions(-) diff --git a/src/Http/Routing/perf/Matching/JumpTableMultipleEntryBenchmark.cs b/src/Http/Routing/perf/Matching/JumpTableMultipleEntryBenchmark.cs index b3a685502592..5ddd95ce9be1 100644 --- a/src/Http/Routing/perf/Matching/JumpTableMultipleEntryBenchmark.cs +++ b/src/Http/Routing/perf/Matching/JumpTableMultipleEntryBenchmark.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; @@ -96,7 +96,7 @@ public int LinearSearch() var destination = 0; for (var i = 0; i < strings.Length; i++) { - destination = _linearSearch.GetDestination(strings[i], segments[i]); + destination = _linearSearch.GetDestination(strings[i].AsSpan(segments[i].Start, segments[i].Length)); } return destination; @@ -111,7 +111,7 @@ public int Dictionary() var destination = 0; for (var i = 0; i < strings.Length; i++) { - destination = _dictionary.GetDestination(strings[i], segments[i]); + destination = _dictionary.GetDestination(strings[i].AsSpan(segments[i].Start, segments[i].Length)); } return destination; @@ -126,7 +126,7 @@ public int Trie() var destination = 0; for (var i = 0; i < strings.Length; i++) { - destination = _trie.GetDestination(strings[i], segments[i]); + destination = _trie.GetDestination(strings[i].AsSpan(segments[i].Start, segments[i].Length)); } return destination; @@ -141,7 +141,7 @@ public int VectorTrie() var destination = 0; for (var i = 0; i < strings.Length; i++) { - destination = _vectorTrie.GetDestination(strings[i], segments[i]); + destination = _vectorTrie.GetDestination(strings[i].AsSpan(segments[i].Start, segments[i].Length)); } return destination; diff --git a/src/Http/Routing/perf/Matching/JumpTableSingleEntryBenchmark.cs b/src/Http/Routing/perf/Matching/JumpTableSingleEntryBenchmark.cs index 99f774620cb0..7e74c2c397f7 100644 --- a/src/Http/Routing/perf/Matching/JumpTableSingleEntryBenchmark.cs +++ b/src/Http/Routing/perf/Matching/JumpTableSingleEntryBenchmark.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; @@ -88,7 +88,7 @@ public int Default() var destination = 0; for (var i = 0; i < strings.Length; i++) { - destination = _default.GetDestination(strings[i], segments[i]); + destination = _default.GetDestination(strings[i].AsSpan(segments[i].Start, segments[i].Length)); } return destination; @@ -103,7 +103,7 @@ public int Ascii() var destination = 0; for (var i = 0; i < strings.Length; i++) { - destination = _ascii.GetDestination(strings[i], segments[i]); + destination = _ascii.GetDestination(strings[i].AsSpan(segments[i].Start, segments[i].Length)); } return destination; @@ -118,7 +118,7 @@ public int Trie() var destination = 0; for (var i = 0; i < strings.Length; i++) { - destination = _trie.GetDestination(strings[i], segments[i]); + destination = _trie.GetDestination(strings[i].AsSpan(segments[i].Start, segments[i].Length)); } return destination; @@ -133,7 +133,7 @@ public int VectorTrie() var destination = 0; for (var i = 0; i < strings.Length; i++) { - destination = _vectorTrie.GetDestination(strings[i], segments[i]); + destination = _vectorTrie.GetDestination(strings[i].AsSpan(segments[i].Start, segments[i].Length)); } return destination; diff --git a/src/Http/Routing/perf/Matching/JumpTableZeroEntryBenchmark.cs b/src/Http/Routing/perf/Matching/JumpTableZeroEntryBenchmark.cs index 337011de7294..ed7eb373b4f6 100644 --- a/src/Http/Routing/perf/Matching/JumpTableZeroEntryBenchmark.cs +++ b/src/Http/Routing/perf/Matching/JumpTableZeroEntryBenchmark.cs @@ -1,6 +1,7 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; using BenchmarkDotNet.Attributes; namespace Microsoft.AspNetCore.Routing.Matching @@ -57,7 +58,7 @@ public int Implementation() var destination = 0; for (var i = 0; i < strings.Length; i++) { - destination = _table.GetDestination(strings[i], segments[i]); + destination = _table.GetDestination(strings[i].AsSpan(segments[i].Start, segments[i].Length)); } return destination; diff --git a/src/Http/Routing/src/Matching/DfaMatcher.cs b/src/Http/Routing/src/Matching/DfaMatcher.cs index d79f13e2abac..58e7ef26ea08 100644 --- a/src/Http/Routing/src/Matching/DfaMatcher.cs +++ b/src/Http/Routing/src/Matching/DfaMatcher.cs @@ -206,7 +206,8 @@ public sealed override Task MatchAsync(HttpContext httpContext) var destination = 0; for (var i = 0; i < segments.Length; i++) { - destination = states[destination].PathTransitions.GetDestination(path, segments[i]); + var segment = segments[i]; + destination = states[destination].PathTransitions.GetDestination(path.AsSpan(segment.Start, segment.Length)); } // Process an arbitrary number of policy-based decisions diff --git a/src/Http/Routing/src/Matching/DfaMatcherBuilder.cs b/src/Http/Routing/src/Matching/DfaMatcherBuilder.cs index 628adf4b74ba..6dfe4e8d3174 100644 --- a/src/Http/Routing/src/Matching/DfaMatcherBuilder.cs +++ b/src/Http/Routing/src/Matching/DfaMatcherBuilder.cs @@ -353,7 +353,7 @@ private static int GetPrecedenceDigitAtDepth(RouteEndpoint endpoint, int depth) if (segment is null) { // Treat "no segment" as high priority. it won't effect the algorithm, but we need to define a sort-order. - return 0; + return 0; } return RoutePrecedence.ComputeInboundPrecedenceDigit(endpoint.RoutePattern, segment); @@ -663,37 +663,6 @@ internal Candidate CreateCandidate(Endpoint endpoint, int score) } } - private int[] GetGroupLengths(DfaNode node) - { - var nodeMatches = node.Matches; - if (nodeMatches == null || nodeMatches.Count == 0) - { - return Array.Empty(); - } - - var groups = new List(); - - var length = 1; - var exemplar = nodeMatches[0]; - - for (var i = 1; i < nodeMatches.Count; i++) - { - if (!_comparer.Equals(exemplar, nodeMatches[i])) - { - groups.Add(length); - length = 0; - - exemplar = nodeMatches[i]; - } - - length++; - } - - groups.Add(length); - - return groups.ToArray(); - } - private static bool HasAdditionalRequiredSegments(RouteEndpoint endpoint, int depth) { for (var i = depth; i < endpoint.RoutePattern.PathSegments.Count; i++) diff --git a/src/Http/Routing/src/Matching/DictionaryJumpTable.cs b/src/Http/Routing/src/Matching/DictionaryJumpTable.cs index 644f9c95acf1..38ee3e91e240 100644 --- a/src/Http/Routing/src/Matching/DictionaryJumpTable.cs +++ b/src/Http/Routing/src/Matching/DictionaryJumpTable.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; @@ -29,14 +29,14 @@ public DictionaryJumpTable( } } - public override int GetDestination(string path, PathSegment segment) + public override int GetDestination(ReadOnlySpan path) { - if (segment.Length == 0) + if (path.Length == 0) { return _exitDestination; } - var text = path.Substring(segment.Start, segment.Length); + var text = path.ToString(); if (_dictionary.TryGetValue(text, out var destination)) { return destination; diff --git a/src/Http/Routing/src/Matching/ILEmitTrieFactory.cs b/src/Http/Routing/src/Matching/ILEmitTrieFactory.cs index 3cb240c2fa28..4b97aa27313a 100644 --- a/src/Http/Routing/src/Matching/ILEmitTrieFactory.cs +++ b/src/Http/Routing/src/Matching/ILEmitTrieFactory.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; @@ -20,7 +20,7 @@ internal static class ILEmitTrieFactory // Creates a Func of (string path, int start, int length) => destination // Not using PathSegment here because we don't want to mess with visibility checks and // generating IL without it is easier. - public static Func Create( + public static JumpTableDelegate Create( int defaultDestination, int exitDestination, (string text, int destination)[] entries, @@ -29,7 +29,7 @@ public static Func Create( var method = new DynamicMethod( "GetDestination", typeof(int), - new[] { typeof(string), typeof(int), typeof(int), }); + new[] { typeof(ReadOnlySpan) }); GenerateMethodBody(method.GetILGenerator(), defaultDestination, exitDestination, entries, vectorize); @@ -37,7 +37,7 @@ public static Func Create( SaveAssembly(method.GetILGenerator(), defaultDestination, exitDestination, entries, vectorize); #endif - return (Func)method.CreateDelegate(typeof(Func)); + return method.CreateDelegate(); } // Internal for testing @@ -79,27 +79,22 @@ private static void GenerateMethodBody( var methods = Methods.Instance; // Initializing top-level locals - this is similar to... - // ReadOnlySpan span = arg0.AsSpan(arg1, arg2); - // ref byte p = ref Unsafe.As(MemoryMarshal.GetReference(span)) + // var length = span.Length; + il.Emit(OpCodes.Ldarga_S, 0); + il.Emit(OpCodes.Call, methods.SpanLength); + il.Emit(OpCodes.Stloc, locals.Length); - // arg0.AsSpan(arg1, arg2) - il.Emit(OpCodes.Ldarg_0); - il.Emit(OpCodes.Ldarg_1); - il.Emit(OpCodes.Ldarg_2); - il.Emit(OpCodes.Call, methods.AsSpan); - - // ReadOnlySpan = ... - il.Emit(OpCodes.Stloc, locals.Span); + // ref byte p = ref Unsafe.As(MemoryMarshal.GetReference(arg0)) - // MemoryMarshal.GetReference(span) - il.Emit(OpCodes.Ldloc, locals.Span); + // MemoryMarshal.GetReference(arg0) + il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Call, methods.GetReference); // Unsafe.As(...) il.Emit(OpCodes.Call, methods.As); // ref byte p = ... - il.Emit(OpCodes.Stloc_0, locals.P); + il.Emit(OpCodes.Stloc, locals.P); var groups = entries.GroupBy(e => e.text.Length).ToArray(); for (var i = 0; i < groups.Length; i++) @@ -109,7 +104,7 @@ private static void GenerateMethodBody( // Similar to 'if (length != X) { ... } var inside = il.DefineLabel(); var next = il.DefineLabel(); - il.Emit(OpCodes.Ldarg_2); + il.Emit(OpCodes.Ldloc, locals.Length); il.Emit(OpCodes.Ldc_I4, group.Key); il.Emit(OpCodes.Beq, inside); il.Emit(OpCodes.Br, next); @@ -408,7 +403,7 @@ private static void SaveAssembly( MethodAttributes.Public | MethodAttributes.Static, CallingConventions.Standard, typeof(int), - new [] { typeof(string), typeof(int), typeof(int), }; + new [] { typeof(ReadOnlySpan) }; GenerateMethodBody(method.GetILGenerator(), defaultDestination, exitDestination, entries, vectorize); @@ -422,7 +417,7 @@ private class Locals public Locals(ILGenerator il, bool vectorize) { P = il.DeclareLocal(typeof(byte).MakeByRefType()); - Span = il.DeclareLocal(typeof(ReadOnlySpan)); + Length = il.DeclareLocal(typeof(int)); UInt16Value = il.DeclareLocal(typeof(ushort)); @@ -460,9 +455,9 @@ public Locals(ILGenerator il, bool vectorize) public LocalBuilder P { get; } /// - /// Holds the relevant portion of the path as a Span[byte]. + /// Holds the length of the Span /// - public LocalBuilder Span { get; } + public LocalBuilder Length { get; } } private class Labels @@ -486,6 +481,8 @@ private class Methods private Methods() { + SpanLength = typeof(Span).GetProperty(nameof(Span.Length)).GetGetMethod(); + // Can't use GetMethod because the parameter is a generic method parameters. Add = typeof(Unsafe) .GetMethods(BindingFlags.Public | BindingFlags.Static) @@ -578,6 +575,9 @@ private Methods() /// public MethodInfo AsSpan { get; } + + public MethodInfo SpanLength { get; } + /// /// - GetReference[char] /// diff --git a/src/Http/Routing/src/Matching/ILEmitTrieJumpTable.cs b/src/Http/Routing/src/Matching/ILEmitTrieJumpTable.cs index e59f2834354a..ac52fe092976 100644 --- a/src/Http/Routing/src/Matching/ILEmitTrieJumpTable.cs +++ b/src/Http/Routing/src/Matching/ILEmitTrieJumpTable.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; @@ -30,7 +30,7 @@ internal class ILEmitTrieJumpTable : JumpTable // Will be replaced at runtime by the generated code. // // Internal for testing - internal Func _getDestination; + internal JumpTableDelegate _getDestination; public ILEmitTrieJumpTable( int defaultDestination, @@ -48,14 +48,14 @@ public ILEmitTrieJumpTable( _getDestination = FallbackGetDestination; } - public override int GetDestination(string path, PathSegment segment) + public override int GetDestination(ReadOnlySpan path) { - return _getDestination(path, segment); + return _getDestination(path); } // Used when we haven't yet initialized the IL trie. We defer compilation of the IL for startup // performance. - private int FallbackGetDestination(string path, PathSegment segment) + private int FallbackGetDestination(ReadOnlySpan path) { if (path.Length == 0) { @@ -65,7 +65,7 @@ private int FallbackGetDestination(string path, PathSegment segment) // We only hit this code path if the IL delegate is still initializing. LazyInitializer.EnsureInitialized(ref _task, ref _initializing, ref _lock, InitializeILDelegateAsync); - return _fallback.GetDestination(path, segment); + return _fallback.GetDestination(path); } // Internal for testing @@ -82,17 +82,17 @@ await Task.Run(() => internal void InitializeILDelegate() { var generated = ILEmitTrieFactory.Create(_defaultDestination, _exitDestination, _entries, _vectorize); - _getDestination = (string path, PathSegment segment) => + _getDestination = (ReadOnlySpan path) => { - if (segment.Length == 0) + if (path.Length == 0) { return _exitDestination; } - var result = generated(path, segment.Start, segment.Length); + var result = generated(path); if (result == ILEmitTrieFactory.NotAscii) { - result = _fallback.GetDestination(path, segment); + result = _fallback.GetDestination(path); } return result; diff --git a/src/Http/Routing/src/Matching/JumpTable.cs b/src/Http/Routing/src/Matching/JumpTable.cs index 860ca8041799..144cf4409c7c 100644 --- a/src/Http/Routing/src/Matching/JumpTable.cs +++ b/src/Http/Routing/src/Matching/JumpTable.cs @@ -1,14 +1,17 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; using System.Diagnostics; namespace Microsoft.AspNetCore.Routing.Matching { + internal delegate int JumpTableDelegate(ReadOnlySpan path); + [DebuggerDisplay("{DebuggerToString(),nq}")] internal abstract class JumpTable { - public abstract int GetDestination(string path, PathSegment segment); + public abstract int GetDestination(ReadOnlySpan path); public virtual string DebuggerToString() { diff --git a/src/Http/Routing/src/Matching/LinearSearchJumpTable.cs b/src/Http/Routing/src/Matching/LinearSearchJumpTable.cs index 37d5805c63c6..a3c24c7c383e 100644 --- a/src/Http/Routing/src/Matching/LinearSearchJumpTable.cs +++ b/src/Http/Routing/src/Matching/LinearSearchJumpTable.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; @@ -23,9 +23,9 @@ public LinearSearchJumpTable( _entries = entries; } - public override int GetDestination(string path, PathSegment segment) + public override int GetDestination(ReadOnlySpan path) { - if (segment.Length == 0) + if (path.Length == 0) { return _exitDestination; } @@ -34,14 +34,7 @@ public override int GetDestination(string path, PathSegment segment) for (var i = 0; i < entries.Length; i++) { var text = entries[i].text; - if (segment.Length == text.Length && - string.Compare( - path, - segment.Start, - text, - 0, - segment.Length, - StringComparison.OrdinalIgnoreCase) == 0) + if (path.Length == text.Length && path.Equals(text, StringComparison.OrdinalIgnoreCase)) { return entries[i].destination; } diff --git a/src/Http/Routing/src/Matching/SingleEntryAsciiJumpTable.cs b/src/Http/Routing/src/Matching/SingleEntryAsciiJumpTable.cs index 5ca0bab566cc..22200843741d 100644 --- a/src/Http/Routing/src/Matching/SingleEntryAsciiJumpTable.cs +++ b/src/Http/Routing/src/Matching/SingleEntryAsciiJumpTable.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; @@ -27,9 +27,9 @@ public SingleEntryAsciiJumpTable( _destination = destination; } - public unsafe override int GetDestination(string path, PathSegment segment) + public override int GetDestination(ReadOnlySpan path) { - var length = segment.Length; + var length = path.Length; if (length == 0) { return _exitDestination; @@ -41,7 +41,7 @@ public unsafe override int GetDestination(string path, PathSegment segment) return _defaultDestination; } - var a = path.AsSpan(segment.Start, length); + var a = path; var b = text.AsSpan(); return Ascii.AsciiIgnoreCaseEquals(a, b, length) ? _destination : _defaultDestination; diff --git a/src/Http/Routing/src/Matching/SingleEntryJumpTable.cs b/src/Http/Routing/src/Matching/SingleEntryJumpTable.cs index 3901807ce502..f87863687bed 100644 --- a/src/Http/Routing/src/Matching/SingleEntryJumpTable.cs +++ b/src/Http/Routing/src/Matching/SingleEntryJumpTable.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; @@ -24,21 +24,14 @@ public SingleEntryJumpTable( _destination = destination; } - public override int GetDestination(string path, PathSegment segment) + public override int GetDestination(ReadOnlySpan path) { - if (segment.Length == 0) + if (path.Length == 0) { return _exitDestination; } - if (segment.Length == _text.Length && - string.Compare( - path, - segment.Start, - _text, - 0, - segment.Length, - StringComparison.OrdinalIgnoreCase) == 0) + if (path.Length == _text.Length && path.Equals(_text, StringComparison.OrdinalIgnoreCase)) { return _destination; } diff --git a/src/Http/Routing/src/Matching/ZeroEntryJumpTable.cs b/src/Http/Routing/src/Matching/ZeroEntryJumpTable.cs index a2100662e1dd..e4b37dc24ea8 100644 --- a/src/Http/Routing/src/Matching/ZeroEntryJumpTable.cs +++ b/src/Http/Routing/src/Matching/ZeroEntryJumpTable.cs @@ -1,6 +1,8 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; + namespace Microsoft.AspNetCore.Routing.Matching { internal class ZeroEntryJumpTable : JumpTable @@ -14,9 +16,9 @@ public ZeroEntryJumpTable(int defaultDestination, int exitDestination) _exitDestination = exitDestination; } - public override int GetDestination(string path, PathSegment segment) + public override int GetDestination(ReadOnlySpan path) { - return segment.Length == 0 ? _exitDestination : _defaultDestination; + return path.Length == 0 ? _exitDestination : _defaultDestination; } public override string DebuggerToString() diff --git a/src/Http/Routing/test/UnitTests/Matching/ILEmitTrieJumpTableTest.cs b/src/Http/Routing/test/UnitTests/Matching/ILEmitTrieJumpTableTest.cs index 4a435514ab16..fa8411b8f0ea 100644 --- a/src/Http/Routing/test/UnitTests/Matching/ILEmitTrieJumpTableTest.cs +++ b/src/Http/Routing/test/UnitTests/Matching/ILEmitTrieJumpTableTest.cs @@ -1,7 +1,8 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using Moq; +using System; using System.Threading.Tasks; using Xunit; @@ -92,7 +93,7 @@ public void GetDestination_Found_IncludesNonAsciiCharacters(string entry, string var segment = new PathSegment(start, length); // Act - var result = table.GetDestination(path, segment); + var result = table.GetDestination(path.AsSpan(segment.Start, segment.Length)); // Assert Assert.Equal(1, result); @@ -153,7 +154,7 @@ public void GetDestination_Found_IncludesCharactersWithCasingDifference(string e var segment = new PathSegment(start, length); // Act - var result = table.GetDestination(path, segment); + var result = table.GetDestination(path.AsSpan(segment.Start, segment.Length)); // Assert Assert.Equal(1, result); @@ -217,7 +218,7 @@ public void GetDestination_NotFound_IncludesCharactersWithCasingDifference(strin var segment = new PathSegment(start, length); // Act - var result = table.GetDestination(path, segment); + var result = table.GetDestination(path.AsSpan(segment.Start, segment.Length)); // Assert Assert.Equal(0, result); diff --git a/src/Http/Routing/test/UnitTests/Matching/MultipleEntryJumpTableTest.cs b/src/Http/Routing/test/UnitTests/Matching/MultipleEntryJumpTableTest.cs index 1a1a00de2add..8c0179d6bb38 100644 --- a/src/Http/Routing/test/UnitTests/Matching/MultipleEntryJumpTableTest.cs +++ b/src/Http/Routing/test/UnitTests/Matching/MultipleEntryJumpTableTest.cs @@ -1,6 +1,7 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; using Xunit; namespace Microsoft.AspNetCore.Routing.Matching @@ -19,7 +20,7 @@ public void GetDestination_ZeroLengthSegment_JumpsToExit() var table = CreateTable(0, 1, ("text", 2)); // Act - var result = table.GetDestination("ignored", new PathSegment(0, 0)); + var result = table.GetDestination("ignored".AsSpan(0, 0)); // Assert Assert.Equal(1, result); @@ -32,7 +33,7 @@ public void GetDestination_NonMatchingSegment_JumpsToDefault() var table = CreateTable(0, 1, ("text", 2)); // Act - var result = table.GetDestination("text", new PathSegment(1, 2)); + var result = table.GetDestination("text".AsSpan(1, 2)); // Assert Assert.Equal(0, result); @@ -45,7 +46,7 @@ public void GetDestination_SegmentMatchingText_JumpsToDestination() var table = CreateTable(0, 1, ("text", 2)); // Act - var result = table.GetDestination("some-text", new PathSegment(5, 4)); + var result = table.GetDestination("some-text".AsSpan(5, 4)); // Assert Assert.Equal(2, result); @@ -58,7 +59,7 @@ public void GetDestination_SegmentMatchingTextIgnoreCase_JumpsToDestination() var table = CreateTable(0, 1, ("text", 2)); // Act - var result = table.GetDestination("some-tExt", new PathSegment(5, 4)); + var result = table.GetDestination("some-tExt".AsSpan(5, 4)); // Assert Assert.Equal(2, result); @@ -71,7 +72,7 @@ public void GetDestination_SegmentMatchingTextIgnoreCase_MultipleEntries() var table = CreateTable(0, 1, ("tezt", 2), ("text", 3)); // Act - var result = table.GetDestination("some-tExt", new PathSegment(5, 4)); + var result = table.GetDestination("some-tExt".AsSpan(5, 4)); // Assert Assert.Equal(3, result); diff --git a/src/Http/Routing/test/UnitTests/Matching/SingleEntryJumpTableTestBase.cs b/src/Http/Routing/test/UnitTests/Matching/SingleEntryJumpTableTestBase.cs index dd3c5953a6d8..e5642926ccc8 100644 --- a/src/Http/Routing/test/UnitTests/Matching/SingleEntryJumpTableTestBase.cs +++ b/src/Http/Routing/test/UnitTests/Matching/SingleEntryJumpTableTestBase.cs @@ -1,6 +1,7 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; using Xunit; namespace Microsoft.AspNetCore.Routing.Matching @@ -20,7 +21,7 @@ public void GetDestination_ZeroLengthSegment_JumpsToExit() var table = CreateJumpTable(0, 1, "text", 2); // Act - var result = table.GetDestination("ignored", new PathSegment(0, 0)); + var result = table.GetDestination("ignored".AsSpan(0, 0)); // Assert Assert.Equal(1, result); @@ -33,7 +34,7 @@ public void GetDestination_NonMatchingSegment_JumpsToDefault() var table = CreateJumpTable(0, 1, "text", 2); // Act - var result = table.GetDestination("text", new PathSegment(1, 2)); + var result = table.GetDestination("text".AsSpan(1, 2)); // Assert Assert.Equal(0, result); @@ -46,7 +47,7 @@ public void GetDestination_SegmentMatchingText_JumpsToDestination() var table = CreateJumpTable(0, 1, "text", 2); // Act - var result = table.GetDestination("some-text", new PathSegment(5, 4)); + var result = table.GetDestination("some-text".AsSpan(5, 4)); // Assert Assert.Equal(2, result); @@ -59,7 +60,7 @@ public void GetDestination_SegmentMatchingTextIgnoreCase_JumpsToDestination() var table = CreateJumpTable(0, 1, "text", 2); // Act - var result = table.GetDestination("some-tExt", new PathSegment(5, 4)); + var result = table.GetDestination("some-tExt".AsSpan(5, 4)); // Assert Assert.Equal(2, result); diff --git a/src/Http/Routing/test/UnitTests/Matching/ZeroEntryJumpTableTest.cs b/src/Http/Routing/test/UnitTests/Matching/ZeroEntryJumpTableTest.cs index 036fef0c03af..7fc480a03646 100644 --- a/src/Http/Routing/test/UnitTests/Matching/ZeroEntryJumpTableTest.cs +++ b/src/Http/Routing/test/UnitTests/Matching/ZeroEntryJumpTableTest.cs @@ -1,6 +1,7 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; using Xunit; namespace Microsoft.AspNetCore.Routing.Matching @@ -14,7 +15,7 @@ public void GetDestination_ZeroLengthSegment_JumpsToExit() var table = new ZeroEntryJumpTable(0, 1); // Act - var result = table.GetDestination("ignored", new PathSegment(0, 0)); + var result = table.GetDestination("ignored".AsSpan(0, 0)); // Assert Assert.Equal(1, result); @@ -27,7 +28,7 @@ public void GetDestination_SegmentWithLength_JumpsToDefault() var table = new ZeroEntryJumpTable(0, 1); // Act - var result = table.GetDestination("ignored", new PathSegment(0, 1)); + var result = table.GetDestination("ignored".AsSpan(0, 1)); // Assert Assert.Equal(0, result); From 6e50aa2001835ab2af33fcf11339c043cc8a4aa7 Mon Sep 17 00:00:00 2001 From: David Fowler Date: Sun, 31 May 2020 17:34:49 -0700 Subject: [PATCH 2/2] PR feedback --- src/Http/Routing/src/Matching/LinearSearchJumpTable.cs | 2 +- src/Http/Routing/src/Matching/SingleEntryJumpTable.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Http/Routing/src/Matching/LinearSearchJumpTable.cs b/src/Http/Routing/src/Matching/LinearSearchJumpTable.cs index a3c24c7c383e..3c7ef2226953 100644 --- a/src/Http/Routing/src/Matching/LinearSearchJumpTable.cs +++ b/src/Http/Routing/src/Matching/LinearSearchJumpTable.cs @@ -34,7 +34,7 @@ public override int GetDestination(ReadOnlySpan path) for (var i = 0; i < entries.Length; i++) { var text = entries[i].text; - if (path.Length == text.Length && path.Equals(text, StringComparison.OrdinalIgnoreCase)) + if (path.Equals(text, StringComparison.OrdinalIgnoreCase)) { return entries[i].destination; } diff --git a/src/Http/Routing/src/Matching/SingleEntryJumpTable.cs b/src/Http/Routing/src/Matching/SingleEntryJumpTable.cs index f87863687bed..f4c9d2d4866e 100644 --- a/src/Http/Routing/src/Matching/SingleEntryJumpTable.cs +++ b/src/Http/Routing/src/Matching/SingleEntryJumpTable.cs @@ -31,7 +31,7 @@ public override int GetDestination(ReadOnlySpan path) return _exitDestination; } - if (path.Length == _text.Length && path.Equals(_text, StringComparison.OrdinalIgnoreCase)) + if (path.Equals(_text, StringComparison.OrdinalIgnoreCase)) { return _destination; }