Browse Source

Merge pull request #10496 from Gillibald/feature/assetLoaderTryPattern

Prevent AssetLoader.GetAssets from crashing for missing assemblies
pull/10523/head
Benedikt Stebner 3 years ago
committed by GitHub
parent
commit
b520f31e3c
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 155
      src/Avalonia.Base/Platform/AssetLoader.cs
  2. 13
      tests/Avalonia.Base.UnitTests/AssetLoaderTests.cs

155
src/Avalonia.Base/Platform/AssetLoader.cs

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
@ -62,7 +63,7 @@ namespace Avalonia.Platform
/// <returns>True if the asset could be found; otherwise false.</returns> /// <returns>True if the asset could be found; otherwise false.</returns>
public bool Exists(Uri uri, Uri? baseUri = null) public bool Exists(Uri uri, Uri? baseUri = null)
{ {
return GetAsset(uri, baseUri) != null; return TryGetAsset(uri, baseUri, out _);
} }
/// <summary> /// <summary>
@ -94,21 +95,27 @@ namespace Avalonia.Platform
/// </exception> /// </exception>
public (Stream stream, Assembly assembly) OpenAndGetAssembly(Uri uri, Uri? baseUri = null) public (Stream stream, Assembly assembly) OpenAndGetAssembly(Uri uri, Uri? baseUri = null)
{ {
var asset = GetAsset(uri, baseUri); if (TryGetAsset(uri, baseUri, out var assetDescriptor))
if (asset == null)
{ {
throw new FileNotFoundException($"The resource {uri} could not be found."); return (assetDescriptor.GetStream(), assetDescriptor.Assembly);
} }
return (asset.GetStream(), asset.Assembly); throw new FileNotFoundException($"The resource {uri} could not be found.");
} }
public Assembly? GetAssembly(Uri uri, Uri? baseUri) public Assembly? GetAssembly(Uri uri, Uri? baseUri)
{ {
if (!uri.IsAbsoluteUri && baseUri != null) if (!uri.IsAbsoluteUri && baseUri != null)
{
uri = new Uri(baseUri, uri); uri = new Uri(baseUri, uri);
return GetAssembly(uri)?.Assembly; }
if (TryGetAssembly(uri, out var assemblyDescriptor))
{
return assemblyDescriptor.Assembly;
}
return null;
} }
/// <summary> /// <summary>
@ -121,99 +128,145 @@ namespace Avalonia.Platform
{ {
if (uri.IsAbsoluteResm()) if (uri.IsAbsoluteResm())
{ {
var assembly = GetAssembly(uri); if (!TryGetAssembly(uri, out var assembly))
{
assembly = _defaultResmAssembly;
}
return assembly?.Resources? return assembly?.Resources?
.Where(x => x.Key.IndexOf(uri.GetUnescapeAbsolutePath(), StringComparison.Ordinal) >= 0) .Where(x => x.Key.Contains(uri.GetUnescapeAbsolutePath()))
.Select(x =>new Uri($"resm:{x.Key}?assembly={assembly.Name}")) ?? .Select(x => new Uri($"resm:{x.Key}?assembly={assembly.Name}")) ??
Enumerable.Empty<Uri>(); Enumerable.Empty<Uri>();
} }
uri = uri.EnsureAbsolute(baseUri); uri = uri.EnsureAbsolute(baseUri);
if (uri.IsAvares()) if (uri.IsAvares())
{ {
var (asm, path) = GetResAsmAndPath(uri); if (!TryGetResAsmAndPath(uri, out var assembly, out var path))
if (asm == null)
{ {
throw new ArgumentException( return Enumerable.Empty<Uri>();
"No default assembly, entry assembly or explicit assembly specified; " +
"don't know where to look up for the resource, try specifying assembly explicitly.");
} }
if (asm.AvaloniaResources == null) if (assembly?.AvaloniaResources == null)
{
return Enumerable.Empty<Uri>(); return Enumerable.Empty<Uri>();
}
if (path[path.Length - 1] != '/') if (path.Length > 0 && path[path.Length - 1] != '/')
{
path += '/'; path += '/';
}
return asm.AvaloniaResources return assembly.AvaloniaResources
.Where(r => r.Key.StartsWith(path, StringComparison.Ordinal)) .Where(r => r.Key.StartsWith(path, StringComparison.Ordinal))
.Select(x => new Uri($"avares://{asm.Name}{x.Key}")); .Select(x => new Uri($"avares://{assembly.Name}{x.Key}"));
} }
return Enumerable.Empty<Uri>(); return Enumerable.Empty<Uri>();
} }
private IAssetDescriptor? GetAsset(Uri uri, Uri? baseUri) private bool TryGetAsset(Uri uri, Uri? baseUri, [NotNullWhen(true)] out IAssetDescriptor? assetDescriptor)
{ {
assetDescriptor = null;
if (uri.IsAbsoluteResm()) if (uri.IsAbsoluteResm())
{ {
var asm = GetAssembly(uri) ?? GetAssembly(baseUri) ?? _defaultResmAssembly; if (!TryGetAssembly(uri, out var assembly) && !TryGetAssembly(baseUri, out assembly))
if (asm == null)
{ {
throw new ArgumentException( assembly = _defaultResmAssembly;
"No default assembly, entry assembly or explicit assembly specified; " +
"don't know where to look up for the resource, try specifying assembly explicitly.");
} }
var resourceKey = uri.AbsolutePath; if (assembly?.Resources != null)
IAssetDescriptor? rv = null; {
asm.Resources?.TryGetValue(resourceKey, out rv); var resourceKey = uri.AbsolutePath;
return rv;
if (assembly.Resources.TryGetValue(resourceKey, out assetDescriptor))
{
return true;
}
}
} }
uri = uri.EnsureAbsolute(baseUri); uri = uri.EnsureAbsolute(baseUri);
if (uri.IsAvares()) if (uri.IsAvares())
{ {
var (asm, path) = GetResAsmAndPath(uri); if (TryGetResAsmAndPath(uri, out var assembly, out var path))
if (asm.AvaloniaResources == null) {
return null; if (assembly.AvaloniaResources == null)
asm.AvaloniaResources.TryGetValue(path, out var desc); {
return desc; return false;
}
if (assembly.AvaloniaResources.TryGetValue(path, out assetDescriptor))
{
return true;
}
}
} }
throw new ArgumentException($"Unsupported url type: " + uri.Scheme, nameof(uri)); return false;
} }
private static (IAssemblyDescriptor asm, string path) GetResAsmAndPath(Uri uri) private static bool TryGetResAsmAndPath(Uri uri, [NotNullWhen(true)] out IAssemblyDescriptor? assembly, out string path)
{ {
var asm = s_assemblyDescriptorResolver.GetAssembly(uri.Authority); path = uri.GetUnescapeAbsolutePath();
return (asm, uri.GetUnescapeAbsolutePath());
if (TryLoadAssembly(uri.Authority, out assembly))
{
return true;
}
return false;
} }
private static IAssemblyDescriptor? GetAssembly(Uri? uri) private static bool TryGetAssembly(Uri? uri, [NotNullWhen(true)] out IAssemblyDescriptor? assembly)
{ {
assembly = null;
if (uri != null) if (uri != null)
{ {
if (!uri.IsAbsoluteUri) if (!uri.IsAbsoluteUri)
return null; {
if (uri.IsAvares()) return false;
return GetResAsmAndPath(uri).asm; }
if (uri.IsAvares() && TryGetResAsmAndPath(uri, out assembly, out _))
{
return true;
}
if (uri.IsResm()) if (uri.IsResm())
{ {
var assemblyName = uri.GetAssemblyNameFromQuery(); var assemblyName = uri.GetAssemblyNameFromQuery();
if (assemblyName.Length > 0)
return s_assemblyDescriptorResolver.GetAssembly(assemblyName); if (assemblyName.Length > 0 && TryLoadAssembly(assemblyName, out assembly))
{
return true;
}
} }
} }
return null; return false;
}
private static bool TryLoadAssembly(string assemblyName, [NotNullWhen(true)] out IAssemblyDescriptor? assembly)
{
assembly = null;
try
{
assembly = s_assemblyDescriptorResolver.GetAssembly(assemblyName);
return true;
}
catch (Exception) { }
return false;
} }
#endif #endif
public static void RegisterResUriParsers() public static void RegisterResUriParsers()
{ {
if (!UriParser.IsKnownScheme("avares")) if (!UriParser.IsKnownScheme("avares"))

13
tests/Avalonia.Base.UnitTests/AssetLoaderTests.cs

@ -9,7 +9,7 @@ namespace Avalonia.Base.UnitTests;
public class AssetLoaderTests : IDisposable public class AssetLoaderTests : IDisposable
{ {
public class MockAssembly : Assembly {} public class MockAssembly : Assembly { }
private const string AssemblyNameWithWhitespace = "Awesome Library"; private const string AssemblyNameWithWhitespace = "Awesome Library";
@ -50,6 +50,17 @@ public class AssetLoaderTests : IDisposable
Assert.Equal(AssemblyNameWithNonAscii, assemblyActual?.FullName); Assert.Equal(AssemblyNameWithNonAscii, assemblyActual?.FullName);
} }
[Fact]
public void Invalid_AssemblyName_Should_Yield_Empty_Enumerable()
{
var uri = new Uri($"avares://InvalidAssembly");
var loader = new AssetLoader();
var assemblyActual = loader.GetAssets(uri, null);
Assert.Empty(assemblyActual);
}
private static IAssemblyDescriptor CreateAssemblyDescriptor(string assemblyName) private static IAssemblyDescriptor CreateAssemblyDescriptor(string assemblyName)
{ {
var assembly = Mock.Of<MockAssembly>(); var assembly = Mock.Of<MockAssembly>();

Loading…
Cancel
Save