diff --git a/ICSharpCode.BamlDecompiler/Baml/KnownThings.cs b/ICSharpCode.BamlDecompiler/Baml/KnownThings.cs index 8b5a32051..9e43e4dae 100644 --- a/ICSharpCode.BamlDecompiler/Baml/KnownThings.cs +++ b/ICSharpCode.BamlDecompiler/Baml/KnownThings.cs @@ -76,7 +76,14 @@ namespace ICSharpCode.BamlDecompiler.Baml return module; } - ITypeDefinition InitType(IModule assembly, string ns, string name) => assembly.GetTypeDefinition(new TopLevelTypeName(ns, name)); + ITypeDefinition InitType(IModule assembly, string ns, string name) + { + // A synthetic stand-in only materializes the types the BAML decompiler explicitly seeds + // here, keeping it bounded to the well-known set. Any other lookup on it returns null. + if (assembly is SyntheticWpfModule synthetic) + return synthetic.RegisterType(ns, name); + return assembly.GetTypeDefinition(new TopLevelTypeName(ns, name)); + } KnownMember InitMember(KnownTypes parent, string name, ITypeDefinition type) => new KnownMember(parent, types[parent], name, type); } diff --git a/ICSharpCode.BamlDecompiler/BamlDecompilerTypeSystem.cs b/ICSharpCode.BamlDecompiler/BamlDecompilerTypeSystem.cs index dc4ade113..a2e2cc412 100644 --- a/ICSharpCode.BamlDecompiler/BamlDecompilerTypeSystem.cs +++ b/ICSharpCode.BamlDecompiler/BamlDecompilerTypeSystem.cs @@ -40,6 +40,14 @@ namespace ICSharpCode.BamlDecompiler "System.Xml, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" }; + // The WPF assemblies whose types serialize under the presentation XML namespace. When one of + // these has to be synthesized (e.g. inspecting a WPF binary on a non-Windows machine), the + // synthetic module reproduces its XmlnsDefinitionAttribute mapping so known types still emit + // the clean presentation xmlns rather than a clr-namespace fallback. + static readonly HashSet presentationXmlnsAssemblies = new(StringComparer.OrdinalIgnoreCase) { + "WindowsBase", "PresentationCore", "PresentationFramework", "PresentationUI" + }; + public BamlDecompilerTypeSystem(MetadataFile mainModule, IAssemblyResolver assemblyResolver) { if (mainModule == null) @@ -69,9 +77,10 @@ namespace ICSharpCode.BamlDecompiler { assemblyReferenceQueue.Enqueue((true, mainModule, refs)); } - foreach (var bamlReference in defaultBamlReferences) + var defaultReferences = defaultBamlReferences.Select(AssemblyNameReference.Parse).ToArray(); + foreach (var bamlReference in defaultReferences) { - assemblyReferenceQueue.Enqueue((true, mainModule, AssemblyNameReference.Parse(bamlReference))); + assemblyReferenceQueue.Enqueue((true, mainModule, bamlReference)); } var comparer = KeyComparer.Create(((bool IsAssembly, MetadataFile MainModule, object Reference) reference) => reference.IsAssembly ? "A:" + ((IAssemblyReference)reference.Reference).FullName : @@ -113,6 +122,24 @@ namespace ICSharpCode.BamlDecompiler } var mainModuleWithOptions = mainModule.WithOptions(TypeSystemOptions.Default); var referencedAssembliesWithOptions = referencedAssemblies.Select(file => file.WithOptions(TypeSystemOptions.Default)); + // Substitute a synthetic stand-in for every well-known BAML assembly that could not be + // resolved (e.g. WPF assemblies when inspecting a WPF binary on a machine without WPF). + // KnownThings assumes these assemblies are always present; the stand-in upholds that + // invariant so the BAML decompiler degrades gracefully instead of failing outright. + var resolvedAssemblyNames = new HashSet( + referencedAssemblies.Select(file => file.Name), StringComparer.OrdinalIgnoreCase); + resolvedAssemblyNames.Add(mainModule.Name); + var syntheticReferences = new List(); + foreach (var reference in defaultReferences) + { + if (resolvedAssemblyNames.Contains(reference.Name)) + continue; + string presentationXmlns = presentationXmlnsAssemblies.Contains(reference.Name) + ? XamlContext.KnownNamespace_Presentation + : null; + syntheticReferences.Add(SyntheticWpfModule.CreateReference(reference, presentationXmlns)); + } + referencedAssembliesWithOptions = referencedAssembliesWithOptions.Concat(syntheticReferences); // Primitive types are necessary to avoid assertions in ILReader. // Fallback to MinimalCorlib to provide the primitive types. if (!HasType(KnownTypeCode.Void) || !HasType(KnownTypeCode.Int32)) diff --git a/ICSharpCode.BamlDecompiler/SyntheticWpfModule.cs b/ICSharpCode.BamlDecompiler/SyntheticWpfModule.cs new file mode 100644 index 000000000..975339f23 --- /dev/null +++ b/ICSharpCode.BamlDecompiler/SyntheticWpfModule.cs @@ -0,0 +1,296 @@ +// Copyright (c) 2026 Siegfried Pammer +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Reflection.Metadata; +using System.Reflection.Metadata.Ecma335; + +using ICSharpCode.Decompiler.Metadata; +using ICSharpCode.Decompiler.TypeSystem; +using ICSharpCode.Decompiler.TypeSystem.Implementation; +using ICSharpCode.Decompiler.Util; + +namespace ICSharpCode.BamlDecompiler +{ + /// + /// An artificial stand-in for one of the well-known BAML assemblies (e.g. PresentationFramework) + /// when the real assembly cannot be resolved - for instance when a WPF binary is inspected on a + /// machine that has no WPF installed (Linux/macOS). + /// + /// It mirrors : it materializes type definitions on request but carries + /// no members. Only the types the BAML decompiler explicitly seeds via + /// are materialized; every other lookup returns null, so references to non-well-known types + /// keep decomposing to UnknownType exactly as they do when the assembly is entirely absent. + /// + /// + sealed class SyntheticWpfModule : IModule + { + /// + /// Creates a module reference that resolves to a standing in + /// for . When is + /// non-null, the synthesized assembly exposes an XmlnsDefinitionAttribute mapping every + /// seeded CLR namespace to that XML namespace, so known WPF types serialize with the clean + /// presentation xmlns instead of a clr-namespace fallback. + /// + public static IModuleReference CreateReference(IAssemblyReference assemblyName, string presentationXmlnsNamespace) + { + return new SyntheticModuleReference(assemblyName, presentationXmlnsNamespace); + } + + public ICompilation Compilation { get; } + + readonly IAssemblyReference assemblyName; + readonly string presentationXmlnsNamespace; + readonly Dictionary typeDefinitions = new(); + readonly SyntheticNamespace rootNamespace; + IReadOnlyList assemblyAttributes; + + SyntheticWpfModule(ICompilation compilation, IAssemblyReference assemblyName, string presentationXmlnsNamespace) + { + this.Compilation = compilation; + this.assemblyName = assemblyName; + this.presentationXmlnsNamespace = presentationXmlnsNamespace; + this.rootNamespace = new SyntheticNamespace(this, null, string.Empty, string.Empty); + } + + /// + /// Seeds this module with a synthetic definition for the given type and returns it. + /// Repeated calls for the same type return the cached instance. This is the only way a type + /// becomes visible on the module; plain + /// lookups never create new types. + /// + public ITypeDefinition RegisterType(string ns, string name) + { + var typeName = new TopLevelTypeName(ns, name); + if (!typeDefinitions.TryGetValue(typeName, out var typeDef)) + { + typeDef = new SyntheticTypeDefinition(this, typeName); + typeDefinitions.Add(typeName, typeDef); + // Invalidate the cached xmlns attributes; a namespace may have appeared. + assemblyAttributes = null; + } + return typeDef; + } + + bool IModule.IsMainModule => Compilation.MainModule == this; + + string IModule.AssemblyName => assemblyName.Name; + Version IModule.AssemblyVersion => assemblyName.Version; + string IModule.FullAssemblyName => assemblyName.FullName; + string ISymbol.Name => assemblyName.Name; + SymbolKind ISymbol.SymbolKind => SymbolKind.Module; + + ICSharpCode.Decompiler.Metadata.MetadataFile IModule.MetadataFile => null; + INamespace IModule.RootNamespace => rootNamespace; + + public IEnumerable TopLevelTypeDefinitions => typeDefinitions.Values; + public IEnumerable TypeDefinitions => typeDefinitions.Values; + + public ITypeDefinition GetTypeDefinition(TopLevelTypeName topLevelTypeName) + { + return typeDefinitions.TryGetValue(topLevelTypeName, out var typeDef) ? typeDef : null; + } + + IEnumerable IModule.GetAssemblyAttributes() => GetAssemblyAttributes(); + IEnumerable IModule.GetModuleAttributes() => EmptyList.Instance; + + bool IModule.InternalsVisibleTo(IModule module) => module == this; + + IReadOnlyList GetAssemblyAttributes() + { + if (presentationXmlnsNamespace == null) + return EmptyList.Instance; + var attributes = assemblyAttributes; + if (attributes != null) + return attributes; + + // Reconstruct the XmlnsDefinitionAttribute set that the real WPF assembly would carry: + // one entry per seeded CLR namespace, all mapping to the presentation XML namespace. + var attributeType = new SyntheticTypeDefinition(this, + new TopLevelTypeName("System.Windows.Markup", "XmlnsDefinitionAttribute")); + var stringType = Compilation.FindType(KnownTypeCode.String); + var namespaces = typeDefinitions.Keys.Select(t => t.Namespace).Distinct(); + var list = new List(); + foreach (var ns in namespaces) + { + var fixedArguments = ImmutableArray.Create( + new CustomAttributeTypedArgument(stringType, presentationXmlnsNamespace), + new CustomAttributeTypedArgument(stringType, ns)); + list.Add(new DefaultAttribute(attributeType, fixedArguments, + ImmutableArray>.Empty)); + } + attributes = list; + assemblyAttributes = attributes; + return attributes; + } + + sealed class SyntheticModuleReference : IModuleReference + { + readonly IAssemblyReference assemblyName; + readonly string presentationXmlnsNamespace; + + public SyntheticModuleReference(IAssemblyReference assemblyName, string presentationXmlnsNamespace) + { + this.assemblyName = assemblyName; + this.presentationXmlnsNamespace = presentationXmlnsNamespace; + } + + IModule IModuleReference.Resolve(ITypeResolveContext context) + { + return new SyntheticWpfModule(context.Compilation, assemblyName, presentationXmlnsNamespace); + } + } + + sealed class SyntheticNamespace : INamespace + { + readonly SyntheticWpfModule module; + + public INamespace ParentNamespace { get; } + public string FullName { get; } + public string Name { get; } + + public SyntheticNamespace(SyntheticWpfModule module, INamespace parentNamespace, string fullName, string name) + { + this.module = module; + this.ParentNamespace = parentNamespace; + this.FullName = fullName; + this.Name = name; + } + + string INamespace.ExternAlias => string.Empty; + + IEnumerable INamespace.ChildNamespaces => EmptyList.Instance; + IEnumerable INamespace.Types => module.TopLevelTypeDefinitions.Where(td => td.Namespace == FullName); + IEnumerable INamespace.ContributingModules => new[] { module }; + + SymbolKind ISymbol.SymbolKind => SymbolKind.Namespace; + ICompilation ICompilationProvider.Compilation => module.Compilation; + + INamespace INamespace.GetChildNamespace(string name) => null; + + ITypeDefinition INamespace.GetTypeDefinition(string name, int typeParameterCount) + { + if (typeParameterCount != 0) + return null; + return module.GetTypeDefinition(new TopLevelTypeName(FullName, name)); + } + } + + sealed class SyntheticTypeDefinition : ITypeDefinition + { + readonly SyntheticWpfModule module; + readonly TopLevelTypeName typeName; + + public SyntheticTypeDefinition(SyntheticWpfModule module, TopLevelTypeName typeName) + { + this.module = module; + this.typeName = typeName; + } + + IReadOnlyList ITypeDefinition.NestedTypes => EmptyList.Instance; + IReadOnlyList ITypeDefinition.Members => EmptyList.Instance; + IEnumerable ITypeDefinition.Fields => EmptyList.Instance; + IEnumerable ITypeDefinition.Methods => EmptyList.Instance; + IEnumerable ITypeDefinition.Properties => EmptyList.Instance; + IEnumerable ITypeDefinition.Events => EmptyList.Instance; + + KnownTypeCode ITypeDefinition.KnownTypeCode => KnownTypeCode.None; + + IType ITypeDefinition.EnumUnderlyingType => SpecialType.UnknownType; + + public FullTypeName FullTypeName => typeName; + + public string MetadataName => typeName.Name; + + ITypeDefinition IEntity.DeclaringTypeDefinition => null; + IType ITypeDefinition.DeclaringType => null; + IType IType.DeclaringType => null; + IType IEntity.DeclaringType => null; + + bool ITypeDefinition.HasExtensions => false; + ExtensionInfo ITypeDefinition.ExtensionInfo => null; + bool ITypeDefinition.IsReadOnly => false; + + // Materialized WPF types carry no metadata, so treat them all as (reference-type) classes. + // The BAML decompiler only reads their identity and null-guards everything else. + TypeKind IType.Kind => TypeKind.Class; + bool? IType.IsReferenceType => true; + bool IType.IsByRefLike => false; + Nullability IType.Nullability => Nullability.Oblivious; + Nullability ITypeDefinition.NullableContext => Nullability.Oblivious; + + // Synthetic WPF types are always nullability-oblivious and BAML never annotates them. + IType IType.ChangeNullability(Nullability nullability) => this; + + int IType.TypeParameterCount => 0; + IReadOnlyList IType.TypeParameters => EmptyList.Instance; + IReadOnlyList IType.TypeArguments => EmptyList.Instance; + IEnumerable IType.DirectBaseTypes => EmptyList.Instance; + + EntityHandle IEntity.MetadataToken => MetadataTokens.TypeDefinitionHandle(0); + + public string Name => typeName.Name; + + IModule IEntity.ParentModule => module; + + Accessibility IEntity.Accessibility => Accessibility.Public; + + bool IEntity.IsStatic => false; + bool IEntity.IsAbstract => false; + bool IEntity.IsSealed => false; + + SymbolKind ISymbol.SymbolKind => SymbolKind.TypeDefinition; + + ICompilation ICompilationProvider.Compilation => module.Compilation; + + string INamedElement.FullName => typeName.ReflectionName; + string INamedElement.ReflectionName => typeName.ReflectionName; + string INamedElement.Namespace => typeName.Namespace; + + bool IEquatable.Equals(IType other) => this == other; + + IEnumerable IType.GetAccessors(Predicate filter, GetMemberOptions options) => EmptyList.Instance; + IEnumerable IEntity.GetAttributes() => EmptyList.Instance; + bool IEntity.HasAttribute(KnownAttribute attribute) => false; + IAttribute IEntity.GetAttribute(KnownAttribute attribute) => null; + IEnumerable IType.GetConstructors(Predicate filter, GetMemberOptions options) => EmptyList.Instance; + IEnumerable IType.GetEvents(Predicate filter, GetMemberOptions options) => EmptyList.Instance; + IEnumerable IType.GetFields(Predicate filter, GetMemberOptions options) => EmptyList.Instance; + IEnumerable IType.GetMembers(Predicate filter, GetMemberOptions options) => EmptyList.Instance; + IEnumerable IType.GetMethods(Predicate filter, GetMemberOptions options) => EmptyList.Instance; + IEnumerable IType.GetMethods(IReadOnlyList typeArguments, Predicate filter, GetMemberOptions options) => EmptyList.Instance; + IEnumerable IType.GetNestedTypes(Predicate filter, GetMemberOptions options) => EmptyList.Instance; + IEnumerable IType.GetNestedTypes(IReadOnlyList typeArguments, Predicate filter, GetMemberOptions options) => EmptyList.Instance; + IEnumerable IType.GetProperties(Predicate filter, GetMemberOptions options) => EmptyList.Instance; + + bool ITypeDefinition.IsRecord => false; + + ITypeDefinition IType.GetDefinition() => this; + ITypeDefinitionOrUnknown IType.GetDefinitionOrUnknown() => this; + TypeParameterSubstitution IType.GetSubstitution() => TypeParameterSubstitution.Identity; + + IType IType.AcceptVisitor(TypeVisitor visitor) => visitor.VisitTypeDefinition(this); + IType IType.VisitChildren(TypeVisitor visitor) => this; + + public override string ToString() => $"[SyntheticWpfType {typeName.ReflectionName}]"; + } + } +}