Skip to content
Merged
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
194 changes: 174 additions & 20 deletions src/coreclr/tools/Common/TypeSystem/Common/ExplicitLayoutValidator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Generic;
using System.Diagnostics;

namespace Internal.TypeSystem
Expand All @@ -15,22 +16,58 @@ private enum FieldLayoutTag : byte
ORef,
}

private struct FieldLayoutInterval : IComparable<FieldLayoutInterval>
{
public FieldLayoutInterval(int start, int size, FieldLayoutTag tag)
{
Start = start;
Size = size;
Tag = tag;
}

public int Start;
public int Size;

public int EndSentinel
{
get
{
return Start + Size;
}
set
{
Size = value - Start;
Debug.Assert(Size >= 0);
}
}

public FieldLayoutTag Tag;

public int CompareTo(FieldLayoutInterval other)
{
return Start.CompareTo(other.Start);
}
}

private readonly int _pointerSize;

private readonly FieldLayoutTag[] _fieldLayout;
// Represent field layout bits as as a series of intervals to prevent pathological bad behavior
// involving excessively large explicit layout structures.
private readonly List<FieldLayoutInterval> _fieldLayout = new List<FieldLayoutInterval>();

private readonly MetadataType _typeBeingValidated;

private ExplicitLayoutValidator(MetadataType type, int typeSizeInBytes)
{
_typeBeingValidated = type;
_pointerSize = type.Context.Target.PointerSize;
_fieldLayout = new FieldLayoutTag[typeSizeInBytes];
}

public static void Validate(MetadataType type, ComputedInstanceFieldLayout layout)
{
ExplicitLayoutValidator validator = new ExplicitLayoutValidator(type, layout.ByteCountUnaligned.AsInt);
int typeSizeInBytes = layout.ByteCountUnaligned.AsInt;
ExplicitLayoutValidator validator = new ExplicitLayoutValidator(type, typeSizeInBytes);

foreach (FieldAndOffset fieldAndOffset in layout.Offsets)
{
validator.AddToFieldLayout(fieldAndOffset.Offset.AsInt, fieldAndOffset.Field.FieldType);
Expand Down Expand Up @@ -75,11 +112,25 @@ private void AddToFieldLayout(int offset, TypeDesc fieldType)
ThrowFieldLayoutError(offset);
}

bool[] fieldORefMap = new bool[fieldSize];
List<FieldLayoutInterval> fieldORefMap = new List<FieldLayoutInterval>();
MarkORefLocations(mdType, fieldORefMap, offset: 0);
for (int index = 0; index < fieldSize; index++)

// Merge in fieldORefMap from structure specifying not attributed intervals as NonORef
int lastGCRegionReportedEnd = 0;

foreach (var gcRegion in fieldORefMap)
{
SetFieldLayout(offset + index, fieldORefMap[index] ? FieldLayoutTag.ORef : FieldLayoutTag.NonORef);
SetFieldLayout(offset + lastGCRegionReportedEnd, gcRegion.Start - lastGCRegionReportedEnd, FieldLayoutTag.NonORef);
Debug.Assert(gcRegion.Tag == FieldLayoutTag.ORef);
SetFieldLayout(offset + gcRegion.Start, gcRegion.Size, gcRegion.Tag);
lastGCRegionReportedEnd = gcRegion.EndSentinel;
}

if (fieldORefMap.Count > 0)
{
int trailingRegionStart = fieldORefMap[fieldORefMap.Count - 1].EndSentinel;
int trailingRegionSize = fieldSize - trailingRegionStart;
SetFieldLayout(offset + trailingRegionStart, trailingRegionSize, FieldLayoutTag.NonORef);
}
}
}
Expand All @@ -98,7 +149,7 @@ private void AddToFieldLayout(int offset, TypeDesc fieldType)
}
}

private void MarkORefLocations(MetadataType type, bool[] orefMap, int offset)
private void MarkORefLocations(MetadataType type, List<FieldLayoutInterval> orefMap, int offset)
{
// Recurse into struct fields
foreach (FieldDesc field in type.GetFields())
Expand All @@ -108,10 +159,7 @@ private void MarkORefLocations(MetadataType type, bool[] orefMap, int offset)
int fieldOffset = offset + field.Offset.AsInt;
if (field.FieldType.IsGCPointer)
{
for (int index = 0; index < _pointerSize; index++)
{
orefMap[fieldOffset + index] = true;
}
SetFieldLayout(orefMap, offset, _pointerSize, FieldLayoutTag.ORef);
}
else if (field.FieldType.IsValueType)
{
Expand All @@ -125,27 +173,133 @@ private void MarkORefLocations(MetadataType type, bool[] orefMap, int offset)
}
}

