CrackTemplate: recognize dotnet-generated names

This commit is contained in:
Dan Thompson (SBS) 2019-04-25 09:28:23 -07:00 коммит произвёл Dan Thompson
Родитель 006de74f54
Коммит 9b4149690a
2 изменённых файлов: 128 добавлений и 1 удалений

Просмотреть файл

@ -236,10 +236,100 @@ namespace MS.Dbg
if( ')' == name[ name.Length - 1 ] )
return false;
if( _LooksLikeDotNetGeneratedName( name ) )
return false;
return _LooksLikeATemplateNameInner( name, startIdx, ref angleBracketIdx );
}
private static bool _LooksLikeDotNetGeneratedName( string name )
{
// See roslyn/src/Compilers/CSharp/Portable/Symbols/Synthesized/GeneratedNames.cs
// https://github.com/dotnet/roslyn/blob/master/src/Compilers/CSharp/Portable/Symbols/Synthesized/GeneratedNames.cs
//
// This looks like something that could be done handily with a regex, but I
// really don't want to spin up regex here; this stuff needs to be as quick as
// possible. So below is a simple, hand-rolled state machine. We work
// backwards from the end of the string. The states map to the parts of a
// dotnet-generated name like so:
//
// "Blahblahblahblah<MaybeStuffHere>X__ABCD"
// /// \__//
// state_singleLetterOrDigit---/// / /
// state_underscore2---// / /
// state_underscore1---/ / /
// state_identifier---/ /
// state_start---/
//
const int state_start = 0;
const int state_identifier = 1;
const int state_underscore1 = 2;
const int state_underscore2 = 3;
const int state_singleLetterOrDigit = 4;
// No "invalid" state: we just directly return false.
int state = state_start;
int idx = name.Length;
// The minimum length of such a name is 7: "A<>B__C"
// So if we get to index 1 before finding the '>', we can stop.
if( idx < 7 )
return false;
while( --idx > 1 )
{
char c = name[ idx ];
switch( state )
{
case state_start:
if( Char.IsLetterOrDigit( c ) )
state = state_identifier;
else
return false;
break;
case state_identifier:
if( c == '_' )
state = state_underscore1;
else if( !Char.IsLetterOrDigit( c ) )
return false;
break;
case state_underscore1:
if( c == '_' )
state = state_underscore2;
else
return false;
break;
case state_underscore2:
if( Char.IsLetterOrDigit( c ) )
state = state_singleLetterOrDigit;
else
return false;
break;
case state_singleLetterOrDigit:
if( c == '>' )
return true;
else
return false;
default:
throw new Exception( "unexpected: invalid state" );
}
}
return false;
} // end _LooksLikeDotNetGeneratedName()
// Takes care of checks that we need to do repeatedly, after the one-time checks
// in the outer method.
private static bool _LooksLikeATemplateNameInner( string name,
@ -438,7 +528,7 @@ namespace MS.Dbg
}
else
{
problem = Util.Sprintf( "Unexpected characters at position {0}.", idx );
problem = Util.Sprintf( "Unexpected characters at position {0}.", idx + 1 );
return false;
}

Просмотреть файл

@ -195,6 +195,43 @@ Describe "TemplateMatching" {
$ti1.Parameters[ 0 ].FullName | Should Be '<lambda_fcd0511faf603cca0b560671c4e52d53>'
}
It "can deal with non-template dotnet names" {
# The CLR uses brackets to make types not usable in C#; it's not a template.
$typeName1 = 'MS.Dbg.IMAGEHLP_MODULEW64+<LoadedImageName>e__FixedBuffer'
$ti1 = [MS.Dbg.DbgTemplateNode]::CrackTemplate( $typeName1 )
$ti1.IsTemplate | Should Be $false
$ti1.FullName | Should Be $typeName1
$ti1.TemplateName | Should Be $typeName1
([MS.Dbg.DbgTemplateNode]::LooksLikeATemplateName( $typeName1 )) | Should Be $false
# This is a "fake" name (not observed in the wild; it's the same as the previous
# one but with the content between the angle brackets removed), but according to
# dotnet source, this sort of name should be possible.
$typeName1 = 'MS.Dbg.IMAGEHLP_MODULEW64+<>e__FixedBuffer'
$ti1 = [MS.Dbg.DbgTemplateNode]::CrackTemplate( $typeName1 )
$ti1.IsTemplate | Should Be $false
$ti1.FullName | Should Be $typeName1
$ti1.TemplateName | Should Be $typeName1
([MS.Dbg.DbgTemplateNode]::LooksLikeATemplateName( $typeName1 )) | Should Be $false
# Minimal archetypal dotnet generated name:
$typeName1 = 'A<>9__X'
$ti1 = [MS.Dbg.DbgTemplateNode]::CrackTemplate( $typeName1 )
$ti1.IsTemplate | Should Be $false
$ti1.FullName | Should Be $typeName1
$ti1.TemplateName | Should Be $typeName1
([MS.Dbg.DbgTemplateNode]::LooksLikeATemplateName( $typeName1 )) | Should Be $false
# now with something inside the brackets
$typeName1 = 'A<B>9__X'
$ti1 = [MS.Dbg.DbgTemplateNode]::CrackTemplate( $typeName1 )
$ti1.IsTemplate | Should Be $false
$ti1.FullName | Should Be $typeName1
$ti1.TemplateName | Should Be $typeName1
([MS.Dbg.DbgTemplateNode]::LooksLikeATemplateName( $typeName1 )) | Should Be $false
}
It "throws if the multi-match wildcard doesn't come last" {
[bool] $itThrew = $false