Skip to content

Fix OverflowException in JsonSerializer.Serialize - #1308

Merged
ahsonkhan merged 8 commits into
dotnet:masterfrom
felipepessoto:fix-609
Feb 20, 2020
Merged

Fix OverflowException in JsonSerializer.Serialize#1308
ahsonkhan merged 8 commits into
dotnet:masterfrom
felipepessoto:fix-609

Conversation

@felipepessoto

Copy link
Copy Markdown
Contributor

Fix #609

@felipepessoto felipepessoto changed the title [WIP] - Fix OverflowException [WIP] - Fix OverflowException in JsonSerializer.Serialize Jan 5, 2020
@felipepessoto felipepessoto changed the title [WIP] - Fix OverflowException in JsonSerializer.Serialize Fix OverflowException in JsonSerializer.Serialize Jan 8, 2020
@azure-pipelines

Copy link
Copy Markdown
Commenter does not have sufficient privileges for PR 1308 in repo dotnet/runtime

@danmoseley

Copy link
Copy Markdown
Contributor

/azp run

@azure-pipelines

Copy link
Copy Markdown
You have several pipelines (over 10) configured to build pull requests in this repository. Specify which pipelines you would like to run by using /azp run [pipelines] command. You can specify multiple pipelines using a comma separated list.

@stephentoub

Copy link
Copy Markdown
Member

@ahsonkhan, @steveharter, can you take a look as well? Thanks.

Comment thread src/libraries/System.Text.Json/tests/Serialization/WriteValueTests.cs Outdated
Comment thread src/libraries/System.Text.Json/tests/Serialization/WriteValueTests.cs Outdated
Comment thread src/libraries/System.Text.Json/tests/Serialization/WriteValueTests.cs Outdated
Comment thread src/libraries/System.Text.Json/tests/Serialization/WriteValueTests.cs Outdated
@ahsonkhan

Copy link
Copy Markdown
Contributor

Fix #609

We should consider making a similar fix to the public ArrayBufferWriter as well, based on where we land in this PR.

if (sizeHint > FreeCapacity)
{
int growBy = Math.Max(sizeHint, _buffer.Length);
if (_buffer.Length == 0)
{
growBy = Math.Max(growBy, DefaultInitialBufferSize);
}
int newSize = checked(_buffer.Length + growBy);
Array.Resize(ref _buffer, newSize);
}

@stephentoub

Copy link
Copy Markdown
Member

@felipepessoto, sorry for the delay. This looks good and ready to go, but there's a conflict in the tests now... can you rebase to address it?

@felipepessoto

Copy link
Copy Markdown
Contributor Author

@felipepessoto, sorry for the delay. This looks good and ready to go, but there's a conflict in the tests now... can you rebase to address it?

Yes sure. But I still have some pending things to do. I still didn't stop to check how can I restrict the test to 64bits, and I couldn't run the coverage test yet, last time it took hours and didn't finish

@stephentoub

Copy link
Copy Markdown
Member

I still didn't stop to check how can I restrict the test to 64bits

e.g.


public static bool IsX64 { get; } = IntPtr.Size >= 8;

@ahsonkhan

ahsonkhan commented Feb 14, 2020

Copy link
Copy Markdown
Contributor

I couldn't run the coverage test yet, last time it took hours and didn't finish

You are probably building using a debug coreclr which causes tests to take a long time to run. If you build coreclr for release and then build the libraries using the release coreclr, the tests should run faster.

I was able to run the coverage after merging master into your branch (by changing your test to be part of inner loop, removed outerloop from attribute):
From root of repo run the following:
build.cmd -subsetCategory coreclr -c Release && build.cmd -subsetCategory libraries /p:CoreCLRConfiguration=Release
Then navigate to src\libraries\System.Text.Json\tests and run:
dotnet msbuild /t:BuildAndTest /p:Coverage=true

See #839 (comment)

cc @safern, @ViktorHofer - do we need to increase the visibility of this issue so fewer folks hit against running tests with debug coreclr, by default?

