Skip to content
This repository was archived by the owner on Sep 26, 2024. It is now read-only.

Commit ca1728b

Browse files
authored
[Blazor] Avoids parsing type names and caching results which might cause null reference exception (#73)
* Capture the type containing namespace and the type name without generic arguments from the Roslyn symbols. * Save the type namespace and type name without generic arguments in the TagHelperDescriptor metadata. * Replace parsing logic with checks on the already computed metadata.
1 parent 080bb02 commit ca1728b

14 files changed

+245
-186
lines changed

src/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentDesignTimeNodeWriter.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -519,7 +519,7 @@ public override void WriteComponent(CodeRenderingContext context, ComponentInter
519519
if (!node.TagName.Contains("."))
520520
{
521521
// The tag is not fully qualified
522-
context.CodeWriter.Write(node.Component.ParsedTypeInfo.Value.Namespace);
522+
context.CodeWriter.Write(node.Component.GetTypeNamespace());
523523
context.CodeWriter.Write(".");
524524
}
525525
context.CodeWriter.Write(node.TagName);

src/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentLoweringPass.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ static TagHelperDescriptor GetTagHelperOrAddDiagnostic(TagHelperIntermediateNode
100100
for (var j = 0; j < usings.Count; j++)
101101
{
102102
var usingNamespace = usings[j].Content;
103-
if (string.Equals(tagHelper.ParsedTypeInfo.Value.Namespace.Value, usingNamespace, StringComparison.Ordinal))
103+
if (string.Equals(tagHelper.GetTypeNamespace(), usingNamespace, StringComparison.Ordinal))
104104
{
105105
if (candidate == null)
106106
{

src/Microsoft.AspNetCore.Razor.Language/src/DefaultRazorTagHelperBinderPhase.cs

Lines changed: 11 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -265,8 +265,7 @@ public ComponentDirectiveVisitor(string filePath, IReadOnlyList<TagHelperDescrip
265265
{
266266
// If this is a child content tag helper, we want to add it if it's original type is in scope.
267267
// E.g, if the type name is `Test.MyComponent.ChildContent`, we want to add it if `Test.MyComponent` is in scope.
268-
TrySplitNamespaceAndType(tagHelper, out var typeNamespace);
269-
if (!typeNamespace.IsEmpty && IsTypeInScope(typeNamespace, currentNamespace))
268+
if (IsTypeInScope(tagHelper, currentNamespace))
270269
{
271270
Matches.Add(tagHelper);
272271
}
@@ -352,8 +351,7 @@ public override void VisitRazorDirective(RazorDirectiveSyntax node)
352351
{
353352
// If this is a child content tag helper, we want to add it if it's original type is in scope of the given namespace.
354353
// E.g, if the type name is `Test.MyComponent.ChildContent`, we want to add it if `Test.MyComponent` is in this namespace.
355-
TrySplitNamespaceAndType(tagHelper, out var typeName);
356-
if (!typeName.IsEmpty && IsTypeInNamespace(typeName, @namespace))
354+
if (IsTypeInNamespace(tagHelper, @namespace))
357355
{
358356
Matches.Add(tagHelper);
359357
}
@@ -368,26 +366,15 @@ public override void VisitRazorDirective(RazorDirectiveSyntax node)
368366
}
369367
}
370368

371-
internal static bool IsTypeInNamespace(StringSegment typeName, string @namespace)
372-
{
373-
if (!TrySplitNamespaceAndType(typeName, out var typeNamespace, out var _) || typeNamespace.IsEmpty)
374-
{
375-
// Either the typeName is not the full type name or this type is at the top level.
376-
return true;
377-
}
378-
379-
return typeNamespace.Equals(@namespace, StringComparison.Ordinal);
380-
}
381-
382369
internal static bool IsTypeInNamespace(TagHelperDescriptor tagHelper, string @namespace)
383370
{
384-
if (!TrySplitNamespaceAndType(tagHelper, out var typeNamespace) || typeNamespace.IsEmpty)
371+
return IsTypeInNamespace(tagHelper.GetTypeNamespace(), @namespace);
372+
373+
static bool IsTypeInNamespace(string typeNamespace, string @namespace)
385374
{
386375
// Either the typeName is not the full type name or this type is at the top level.
387-
return true;
376+
return string.IsNullOrEmpty(typeNamespace) || typeNamespace.Equals(@namespace, StringComparison.Ordinal);
388377
}
389-
390-
return typeNamespace.Equals(@namespace, StringComparison.Ordinal);
391378
}
392379

393380
// Check if the given type is already in scope given the namespace of the current document.
@@ -397,34 +384,18 @@ internal static bool IsTypeInNamespace(TagHelperDescriptor tagHelper, string @na
397384
// Whereas `MyComponents.SomethingElse.OtherComponent` is not in scope.
398385
internal static bool IsTypeInScope(TagHelperDescriptor descriptor, string currentNamespace)
399386
{
400-
if (!TrySplitNamespaceAndType(descriptor, out var typeNamespace, out _) || typeNamespace.IsEmpty)
401-
{
402-
// Either the typeName is not the full type name or this type is at the top level.
403-
return true;
404-
}
405-
406-
return IsTypeInScopeCore(currentNamespace, typeNamespace);
387+
return IsTypeInScopeCore(currentNamespace, descriptor.GetTypeNamespace());
407388
}
408389

409-
// Check if the given type is already in scope given the namespace of the current document.
410-
// E.g,
411-
// If the namespace of the document is `MyComponents.Components.Shared`,
412-
// then the types `MyComponents.FooComponent`, `MyComponents.Components.BarComponent`, `MyComponents.Components.Shared.BazComponent` are all in scope.
413-
// Whereas `MyComponents.SomethingElse.OtherComponent` is not in scope.
414-
internal static bool IsTypeInScope(StringSegment typeName, string currentNamespace)
390+
private static bool IsTypeInScopeCore(string currentNamespace, string typeNamespace)
415391
{
416-
if (!TrySplitNamespaceAndType(typeName, out var typeNamespace, out _) || typeNamespace.IsEmpty)
392+
if (string.IsNullOrEmpty(typeNamespace))
417393
{
418394
// Either the typeName is not the full type name or this type is at the top level.
419395
return true;
420396
}
421397

422-
return IsTypeInScopeCore(currentNamespace, typeNamespace);
423-
}
424-
425-
private static bool IsTypeInScopeCore(string currentNamespace, StringSegment typeNamespace)
426-
{
427-
if (!new StringSegment(currentNamespace).StartsWith(typeNamespace, StringComparison.Ordinal))
398+
if (!currentNamespace.StartsWith(typeNamespace, StringComparison.Ordinal))
428399
{
429400
// typeName: MyComponents.Shared.SomeCoolNamespace
430401
// currentNamespace: MyComponents.Shared
@@ -445,83 +416,7 @@ private static bool IsTypeInScopeCore(string currentNamespace, StringSegment typ
445416
// open file in the editor. We mangle the class name for its generated code, so using that here to filter these out.
446417
internal static bool IsTagHelperFromMangledClass(TagHelperDescriptor tagHelper)
447418
{
448-
StringSegment className;
449-
if (tagHelper.IsChildContentTagHelper())
450-
{
451-
// If this is a child content tag helper, we want to look at it's original type.
452-
// E.g, if the type name is `Test.__generated__MyComponent.ChildContent`, we want to look at `Test.__generated__MyComponent`.
453-
TrySplitNamespaceAndType(tagHelper, out var typeNamespace);
454-
return TrySplitNamespaceAndType(typeNamespace, out var _, out className)
455-
&& ComponentMetadata.IsMangledClass(className);
456-
}
457-
458-
return TrySplitNamespaceAndType(tagHelper, out var _, out className) &&
459-
ComponentMetadata.IsMangledClass(className);
460-
}
461-
462-
internal static bool TrySplitNamespaceAndType(TagHelperDescriptor tagHelperDescriptor, out StringSegment @namespace)
463-
=> TrySplitNamespaceAndType(tagHelperDescriptor, out @namespace, out _);
464-
465-
internal static bool TrySplitNamespaceAndType(TagHelperDescriptor tagHelperDescriptor, out StringSegment @namespace, out StringSegment typeName)
466-
{
467-
if (tagHelperDescriptor.ParsedTypeInfo is { } value)
468-
{
469-
@namespace = value.Namespace;
470-
typeName = value.TypeName;
471-
return value.Success;
472-
}
473-
474-
var success = TrySplitNamespaceAndType(tagHelperDescriptor.GetTypeName(), out @namespace, out typeName);
475-
tagHelperDescriptor.ParsedTypeInfo = new(success, @namespace, typeName);
476-
return success;
477-
}
478-
479-
// Internal for testing.
480-
internal static bool TrySplitNamespaceAndType(StringSegment fullTypeName, out StringSegment @namespace, out StringSegment typeName)
481-
{
482-
@namespace = StringSegment.Empty;
483-
typeName = StringSegment.Empty;
484-
485-
if (fullTypeName.IsEmpty)
486-
{
487-
return false;
488-
}
489-
490-
var nestingLevel = 0;
491-
var splitLocation = -1;
492-
for (var i = fullTypeName.Length - 1; i >= 0; i--)
493-
{
494-
var c = fullTypeName[i];
495-
if (c == Type.Delimiter && nestingLevel == 0)
496-
{
497-
splitLocation = i;
498-
break;
499-
}
500-
else if (c == '>')
501-
{
502-
nestingLevel++;
503-
}
504-
else if (c == '<')
505-
{
506-
nestingLevel--;
507-
}
508-
}
509-
510-
if (splitLocation == -1)
511-
{
512-
typeName = fullTypeName;
513-
return true;
514-
}
515-
516-
@namespace = fullTypeName.Subsegment(0, splitLocation);
517-
518-
var typeNameStartLocation = splitLocation + 1;
519-
if (typeNameStartLocation < fullTypeName.Length)
520-
{
521-
typeName = fullTypeName.Subsegment(typeNameStartLocation, fullTypeName.Length - typeNameStartLocation);
522-
}
523-
524-
return true;
419+
return ComponentMetadata.IsMangledClass(tagHelper.GetTypeNameIdentifier());
525420
}
526421
}
527422
}

src/Microsoft.AspNetCore.Razor.Language/src/TagHelperDescriptor.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ protected TagHelperDescriptor(string kind)
4747
private int? _hashCode;
4848
internal bool? IsComponentFullyQualifiedNameMatchCache { get; set; }
4949
internal bool? IsChildContentTagHelperCache { get; set; }
50-
internal ParsedTypeInformation? ParsedTypeInfo { get; set; }
50+
5151
internal BoundAttributeDescriptor[] EditorRequiredAttributes
5252
{
5353
get

src/Microsoft.AspNetCore.Razor.Language/src/TagHelperDescriptorBuilderExtensions.cs

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Licensed to the .NET Foundation under one or more agreements.
1+
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
#nullable disable
@@ -9,6 +9,36 @@ namespace Microsoft.AspNetCore.Razor.Language;
99

1010
public static class TagHelperDescriptorBuilderExtensions
1111
{
12+
public static void SetTypeNamespace(this TagHelperDescriptorBuilder builder, string typeNamespace)
13+
{
14+
if (builder == null)
15+
{
16+
throw new ArgumentNullException(nameof(builder));
17+
}
18+
19+
if (typeNamespace == null)
20+
{
21+
throw new ArgumentNullException(nameof(typeNamespace));
22+
}
23+
24+
builder.Metadata[TagHelperMetadata.Common.TypeNamespace] = typeNamespace;
25+
}
26+
27+
public static void SetTypeNameIdentifier(this TagHelperDescriptorBuilder builder, string typeNameIdentifier)
28+
{
29+
if (builder == null)
30+
{
31+
throw new ArgumentNullException(nameof(builder));
32+
}
33+
34+
if (typeNameIdentifier == null)
35+
{
36+
throw new ArgumentNullException(nameof(typeNameIdentifier));
37+
}
38+
39+
builder.Metadata[TagHelperMetadata.Common.TypeNameIdentifier] = typeNameIdentifier;
40+
}
41+
1242
public static void SetTypeName(this TagHelperDescriptorBuilder builder, string typeName)
1343
{
1444
if (builder == null)

src/Microsoft.AspNetCore.Razor.Language/src/TagHelperDescriptorExtensions.cs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,28 @@ public static string GetTypeName(this TagHelperDescriptor tagHelper)
2121
return typeName;
2222
}
2323

24+
public static string GetTypeNamespace(this TagHelperDescriptor tagHelper)
25+
{
26+
if (tagHelper == null)
27+
{
28+
throw new ArgumentNullException(nameof(tagHelper));
29+
}
30+
31+
tagHelper.Metadata.TryGetValue(TagHelperMetadata.Common.TypeNamespace, out var typeNamespace);
32+
return typeNamespace;
33+
}
34+
35+
public static string GetTypeNameIdentifier(this TagHelperDescriptor tagHelper)
36+
{
37+
if (tagHelper == null)
38+
{
39+
throw new ArgumentNullException(nameof(tagHelper));
40+
}
41+
42+
tagHelper.Metadata.TryGetValue(TagHelperMetadata.Common.TypeNameIdentifier, out var typeNameIdentifier);
43+
return typeNameIdentifier;
44+
}
45+
2446
public static bool IsDefaultKind(this TagHelperDescriptor tagHelper)
2547
{
2648
if (tagHelper == null)

src/Microsoft.AspNetCore.Razor.Language/src/TagHelperMetadata.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ public static class Common
1313

1414
public static readonly string TypeName = "Common.TypeName";
1515

16+
public static readonly string TypeNamespace = "Common.TypeNamespace";
17+
18+
public static readonly string TypeNameIdentifier = "Common.TypeNameIdentifier";
19+
1620
public static readonly string GloballyQualifiedTypeName = "Common.GloballyQualifiedTypeName";
1721

1822
public static readonly string ClassifyAttributesOnly = "Common.ClassifyAttributesOnly";

0 commit comments

Comments
 (0)