private void SetFieldLayout(int offset, int count, FieldLayoutTag tag)
private void SetFieldLayout(List<FieldLayoutInterval> fieldLayoutInterval, int offset, int count, FieldLayoutTag tag)
{
for (int index = 0; index < count; index++)
if (count == 0)
return;

var newInterval = new FieldLayoutInterval(offset, count, tag);

int binarySearchIndex = fieldLayoutInterval.BinarySearch(newInterval);

if (binarySearchIndex >= 0)
{
var existingInterval = fieldLayoutInterval[binarySearchIndex];

// Exact match found for start of interval.
if (tag != existingInterval.Tag)
{
ThrowFieldLayoutError(offset);
}

if (existingInterval.Size >= count)
{
// Existing interval is big enough.
}
else
{
// Expand existing interval, and then check to see if that's valid.
existingInterval.Size = count;
fieldLayoutInterval[binarySearchIndex] = existingInterval;

ValidateAndMergeIntervalWithFollowingIntervals(fieldLayoutInterval, binarySearchIndex);
}
}
else
{
SetFieldLayout(offset + index, tag);
// No exact start match found.

int newIntervalLocation = ~binarySearchIndex;

// Check for previous interval overlaps cases
if (newIntervalLocation > 0)
{
var previousInterval = fieldLayoutInterval[newIntervalLocation - 1];
bool tagMatches = previousInterval.Tag == tag;

if (previousInterval.EndSentinel > offset)
{
// Previous interval overlaps.
if (!tagMatches)
{
ThrowFieldLayoutError(offset);
}
}

if (previousInterval.EndSentinel > offset || (tagMatches && previousInterval.EndSentinel == offset))
{
// Previous interval overlaps, or exactly matches up with new interval and tag matches. Instead
// of expanding interval set, simply expand the previous interval.
previousInterval.EndSentinel = newInterval.EndSentinel;

fieldLayoutInterval[newIntervalLocation - 1] = previousInterval;
newIntervalLocation = newIntervalLocation - 1;
}
else
{
fieldLayoutInterval.Insert(newIntervalLocation, newInterval);
}
}
else
{
// New interval added at start
fieldLayoutInterval.Insert(newIntervalLocation, newInterval);
}

ValidateAndMergeIntervalWithFollowingIntervals(fieldLayoutInterval, newIntervalLocation);
}
}

private void SetFieldLayout(int offset, FieldLayoutTag tag)
private void ValidateAndMergeIntervalWithFollowingIntervals(List<FieldLayoutInterval> fieldLayoutInterval, int intervalIndex)
{
FieldLayoutTag existingTag = _fieldLayout[offset];
if (existingTag != tag)
while(true)
{
if (existingTag != FieldLayoutTag.Empty)
if (intervalIndex + 1 == fieldLayoutInterval.Count)
{
ThrowFieldLayoutError(offset);
// existing interval is last interval. Expansion always succeeds
break;
}
else
{
var nextInterval = fieldLayoutInterval[intervalIndex + 1];
var expandedInterval = fieldLayoutInterval[intervalIndex];
var tag = expandedInterval.Tag;

if (nextInterval.Start > expandedInterval.EndSentinel)
{
// Next interval does not contact existing interval. Expansion succeeded
break;
}

if ((nextInterval.Start == expandedInterval.EndSentinel) && nextInterval.Tag != tag)
{
// Next interval starts just after existing interval, but does not match tag. Expansion succeeded
break;
}

Debug.Assert(nextInterval.Start <= expandedInterval.EndSentinel);
// Next interval overlaps with expanded interval.

if (nextInterval.Tag != tag)
{
ThrowFieldLayoutError(nextInterval.Start);
}

// Expand existing interval to cover region of next interval
expandedInterval.EndSentinel = nextInterval.EndSentinel;
fieldLayoutInterval[intervalIndex] = expandedInterval;

// Remove next interval
fieldLayoutInterval.RemoveAt(intervalIndex + 1);
}
_fieldLayout[offset] = tag;
}
}

private void SetFieldLayout(int offset, int count, FieldLayoutTag tag)
{
SetFieldLayout(_fieldLayout, offset, count, tag);
}

private void ThrowFieldLayoutError(int offset)
{
ThrowHelper.ThrowTypeLoadException(ExceptionStringID.ClassLoadExplicitLayout, _typeBeingValidated, offset.ToStringInvariant());
Expand Down