Skip to content

Do not use stackalloc inside of a loop #33782

@terrajobst

Description

@terrajobst

Calling stackalloc in a loop leads to stack overflow. This bug is easy to introduce with the above.

Category: Performance

Examples of patterns to report on

For loop, iteration count is non-constant

private int SomeMethod(ReadOnlySpan<byte> source, Span<byte> destination)
{
    int writeOffset = 0;

    for (int i = 0; i < source.Length; i += ChunkSize)
    {
        Span<byte> tmp = stackalloc byte[32];
        writeOffset += CRC32(
            source.Slice(i, Math.Min(source.Length - i, ChunkSize)),
            tmp);

        WriteReversed(tmp, destination.Slice(writeOffset));
    }

    return writeOffset;
}

While loop, non-constant iteration count:

private int SomeMethod(ReadOnlySpan<byte> source, Span<byte> destination)
{
    int writeOffset = 0;

    while (!source.IsEmpty)
    {
        Span<byte> tmp = stackalloc byte[32];
        ReadOnlySpan<byte> cur = source.Length < ChunkSize ? source : source.Slice(0, ChunkSize);

        writeOffset += CRC32(cur, tmp);
        WriteReversed(tmp, destination.Slice(writeOffset));
        source = source.Slice(cur.Length);
    }

    return writeOffset;
}

(Also do/while, and foreach)

Examples of patterns to not report on

This method always has the same sized stackalloc.
It could be used more efficiently, but it's deterministic, so it's not worth reporting on.

private int SomeMethod(ReadOnlySpan<byte> source, Span<byte> destination)
{
    for (int i = 0; i < 4; i++)
    {
        Span<byte> tmp = stackalloc byte[32];
        CRC32(source.Slice(i), tmp);
        WriteReversed(tmp, destination.Slice(i));
    }

    return 35;
}

Stackalloc is in a loop, but it only ever runs on one iteration:

private int SomeMethod(ReadOnlySpan<byte> source, Span<byte> destination)
{
    for (int i = 1; i < source.Length; i++)
    {
        if (source[i - 1] + source[i] > 202)
        {
            Span<byte> tmp = stackalloc byte[32];
            CRC32(source.Slice(i+1), tmp);
            WriteReversed(tmp, destination);
            return 32;
        }
    }

    return 0;
}

Same idea, different shape.

private int SomeMethod(Span<byte> destination)
{
    int accum = 0;

    while (true)
    {
        ReadOnlySpan<byte> next = GetNextData();

        for (int i = 0; i < next.Length; i++)
        {
            accum += next[i];
        }

        int popCount = PopCount(accum);

        if (popCount > 2 && popCount < 6)
        {
            if (_reversed)
            {
                Span<byte> tmp = stackalloc byte[32];
                CRC32(accum.AsBytes(), tmp);
                WriteReversed(tmp, destination);
            }
            else
            {
                CRC32(accum.AsBytes(), destination);
            }

            break;
        }
    }

    return 32;
}

Metadata

Metadata

Assignees

Labels

api-approvedAPI was approved in API review, it can be implementedarea-System.Runtimecode-analyzerMarks an issue that suggests a Roslyn analyzer

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions