in src/Microsoft.Diagnostics.Runtime/src/Common/DACNameParser.cs [33:277]
public static string? Parse(string? name)
{
if(name == null)
return null;
if(name.Length == 0)
return name;
try
{
// This is the primary method of the parser. It operates as a simple state machine storing information about types encountered as
// they are encountered and back-propagating information to types as it is discovered.
//
// The BEST way to debug/understand this method is to simply step through it while watching genericArgs and nameSegments in the debugger watch window. The TypeNameSegment
// has a DebuggerDisplayAttribute so you should see the state of the type as it goes through transitions (i.e. going from simple to generic with a known count of args, as
// its unknown args are filled in seeing them appear in the debugger display string as we patch in the actual type names, etc...)
ParsingState currentState = ParsingState.ParsingTypeName;
// The name segment lists hold types as we are constructing them. These can be complete types (such as System.String) or partial types (like List<T> before we have parsed what T is)
// or nested types (like the +Entry part of Dictionary<TKey, TEntry>+Entry) which may be later unified with the type they are nested in (we represent parses like Dictionary`2+Entry
// as two TypeNameSegments (Dictionary`2 and Entry) and unify them later since when we extract Dictionary we don't yet know its generic arity or the fact it is the outer type in
// a nested type).
//
// The nameSegments relate to top-level type names, the genericArgs relate to generic argument lists as we are parsing them. The genericArgs entries are back-propagated both to earlier
// generic args as well as to types in the nameSegments list, as we complete the argument list parsing.
List<TypeNameSegment>? nameSegments = null;
List<TypeNameSegment>? genericArgs = null;
// Local helper methods to help minimize code duplication
void EnsureNameSegmentList()
{
if (nameSegments == null)
nameSegments = new List<TypeNameSegment>();
}
void EnsureGenericArgList()
{
if (genericArgs == null)
genericArgs = new List<TypeNameSegment>();
}
int curPos = 0;
int parsingNestedClassDepth = 0;
int parsingGenericArgListDepth = 0;
while (ShouldContinueParsing(currentState))
{
switch (currentState)
{
case ParsingState.ParsingTypeName:
{
// We are parsing a type name, this is the initial state as well as one entered into every time we are extracting a single type name through the course of parsing.
// This handles parsing nested types as well as generic param types.
//
// Parsing of type names is non-extractive, i.e. we don't substring the input string or create any new heap allocations to track them (apart from the two local
// Lists), we simply remember the extents of the name (start/end).
int start;
(start, curPos) = GetTypeNameExtent(name, curPos, parsingGenericArgList: parsingGenericArgListDepth != 0);
// Special case: Check if after parsing the type name we have exhausted the string, if so it means the input string is the output string, so just return it
// without allocating a copy.
if (ReturnOriginalDACString(curPos, name.Length, nameSegments))
return name;
bool typeIsNestedClass = (parsingNestedClassDepth != 0);
if (parsingGenericArgListDepth == 0)
{
// We are parsing a top-level type name/sequence
EnsureNameSegmentList();
#pragma warning disable CS8602 // EnsureNameSegmentList call above ensures that nameSegments is never null here
nameSegments.Add(new TypeNameSegment(name, (start, curPos), typeIsNestedClass, parsingArgDepth: 0));
#pragma warning restore CS8602
}
else
{
// We are parsing a generic list (potentialy nested lists in the case where a generic param is itself generic)
EnsureGenericArgList();
#pragma warning disable CS8602 // EnsureGenericArgList call above ensures that genericArgs is never null here
genericArgs.Add(new TypeNameSegment(name, (start, curPos), typeIsNestedClass, parsingGenericArgListDepth));
#pragma warning restore CS8602
}
if (parsingNestedClassDepth != 0)
parsingNestedClassDepth--;
(currentState, curPos) = DetermineNextStateAndPos(name, curPos);
break;
}
case ParsingState.ParsingNestedClass:
{
// We are starting to parse a nested type name, just record the nested class depth (we have to handle multiple levels of nested classes), and
// transition back to the type name parsing state.
parsingNestedClassDepth++;
currentState = ParsingState.ParsingTypeName;
break;
}
case ParsingState.ParsingGenericArgCount:
{
// Parse the arity of the generic type. Note: we do this 'in place' i.e. without extracting the count substring, it's unfortunate but int.Parse does not include
// an overload that operates in place based on start/length.
int genericArgCount;
(genericArgCount, curPos) = ParseGenericArityCountFromStringInPlace(name, curPos);
List<TypeNameSegment>? targetList = ((genericArgs != null) && (genericArgs.Count != 0)) ? genericArgs : nameSegments;
if (targetList != null)
{
// NOTE: TypeNameSegment is a struct to avoid heap allocations, that means we have to extract / modify / re-store to ensure the updated state gets back into whatever
// list this came from.
int targetIndex = targetList.Count - 1;
TypeNameSegment seg = targetList[targetIndex];
seg.SetExpectedGenericArgCount(genericArgCount);
targetList[targetIndex] = seg;
}
else
{
currentState = ParsingState.Error;
break;
}
(currentState, curPos) = DetermineNextStateAndPos(name, curPos);
break;
}
case ParsingState.ParsingGenericArgAssemblySpecifier:
{
// Nothing to do here, really, just skip the assembly name specified in the generic arg type
while (curPos < name.Length && name[curPos] != ']')
curPos++;
(currentState, curPos) = DetermineNextStateAndPos(name, curPos);
break;
}
case ParsingState.ParsingGenericArgs:
{
// Start parsing the list of generic types, this just entails marking that we are parsing a generic arg list. NOTE: to support nested generic arg lsits we
// have to keep track of list count, not just a simple bool are/aren't parsing.
parsingGenericArgListDepth++;
currentState = ParsingState.ParsingTypeName;
break;
}
case ParsingState.ParsingArraySpecifier:
{
// Parse the array specifier, this mainly is to catch multi-dimensional arrays
// There is always at least a single dimenision in arrays, every comma counts as one more
int arrayDimensions = 1;
// Calculate the array dimensions
while ((curPos < name.Length) && (name[curPos] != ']'))
{
if (name[curPos] == ',')
arrayDimensions++;
curPos++;
}
// Consume the final ] of the array specifier, unless we are at the end of the string already
if (curPos != name.Length)
curPos++;
if (parsingGenericArgListDepth != 0 || nameSegments != null)
{
// NOTE: TypeNameSegment is a struct to avoid heap allocations, that means we have to extract / modify / re-store to ensure the updated state gets back into whatever
// list this came from.
List<TypeNameSegment>? targetList = parsingGenericArgListDepth != 0 ? genericArgs : nameSegments;
if (targetList != null)
{
int targetIndex = targetList.Count - 1;
TypeNameSegment targetSegment = targetList[targetIndex];
targetSegment.SetArrayDimensions(arrayDimensions);
targetList[targetIndex] = targetSegment;
}
else
{
currentState = ParsingState.Error;
break;
}
(currentState, curPos) = DetermineNextStateAndPos(name, curPos);
if (genericArgs == null || genericArgs.Count == 0 && currentState == ParsingState.Done)
{
// Special case: Return original string in cases like this:
//
// System.String[,,,] or System.Int32[][]
if (ReturnOriginalDACString(curPos, name.Length, nameSegments))
return name;
}
}
else
{
Debug.Fail("Inside ParsingArraySpecifier but we don't think we are parsing generic params and have nothing on the top-level name segment list.");
currentState = ParsingState.Error;
}
break;
}
case ParsingState.ResolveParsedGenericList:
{
// We are done with this level of arguments in terms of parsing, now we just have to apply them to the types they belong with (from previous parsing levels or the
// top-level).
parsingGenericArgListDepth--;
if (genericArgs == null || genericArgs.Count == 0)
{
// For top-level types with multiple-level generic arg lists (so a type with a generic arg which itself is a generic type) as we unwind the nested generic args
// lists we can end up wth no more work to do upon exiting a level (because we already propagated the info backwards before getting here), in which case, do nothing.
(currentState, curPos) = DetermineNextStateAndPos(name, curPos);
break;
}
(currentState, curPos) = ResolveParsedGenericList(name, curPos, parsingGenericArgListDepth, nameSegments, genericArgs);
break;
}
}
}
if (currentState == ParsingState.Error || nameSegments == null)
{
// If we have encountered something we failed on, return the DAC string so at least there is SOMETHING
Debug.WriteLine($"Failed Parsing DAC Name: {name}");
return name;
}
//Build the final result from all the type name segments we have
StringBuilder result = new StringBuilder();
foreach (TypeNameSegment segment in nameSegments)
segment.ToString(result);
return result.ToString();
}
catch (Exception e)
{
Debug.WriteLine($"Encountered an exception while parsing name {name}: {e}");
return name;
}
}