Skip to content
This repository was archived by the owner on Nov 20, 2018. It is now read-only.

Add InplaceStringFormatter #717

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 80 additions & 0 deletions src/Microsoft.AspNetCore.Http/Internal/InplaceStringFormatter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// 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.Runtime.CompilerServices;

namespace Microsoft.AspNetCore.Http.Internal
{
public struct InplaceStringFormatter
{
private int _length;
private int _offset;
private bool _writing;
private string _value;

public InplaceStringFormatter(int length) : this()
{
_length = length;
}

public void AppendLength(string s)
{
AppendLength(s.Length);
}
public void AppendLength(char c)
{
AppendLength(1);
}

public void AppendLength(int length)
{
if (_writing)
{
throw new InvalidOperationException("Cannot append lenght after write started.");
}
_length += length;
}

public unsafe void Append(string s)
{
EnsureValue(s.Length);
fixed (char* value = _value)
fixed (char* pDomainToken = s)
{
Unsafe.CopyBlock(value + _offset, pDomainToken, (uint)s.Length * 2);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While this might currently work, don't think you can use CopyBlock as the spec says it expects all writes to be aligned dotnet/coreclr#7198 (comment)

ECMA-355 says:
"cpblk assumes that both destaddr and srcaddr are aligned to the natural size of the machine (but see the unaligned. prefix instruction). The operation of the cpblk instruction can be altered by an immediately preceding volatile. or unaligned. prefix instruction.

Might want to get a Unsafe.CopyBlockUnaligned added. This has also been pointed out in its use in corefxlabs

_offset += s.Length;
}
}
public unsafe void Append(char c)
{
EnsureValue(1);
fixed (char* value = _value)
{
value[_offset++] = c;
}
}

private void EnsureValue(int length)
{
if (_value == null)
{
_writing = true;
_value = new string('\0', _length);
}
if (_offset + length > _length)
{
throw new InvalidOperationException($"Not enough space to write '{length}' characters, only '{_length - _offset}' left.");
}
}

public override string ToString()
{
if (_offset != _length)
{
throw new InvalidOperationException($"Entire reserved lenght was not used. Length: '{_length}', written '{_offset}'.");
}
return _value;
}
}
}
3 changes: 2 additions & 1 deletion src/Microsoft.AspNetCore.Http/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@
"type": "build"
},
"Microsoft.Net.Http.Headers": "1.1.0-*",
"System.Buffers": "4.0.0-*"
"System.Buffers": "4.0.0-*",
"System.Runtime.CompilerServices.Unsafe": "4.0.0"
},
"frameworks": {
"net451": {},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
using System;
using Microsoft.AspNetCore.Http.Internal;
using Xunit;

namespace Microsoft.AspNetCore.Http.Tests.Internal
{
public class InplaceStringFormatterTest
{
[Fact]
public void ToString_ReturnsStringWithAllAppendedValues()
{
var s1 = "123";
var c1 = '4';
var s2 = "56789";

var formatter = new InplaceStringFormatter();
formatter.AppendLength(s1);
formatter.AppendLength(c1);
formatter.AppendLength(s2);
formatter.Append(s1);
formatter.Append(c1);
formatter.Append(s2);
Assert.Equal("123456789", formatter.ToString());
}

[Fact]
public void ToString_ThrowsIfNotEnoughWritten()
{
var formatter = new InplaceStringFormatter(5);
formatter.Append("123");
var exception = Assert.Throws<InvalidOperationException>(() => formatter.ToString());
Assert.Equal(exception.Message, "Entire reserved lenght was not used. Length: '5', written '3'.");
}

[Fact]
public void AppendLength_IfAppendWasCalled()
{
var formatter = new InplaceStringFormatter(3);
formatter.Append("123");

var exception = Assert.Throws<InvalidOperationException>(() => formatter.AppendLength(1));
Assert.Equal(exception.Message, "Cannot append lenght after write started.");
}

[Fact]
public void Append_ThrowsIfNotEnoughSpace()
{
var formatter = new InplaceStringFormatter(1);

var exception = Assert.Throws<InvalidOperationException>(() => formatter.Append("123"));
Assert.Equal(exception.Message, "Not enough space to write '3' characters, only '1' left.");
}
}
}