Looks like all the new branches introduced in this PR are covered by the test :)
image

The path that is likely not covered (and this sequence of code flow won't show up in test coverage):
(uint)(currentLength + growBy) > int.MaxValue is true but (uint)(currentLength + sizeHint) > int.MaxValue is false

If you do run all outerloop tests, it will take a lot longer (~10 minutes for me, but shouldn't take hours):
dotnet msbuild /t:RebuildAndTest /p:Coverage=true /p:Outerloop=true

===========================================================================================================
    Discovering: System.Text.Json.Tests (method display = ClassAndMethod, method display options = None)
    Discovered:  System.Text.Json.Tests (found 1692 of 1703 test cases)
    Starting:    System.Text.Json.Tests (parallel test collections = on, max threads = 8)
    Finished:    System.Text.Json.Tests
  === TEST EXECUTION SUMMARY ===
     System.Text.Json.Tests  Total: 11482, Errors: 0, Failed: 0, Skipped: 0, Time: 365.595s

Comment thread src/libraries/System.Text.Json/tests/Serialization/WriteValueTests.cs Outdated
At first I tried to create five internal references to CustomClassToExceedMaxBufferSize and create 25 levels deep instances, but the tests ran forever. So I changed it to have several big string properties, which lead to the System.OverflowException using 3 level deep instance.
…s to use growBy = sizeHint

If it still overflow, we set the newSize to int.MaxValue, which will throw an OutOfMemoryException when calling ArrayPool.Rent.
OuterLoop
Make the test simpler
Run test on 64 bits only
@felipepessoto

Copy link
Copy Markdown
Contributor Author

I still didn't stop to check how can I restrict the test to 64bits

e.g.

public static bool IsX64 { get; } = IntPtr.Size >= 8;

Thx, done

Comment thread src/libraries/System.Text.Json/tests/Serialization/WriteValueTests.cs Outdated
Separate OuterLoop attribute, to be consistent with the other tests in this solution.
@ahsonkhan

Copy link
Copy Markdown
Contributor

@felipepessoto - are you up for making a similar fix to ArrayBufferWriter<T>, in a separate PR to fix/close the issue?

private void CheckAndResizeBuffer(int sizeHint)
{
if (sizeHint < 0)
throw new ArgumentException(nameof(sizeHint));
if (sizeHint == 0)
{
sizeHint = 1;
}
if (sizeHint > FreeCapacity)
{
int growBy = Math.Max(sizeHint, _buffer.Length);
if (_buffer.Length == 0)
{
growBy = Math.Max(growBy, DefaultInitialBufferSize);
}
int newSize = checked(_buffer.Length + growBy);
Array.Resize(ref _buffer, newSize);
}
Debug.Assert(FreeCapacity > 0 && FreeCapacity >= sizeHint);
}

@felipepessoto

Copy link
Copy Markdown
Contributor Author

@felipepessoto - are you up for making a similar fix to ArrayBufferWriter<T>, in a separate PR to fix/close the issue?

private void CheckAndResizeBuffer(int sizeHint)
{
if (sizeHint < 0)
throw new ArgumentException(nameof(sizeHint));
if (sizeHint == 0)
{
sizeHint = 1;
}
if (sizeHint > FreeCapacity)
{
int growBy = Math.Max(sizeHint, _buffer.Length);
if (_buffer.Length == 0)
{
growBy = Math.Max(growBy, DefaultInitialBufferSize);
}
int newSize = checked(_buffer.Length + growBy);
Array.Resize(ref _buffer, newSize);
}
Debug.Assert(FreeCapacity > 0 && FreeCapacity >= sizeHint);
}

Absolutely

@ahsonkhan
ahsonkhan merged commit b5ea894 into dotnet:master Feb 20, 2020
@felipepessoto
felipepessoto deleted the fix-609 branch February 20, 2020 22:13
@layomia layomia added this to the 5.0 milestone May 15, 2020
@ghost ghost locked as resolved and limited conversation to collaborators Dec 11, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Json Arithmetic operation resulted in an overflow

6 participants