Skip to content

Commit 302c11b

Browse files
fail on collision
1 parent 6372d0b commit 302c11b

9 files changed

+146
-35
lines changed

src/Controls/src/Build.Tasks/TypeReferenceExtensions.cs

Lines changed: 30 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -9,23 +9,14 @@ namespace Microsoft.Maui.Controls.Build.Tasks
99
{
1010
class TypeRefComparer : IEqualityComparer<TypeReference>
1111
{
12-
static string GetAssemblyName(TypeReference typeRef)
13-
{
14-
if (typeRef.Scope is ModuleDefinition md)
15-
return md.Assembly.Name.Name;
16-
if (typeRef.Scope is AssemblyNameReference anr)
17-
return anr.Name;
18-
throw new ArgumentOutOfRangeException(nameof(typeRef));
19-
}
20-
2112
public bool Equals(TypeReference x, TypeReference y)
2213
{
23-
if (x == null)
24-
return y == null;
25-
if (y == null)
26-
return x == null;
14+
if (x == null && y == null)
15+
return true;
16+
if (x == null || y == null)
17+
return false;
2718

28-
//strip the leading `&` as byref typered fullnames have a `&`
19+
//strip the leading `&` as byref typeref fullnames have a `&`
2920
var xname = x.FullName.EndsWith("&", StringComparison.InvariantCulture) ? x.FullName.Substring(0, x.FullName.Length - 1) : x.FullName;
3021
var yname = y.FullName.EndsWith("&", StringComparison.InvariantCulture) ? y.FullName.Substring(0, y.FullName.Length - 1) : y.FullName;
3122
if (xname != yname)
@@ -34,27 +25,40 @@ public bool Equals(TypeReference x, TypeReference y)
3425
var yasm = GetAssemblyName(y);
3526

3627
//standard types comes from either mscorlib. System.Runtime or netstandard. Assume they are equivalent
37-
if ((xasm.StartsWith("System.Runtime", StringComparison.Ordinal)
38-
|| xasm.StartsWith("System", StringComparison.Ordinal)
39-
|| xasm.StartsWith("mscorlib", StringComparison.Ordinal)
40-
|| xasm.StartsWith("netstandard", StringComparison.Ordinal)
41-
|| xasm.StartsWith("System.Xml", StringComparison.Ordinal))
42-
&& (yasm.StartsWith("System.Runtime", StringComparison.Ordinal)
43-
|| yasm.StartsWith("System", StringComparison.Ordinal)
44-
|| yasm.StartsWith("mscorlib", StringComparison.Ordinal)
45-
|| yasm.StartsWith("netstandard", StringComparison.Ordinal)
46-
|| yasm.StartsWith("System.Xml", StringComparison.Ordinal)))
28+
if (IsSystemAssemvly(xasm) && IsSystemAssemvly(yasm))
4729
return true;
4830
return xasm == yasm;
4931
}
5032

5133
public int GetHashCode(TypeReference obj)
5234
{
53-
return $"{GetAssemblyName(obj)}//{obj.FullName}".GetHashCode();
35+
var assemblyName = GetAssemblyName(obj);
36+
if (IsSystemAssemvly(assemblyName))
37+
return obj.FullName.GetHashCode();
38+
return assemblyName.GetHashCode() ^ obj.FullName.GetHashCode();
39+
}
40+
41+
static string GetAssemblyName(TypeReference typeRef)
42+
{
43+
if (typeRef.Scope is ModuleDefinition md)
44+
return md.Assembly.Name.Name;
45+
if (typeRef.Scope is AssemblyNameReference anr)
46+
return anr.Name;
47+
throw new ArgumentOutOfRangeException(nameof(typeRef));
48+
}
49+
50+
bool IsSystemAssemvly(string assemblyName)
51+
{
52+
return assemblyName.StartsWith("System", StringComparison.Ordinal)
53+
|| assemblyName.StartsWith("mscorlib", StringComparison.Ordinal)
54+
|| assemblyName.StartsWith("netstandard", StringComparison.Ordinal)
55+
|| assemblyName.StartsWith("System.Runtime", StringComparison.Ordinal)
56+
|| assemblyName.StartsWith("System.Xml", StringComparison.Ordinal)
57+
|| assemblyName.StartsWith("System.Private.CoreLib", StringComparison.Ordinal);
5458
}
5559

5660
static TypeRefComparer s_default;
57-
public static TypeRefComparer Default => s_default ?? (s_default = new TypeRefComparer());
61+
public static TypeRefComparer Default => s_default ??= new TypeRefComparer();
5862
}
5963

6064
static class TypeReferenceExtensions

src/Controls/src/Build.Tasks/XmlTypeExtensions.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ public static bool TryGetTypeReference(this XmlType xmlType, XamlCache cache, Mo
105105

106106
var typeArguments = xmlType.TypeArguments;
107107

108-
TypeReference type = xmlType.GetTypeReference(xmlnsDefinitions, module.Assembly.Name.Name, (typeInfo) =>
108+
IEnumerable<TypeReference> types = xmlType.GetTypeReferences(xmlnsDefinitions, module.Assembly.Name.Name, (typeInfo) =>
109109
{
110110
if (typeInfo.clrNamespace.StartsWith("http")) //aggregated xmlns, might result in a typeload exception
111111
return null;
@@ -116,6 +116,13 @@ public static bool TryGetTypeReference(this XmlType xmlType, XamlCache cache, Mo
116116
return null;
117117
}, expandToExtension: expandToExtension);
118118

119+
if (types.Distinct(TypeRefComparer.Default).Skip(1).Any())
120+
{
121+
typeReference = null;
122+
return false;
123+
}
124+
125+
var type = types.Distinct(TypeRefComparer.Default).FirstOrDefault();
119126
if (type != null && typeArguments != null && type.HasGenericParameters)
120127
type = module.ImportReference(type).MakeGenericInstanceType(typeArguments.Select(x => x.GetTypeReference(cache, module, xmlInfo)).ToArray());
121128

src/Controls/src/SourceGen/CodeBehindGenerator.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,7 @@ static string EscapeIdentifier(string identifier)
172172

173173
var nsmgr = new XmlNamespaceManager(new NameTable());
174174
nsmgr.AddNamespace("__f__", XamlParser.MauiUri);
175+
nsmgr.AddNamespace("__g__", XamlParser.MauiGlobalUri);
175176
if (allowImplicitXmlns)
176177
{
177178
nsmgr.AddNamespace("", XamlParser.DefaultImplicitUri);
@@ -564,7 +565,8 @@ static bool GetXamlCompilationProcessingInstruction(XmlDocument xmlDoc)
564565
XmlNodeList names =
565566
root.SelectNodes(
566567
"//*[@" + xPrefix + ":Name" +
567-
"][not(ancestor:: __f__:DataTemplate) and not(ancestor:: __f__:ControlTemplate) and not(ancestor:: __f__:Style) and not(ancestor:: __f__:VisualStateManager.VisualStateGroups)]", nsmgr);
568+
"][not(ancestor:: __f__:DataTemplate) and not(ancestor:: __f__:ControlTemplate) and not(ancestor:: __f__:Style) and not(ancestor:: __f__:VisualStateManager.VisualStateGroups)" +
569+
"and not(ancestor:: __g__:DataTemplate) and not(ancestor:: __g__:ControlTemplate) and not(ancestor:: __g__:Style) and not(ancestor:: __g__:VisualStateManager.VisualStateGroups)]", nsmgr);
568570
foreach (XmlNode node in names)
569571
{
570572
cancellationToken.ThrowIfCancellationRequested();

src/Controls/src/Xaml/XamlParser.cs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -404,7 +404,7 @@ public static Type GetElementType(XmlType xmlType, IXmlLineInfo xmlInfo, Assembl
404404
if (s_xmlnsDefinitions == null)
405405
GatherXmlnsDefinitionAttributes(currentAssembly);
406406

407-
Type type = xmlType.GetTypeReference(
407+
IEnumerable<Type> types = xmlType.GetTypeReferences(
408408
s_xmlnsDefinitions,
409409
currentAssembly?.FullName,
410410
(typeInfo) =>
@@ -419,7 +419,7 @@ public static Type GetElementType(XmlType xmlType, IXmlLineInfo xmlInfo, Assembl
419419
var typeArguments = xmlType.TypeArguments;
420420
exception = null;
421421

422-
if (type == null)
422+
if (!types.Any())
423423
{
424424
// This covers the scenario where the AppDomain's loaded
425425
// assemblies might have changed since this method was first
@@ -433,6 +433,14 @@ public static Type GetElementType(XmlType xmlType, IXmlLineInfo xmlInfo, Assembl
433433
}
434434
}
435435

436+
if (types.Distinct().Skip(1).Any())
437+
{
438+
exception = new XamlParseException($"Ambiguous type '{xmlType.Name}' in xmlns '{xmlType.NamespaceUri}'", xmlInfo);
439+
return null;
440+
}
441+
442+
var type = types.Distinct().FirstOrDefault();
443+
436444
if (type != null && typeArguments != null)
437445
{
438446
XamlParseException innerexception = null;

src/Controls/src/Xaml/XmlTypeXamlExtensions.cs

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,22 @@
2727
// THE SOFTWARE.
2828

2929
using System;
30+
using System.Linq;
3031
using System.Collections.Generic;
3132

3233
namespace Microsoft.Maui.Controls.Xaml
3334
{
3435
static class XmlTypeXamlExtensions
3536
{
3637
public static T? GetTypeReference<T>(
38+
this XmlType xmlType,
39+
IEnumerable<XmlnsDefinitionAttribute> xmlnsDefinitions,
40+
string defaultAssemblyName,
41+
Func<(string typeName, string clrNamespace, string assemblyName), T> refFromTypeInfo,
42+
bool expandToExtension = true)
43+
where T : class => GetTypeReferences(xmlType, xmlnsDefinitions, defaultAssemblyName, refFromTypeInfo, expandToExtension).FirstOrDefault();
44+
45+
public static IEnumerable<T> GetTypeReferences<T>(
3746
this XmlType xmlType,
3847
IEnumerable<XmlnsDefinitionAttribute> xmlnsDefinitions,
3948
string defaultAssemblyName,
@@ -94,11 +103,14 @@ static class XmlTypeXamlExtensions
94103
}
95104

96105
T? type = null;
106+
string? returnTypeName = null;
97107
foreach (var typeInfo in potentialTypes)
98-
if ((type = refFromTypeInfo(typeInfo)) != null)
99-
break;
100-
101-
return type;
108+
if ( (returnTypeName == null || returnTypeName == typeInfo.typeName) //only return multiple types if they share the same name. avoid returning both BindingExtension and Binding
109+
&& (type = refFromTypeInfo(typeInfo)) != null)
110+
{
111+
returnTypeName = typeInfo.typeName;
112+
yield return type;
113+
}
102114
}
103115
}
104116
}

src/Controls/tests/Xaml.UnitTests/Issues/Unreported003.xaml.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ class Tests
2323
[TestCase(true), TestCase(false)]
2424
public void AllowCtorArgsForValueTypes(bool useCompiledXaml)
2525
{
26+
if (useCompiledXaml)
27+
MockCompiler.Compile(typeof(Unreported003));
28+
2629
var page = new Unreported003(useCompiledXaml);
2730
}
2831
}

src/Controls/tests/Xaml.UnitTests/XmlnsAggregattion.xaml.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77

88
namespace Microsoft.Maui.Controls.Xaml.UnitTests;
99

10-
// [XamlCompilation(XamlCompilationOptions.Skip)]
1110
public partial class XmlnsAggregattion : ContentPage
1211
{
1312
public XmlnsAggregattion() => InitializeComponent();
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?xml version="1.0" encoding="utf-8" ?>
2+
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/maui/global"
3+
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
4+
x:Class="Microsoft.Maui.Controls.Xaml.UnitTests.XmlnsCollision"
5+
Title="XmlnsCollision">
6+
<ConflictingLabel
7+
Text="Welcome to .NET MAUI!"
8+
VerticalOptions="Center"
9+
HorizontalOptions="Center" />
10+
</ContentPage>
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
using Microsoft.Maui.ApplicationModel;
2+
using Microsoft.Maui.Controls;
3+
using Microsoft.Maui.Controls.Build.Tasks;
4+
using Microsoft.Maui.Controls.Core.UnitTests;
5+
using NUnit.Framework;
6+
7+
[assembly: XmlnsDefinition("http://companyone.com/schemas/toolkit", "CompanyOne.Controls")]
8+
[assembly:XmlnsPrefix("c1", "http://companyone.com/schemas/toolkit")]
9+
[assembly: XmlnsDefinition("http://companytwo.com/schemas/toolkit", "CompanyTwo.Controls")]
10+
[assembly: XmlnsPrefix("c2", "http://companytwo.com/schemas/toolkit")]
11+
[assembly: XmlnsDefinition("http://schemas.microsoft.com/dotnet/maui/global", "http://companyone.com/schemas/toolkit")]
12+
[assembly: XmlnsDefinition("http://schemas.microsoft.com/dotnet/maui/global", "http://companytwo.com/schemas/toolkit")]
13+
14+
namespace CompanyOne.Controls
15+
{
16+
public class ConflictingLabel : Label
17+
{
18+
}
19+
}
20+
21+
namespace CompanyTwo.Controls
22+
{
23+
public class ConflictingLabel : Label
24+
{
25+
}
26+
}
27+
28+
namespace Microsoft.Maui.Controls.Xaml.UnitTests
29+
{
30+
[XamlCompilation(XamlCompilationOptions.Skip)]
31+
public partial class XmlnsCollision : ContentPage
32+
{
33+
public XmlnsCollision()
34+
{
35+
InitializeComponent();
36+
}
37+
public XmlnsCollision(bool useCompiledXaml)
38+
{
39+
//this stub will be replaced at compile time
40+
}
41+
42+
[TestFixture]
43+
class Test
44+
{
45+
[SetUp] public void Setup() => AppInfo.SetCurrent(new MockAppInfo());
46+
[TearDown] public void TearDown() => AppInfo.SetCurrent(null);
47+
48+
[Test]
49+
public void ConflictInXmlns([Values] bool useCompiledXaml)
50+
{
51+
if (useCompiledXaml)
52+
Assert.Throws<BuildException>(() =>
53+
{
54+
MockCompiler.Compile(typeof(XmlnsCollision), out var hasLoggedErrors);
55+
Assert.IsTrue(hasLoggedErrors);
56+
});
57+
58+
else
59+
Assert.Throws<XamlParseException>(() =>
60+
{
61+
var layout = new XmlnsCollision(useCompiledXaml);
62+
});
63+
}
64+
}
65+
}
66+
}

0 commit comments

Comments
 (0)