Skip to content

Optimize retrieval of simple name from Assembly.FullName, fix up faulty methods #9739

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Jan 22, 2025
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,9 @@
<Compile Include="$(WpfSharedDir)\MS\Internal\ResourceIDHelper.cs">
<Link>Shared\MS\Internal\ResourceIDHelper.cs</Link>
</Compile>
<Compile Include="$(WpfSharedDir)\MS\Internal\ReflectionUtils.cs">
<Link>Shared\MS\Internal\ReflectionUtils.cs</Link>
</Compile>
<Compile Include="$(WpfSharedDir)\MS\Internal\SecurityHelper.cs">
<Link>Shared\MS\Internal\SecurityHelper.cs</Link>
</Compile>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -355,8 +355,8 @@ private Stream GetCompositeFontResourceStream()
{
string fontFilename = _fontUri.OriginalString.Substring(_fontUri.OriginalString.LastIndexOf('/') + 1).ToLowerInvariant();

var fontResourceAssembly = Assembly.GetExecutingAssembly();
ResourceManager rm = new ResourceManager($"{fontResourceAssembly.GetName().Name}.g", fontResourceAssembly);
Assembly fontResourceAssembly = Assembly.GetExecutingAssembly();
ResourceManager rm = new($"{ReflectionUtils.GetAssemblyPartialName(fontResourceAssembly)}.g", fontResourceAssembly);

return rm?.GetStream($"fonts/{fontFilename}");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -232,11 +232,10 @@ private ResourceSet ResourceSet
{
if (_resourceSet == null)
{
string manifestResourceName;
//"$(AssemblyShortname).unlocalizable.g"
string manifestResourceName = $"{ReflectionUtils.GetAssemblyPartialName(_assembly)}{UnLocalizableResourceNameSuffix}";
ResourceManager manager = new(manifestResourceName, _assembly);

manifestResourceName = SafeSecurityHelper.GetAssemblyPartialName(_assembly) + UnLocalizableResourceNameSuffix;

ResourceManager manager = new ResourceManager(manifestResourceName, this._assembly);
_resourceSet = manager.GetResourceSet(CultureInfo.InvariantCulture, true, false);
}

Expand All @@ -254,11 +253,9 @@ private ResourceManager ResourceManager
{
if (_resourceManager == null)
{
string baseResourceName; // Our build system always generate a resource base name "$(AssemblyShortname).g"

baseResourceName = SafeSecurityHelper.GetAssemblyPartialName(_assembly) + LocalizableResourceNameSuffix;

_resourceManager = new ResourceManager(baseResourceName, this._assembly);
// Our build system always generate a resource base name "$(AssemblyShortname).g"
string baseResourceName = $"{ReflectionUtils.GetAssemblyPartialName(_assembly)}{LocalizableResourceNameSuffix}";
_resourceManager = new ResourceManager(baseResourceName, _assembly);
}

return _resourceManager;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using MS.Win32;
using System.Windows.Input;
using System.Reflection;
using MS.Internal;
using MS.Win32;

namespace System.Windows.Interop
{
Expand Down Expand Up @@ -418,7 +419,7 @@ internal static bool PlatformSupportsTransparentChildWindows
/// <remarks>Not intended to be tested outside test code</remarks>
internal static void SetPlatformSupportsTransparentChildWindowsForTestingOnly(bool value)
{
if (string.Equals(System.Reflection.Assembly.GetEntryAssembly().GetName().Name, "drthwndsource", StringComparison.CurrentCultureIgnoreCase))
if (ReflectionUtils.GetAssemblyPartialName(Assembly.GetEntryAssembly()).Equals("drthwndsource", StringComparison.CurrentCultureIgnoreCase))
{
_platformSupportsTransparentChildWindows = value;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -327,7 +327,7 @@ static internal bool IsComponentEntryAssembly(string component)

if (assembly != null)
{
return (string.Equals(SafeSecurityHelper.GetAssemblyPartialName(assembly), assemblyName, StringComparison.OrdinalIgnoreCase));
return ReflectionUtils.GetAssemblyPartialName(assembly).Equals(assemblyName, StringComparison.OrdinalIgnoreCase);
}
else
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,8 +117,8 @@ static DocumentsTrace()
public DocumentsTrace(string switchName)
{
#if DEBUG
string name = SafeSecurityHelper.GetAssemblyPartialName( Assembly.GetCallingAssembly() );
_switch = new BooleanSwitch(switchName, $"[{name}]");
ReadOnlySpan<char> shortAssemblyName = ReflectionUtils.GetAssemblyPartialName(Assembly.GetCallingAssembly());
_switch = new BooleanSwitch(switchName, $"[{shortAssemblyName}]");
#endif
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
using System.Globalization;
using XamlReaderHelper = System.Windows.Markup.XamlReaderHelper;
using System.Runtime.CompilerServices;
using MS.Internal;

namespace System.Windows.Baml2006
{
Expand Down Expand Up @@ -2105,11 +2106,9 @@ private string Logic_GetFullXmlns(string uriInput)

// Providing the assembly short name may lead to ambiguity between two versions of the same assembly, but we need to
// keep it this way since it is exposed publicly via the Namespace property, Baml2006ReaderInternal provides the full Assembly name.
// We need to avoid Assembly.GetName() so we run in PartialTrust without asserting.
internal virtual ReadOnlySpan<char> GetAssemblyNameForNamespace(Assembly assembly)
{
string assemblyLongName = assembly.FullName;
return assemblyLongName.AsSpan(0, assemblyLongName.IndexOf(','));
return ReflectionUtils.GetAssemblyPartialName(assembly);
}

// (prefix, namespaceUri)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,9 @@ internal Baml2006ReaderInternal(
#endregion

// Return the full assembly name, this includes the assembly version
internal override ReadOnlySpan<char> GetAssemblyNameForNamespace(Assembly asm)
internal override ReadOnlySpan<char> GetAssemblyNameForNamespace(Assembly assembly)
{
return asm.FullName;
return assembly.FullName;
}

// When processing ResourceDictionary.Source we may find a Uri that references the
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

//
// Contents: XAML writer
//

using System.Xml.Serialization;
using System.ComponentModel;
using System.Reflection;
using System.Collections;
using System.Reflection;
using MS.Internal;
using System.Text;
using System.Xml;
using System.Xml.Serialization;

namespace System.Windows.Markup.Primitives
{
Expand Down Expand Up @@ -1610,14 +1610,14 @@ public static string GetNamespaceUriFor(Type type)
{
if (type.Namespace == null)
{
result = $"{clrUriPrefix};assembly={type.Assembly.GetName().Name}";
result = $"{clrUriPrefix};assembly={ReflectionUtils.GetAssemblyPartialName(type.Assembly)}";
}
else
{
Dictionary<string, string> namespaceToUri = GetMappingsFor(type.Assembly);
if (!namespaceToUri.TryGetValue(type.Namespace, out result))
{
result = $"{clrUriPrefix}{type.Namespace};assembly={type.Assembly.GetName().Name}";
result = $"{clrUriPrefix}{type.Namespace};assembly={ReflectionUtils.GetAssemblyPartialName(type.Assembly)}";
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2481,7 +2481,7 @@ private static bool IsFriendAssembly(Assembly assembly)

private static bool IsInternalAllowedOnType(Type type)
{
bool isInternalAllowed = ReflectionHelper.LocalAssemblyName == type.Assembly.GetName().Name ||
bool isInternalAllowed = ReflectionHelper.LocalAssemblyName == ReflectionUtils.GetAssemblyPartialName(type.Assembly) ||
IsFriendAssembly(type.Assembly);
_hasInternals = _hasInternals || isInternalAllowed;
return isInternalAllowed;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -571,7 +571,7 @@ internal ResourceDictionaries(Assembly assembly)
}
else
{
_assemblyName = SafeSecurityHelper.GetAssemblyPartialName(assembly);
_assemblyName = ReflectionUtils.GetAssemblyPartialName(assembly).ToString();
}
}

Expand Down Expand Up @@ -786,7 +786,7 @@ private void LoadExternalAssembly(bool classic, bool generic, out Assembly assem
}

assemblyName = sb.ToString();
string fullName = SafeSecurityHelper.GetFullAssemblyNameFromPartialName(_assembly, assemblyName);
string fullName = ReflectionUtils.GetFullAssemblyNameFromPartialName(_assembly, assemblyName);

assembly = null;
try
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

#nullable enable

using System.Runtime.CompilerServices;
using System.Reflection.Metadata;
using System.Reflection;
using System;

namespace MS.Internal
{
/// <summary>
/// Provides utilities for working with reflection efficiently.
/// </summary>
internal static class ReflectionUtils
{
#if !NETFX
/// <summary>
/// Retrieves the full assembly name by combining the <paramref name="partialName"/> passed in
/// with everything else from <paramref name="assembly"/>.
/// </summary>
internal static string GetFullAssemblyNameFromPartialName(Assembly assembly, string partialName)
{
ArgumentNullException.ThrowIfNull(assembly, nameof(assembly));
string? fullName = assembly.FullName;
ArgumentNullException.ThrowIfNull(fullName, nameof(Assembly.FullName));

AssemblyName name = new(fullName) { Name = partialName };
return name.FullName;
}
#endif

/// <summary>
/// Given an <paramref name="assembly"/>, returns the partial/simple name of the assembly.
/// </summary>
#if !NETFX
internal static ReadOnlySpan<char> GetAssemblyPartialName(Assembly assembly)
#else
internal static string GetAssemblyPartialName(Assembly assembly)
#endif
{
#if !NETFX
ArgumentNullException.ThrowIfNull(assembly, nameof(assembly));
// We know that the input is trusted (it will be properly escaped, with ", " between tokens etc.)
// So we can allow ourselves to do a little trick, where we just find the first separator
// You cannot load an assembly (or define) where name is empty, it needs to be at least 1 character
// But we will keep this for consistency of the previous function, maybe I've missed a class
ReadOnlySpan<char> fullName = assembly.FullName;
if (fullName.IsEmpty)
return ReadOnlySpan<char>.Empty;

ReadOnlySpan<char> nameSlice = fullName;
// Skip any escaped commas in the name if present
int escapedComma = fullName.LastIndexOf("\\,", StringComparison.Ordinal);
if (escapedComma != -1)
nameSlice = nameSlice.Slice(escapedComma + 2);

// Find the real ending of the name section
int commaIndex = nameSlice.IndexOf(',');
if (commaIndex != -1)
fullName = fullName.Slice(0, fullName.Length - nameSlice.Length + commaIndex);

// Check if we need to unescape, this is very rare case so we can just do it the dirty way
if (escapedComma != -1 || fullName.Contains('\\'))
UnescapeDirty(ref fullName);

// Since having "," or "=" in the assembly name is very rare, we don't want to inline
// and we will fallback to the runtime implementation to handle such case for us
[MethodImpl(MethodImplOptions.NoInlining)]
static void UnescapeDirty(ref ReadOnlySpan<char> dirtyName)
{
dirtyName = !AssemblyNameInfo.TryParse(dirtyName, out AssemblyNameInfo? result) ? ReadOnlySpan<char>.Empty : result.Name;
}

return fullName;
#else
AssemblyName name = new(assembly.FullName);
return name.Name ?? string.Empty;
#endif
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -58,39 +58,7 @@ internal static void TransformLocalRectToScreen(HandleRef hwnd, ref NativeMethod
}
#endif

#if PRESENTATION_CORE || PRESENTATIONFRAMEWORK ||REACHFRAMEWORK || DEBUG

#if !WINDOWS_BASE && !SYSTEM_XAML
/// <summary>
/// Given an assembly, returns the partial name of the assembly.
/// </summary>
internal static string GetAssemblyPartialName(Assembly assembly)
{
AssemblyName name = new AssemblyName(assembly.FullName);
string partialName = name.Name;
return partialName ?? string.Empty;
}
#endif

#endif

#if PRESENTATIONFRAMEWORK

/// <summary>
/// Get the full assembly name by combining the partial name passed in
/// with everything else from proto assembly.
/// </summary>
internal static string GetFullAssemblyNameFromPartialName(
Assembly protoAssembly,
string partialName)
{
AssemblyName name = new AssemblyName(protoAssembly.FullName)
{
Name = partialName
};
return name.FullName;
}

internal static Point ClientToScreen(UIElement relativeTo, Point point)
{
GeneralTransform transform;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -504,7 +504,7 @@ internal static bool IsFriendAssembly(Assembly sourceAssembly)
#if PBTCOMPILER
internal static bool IsInternalAllowedOnType(Type type)
{
return ((LocalAssemblyName == type.Assembly.GetName().Name) || IsFriendAssembly(type.Assembly));
return LocalAssemblyName == ReflectionUtils.GetAssemblyPartialName(type.Assembly) || IsFriendAssembly(type.Assembly);
}
#endif

Expand Down
3 changes: 3 additions & 0 deletions src/Microsoft.DotNet.Wpf/src/System.Xaml/System.Xaml.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@
<Compile Include="$(WpfSharedDir)MS\Internal\Xaml\Parser\SpecialBracketCharacters.cs">
<Link>Common\WPF\MS\Internal\Xaml\Parser\SpecialBracketCharacters.cs</Link>
</Compile>
<Compile Include="$(WpfSharedDir)\MS\Internal\ReflectionUtils.cs">
<Link>Common\WPF\MS\Internal\ReflectionUtils.cs</Link>
</Compile>
<Compile Include="$(WpfSharedDir)MS\Internal\SafeSecurityHelper.cs">
<Link>Common\WPF\MS\Internal\SafeSecurityHelper.cs</Link>
</Compile>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
using System.Windows.Markup;
using System.Xaml.Schema;

using MS.Internal;

namespace System.Xaml.MS.Impl
{
class XmlNsInfo
Expand Down Expand Up @@ -223,8 +225,7 @@ ConcurrentDictionary<string, IList<string>> LoadClrToXmlNs()
xmlNamespaceList.Add(nsDef.XmlNamespace);
}

string assemblyName = _fullyQualifyAssemblyName ?
assembly.FullName : XamlSchemaContext.GetAssemblyShortName(assembly);
string assemblyName = _fullyQualifyAssemblyName ? assembly.FullName : ReflectionUtils.GetAssemblyPartialName(assembly).ToString();
foreach (KeyValuePair<string, IList<string>> clrToXmlNs in result)
{
// Sort namespaces in preference order
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using System.Collections.ObjectModel;
using System.Reflection;
using System.Text;
using MS.Internal;
using System.Threading;
using System.Xaml.MS.Impl;
using System.Xaml.Schema;
Expand Down Expand Up @@ -786,7 +787,6 @@ internal bool AreInternalsVisibleTo(Assembly fromAssembly, Assembly toAssembly)
return false;
}

// Not using Assembly.GetName() because it doesn't work in partial-trust
AssemblyName toAssemblyName = new AssemblyName(toAssembly.FullName);
foreach (AssemblyName friend in friends)
{
Expand Down Expand Up @@ -1042,8 +1042,7 @@ private ReadOnlyCollection<string> GetXmlNsMappings(Assembly assembly, string cl

if (!assemblyMappings.TryGetValue(clrNs, out result))
{
string assemblyName = FullyQualifyAssemblyNamesInClrNamespaces ?
assembly.FullName : GetAssemblyShortName(assembly);
string assemblyName = FullyQualifyAssemblyNamesInClrNamespaces ? assembly.FullName : ReflectionUtils.GetAssemblyPartialName(assembly).ToString();
string xmlns = ClrNamespaceUriParser.GetUri(clrNs, assemblyName);
List<string> list = new List<string>();
list.Add(xmlns);
Expand Down Expand Up @@ -1202,14 +1201,6 @@ bool UpdateNamespaceByUriList(XmlNsInfo nsInfo)

#region Helper Methods

// Given an assembly, return the assembly short name. We need to avoid Assembly.GetName() so we run in PartialTrust without asserting.
internal static string GetAssemblyShortName(Assembly assembly)
{
string assemblyLongName = assembly.FullName;
string assemblyShortName = assemblyLongName.Substring(0, assemblyLongName.IndexOf(','));
return assemblyShortName;
}

internal static ConcurrentDictionary<K, V> CreateDictionary<K, V>()
{
return new ConcurrentDictionary<K, V>(ConcurrencyLevel, DictionaryCapacity);
Expand Down
Loading