mirror of https://github.com/icsharpcode/ILSpy.git
Compare commits
40 Commits
aad16c66e9
...
98ee6c3b84
| Author | SHA1 | Date |
|---|---|---|
|
|
98ee6c3b84 | 3 days ago |
|
|
391bf28f81 | 3 days ago |
|
|
a9a43f96a9 | 3 days ago |
|
|
8e2e48f5cc | 3 days ago |
|
|
8a03ee246f | 3 days ago |
|
|
8059fde539 | 3 days ago |
|
|
fadcb3543c | 3 days ago |
|
|
c107c5a4bc | 3 days ago |
|
|
b29dd718a5 | 3 days ago |
|
|
1bfa3b821f | 3 days ago |
|
|
7c39e9ea5e | 3 days ago |
|
|
58baac0c50 | 3 days ago |
|
|
255e73f3a0 | 3 days ago |
|
|
d28ecf1b50 | 3 days ago |
|
|
1a297bebb2 | 3 days ago |
|
|
82edef3bc6 | 3 days ago |
|
|
cf23d76a5e | 3 days ago |
|
|
00e21afd80 | 6 days ago |
|
|
1a9807079d | 6 days ago |
|
|
b67ba16f69 | 1 week ago |
|
|
bbbff59ce2 | 2 weeks ago |
|
|
5833ee57a4 | 2 weeks ago |
|
|
6cd4131d59 | 2 weeks ago |
|
|
2826fff43a | 2 weeks ago |
|
|
a41156ff1d | 2 weeks ago |
|
|
dfc2bbb970 | 2 weeks ago |
|
|
4c347ef6b9 | 2 weeks ago |
|
|
4c8e606a6a | 2 weeks ago |
|
|
94f574ed9c | 2 weeks ago |
|
|
eec031a220 | 2 weeks ago |
|
|
fd6a81e7cb | 2 weeks ago |
|
|
a541fc0ddf | 2 weeks ago |
|
|
317e33b3b8 | 2 weeks ago |
|
|
784379d012 | 2 weeks ago |
|
|
a8d841199d | 2 weeks ago |
|
|
775ff371ac | 2 weeks ago |
|
|
d5e2e61a90 | 2 weeks ago |
|
|
28fffb7e2c | 2 weeks ago |
|
|
50afc97e7b | 2 weeks ago |
|
|
d3e9121a32 | 2 weeks ago |
53 changed files with 2882 additions and 351 deletions
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,177 @@
@@ -0,0 +1,177 @@
|
||||
// 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.Linq; |
||||
|
||||
using ICSharpCode.Decompiler.IL.Transforms; |
||||
|
||||
namespace ICSharpCode.Decompiler.IL.ControlFlow |
||||
{ |
||||
/// <summary>
|
||||
/// Collapses the runtime-async manual await pattern (GetAwaiter / get_IsCompleted /
|
||||
/// AsyncHelpers.UnsafeAwaitAwaiter / GetResult across three blocks) into a single IL
|
||||
/// Await instruction, matching what the state-machine async pipeline emits.
|
||||
/// </summary>
|
||||
static class RuntimeAsyncManualAwaitTransform |
||||
{ |
||||
public static void Run(ILFunction function, ILTransformContext context) |
||||
{ |
||||
bool changed = false; |
||||
foreach (var block in function.Descendants.OfType<Block>().ToArray()) |
||||
{ |
||||
if (DetectRuntimeAsyncManualAwait(block, context)) |
||||
changed = true; |
||||
} |
||||
if (changed) |
||||
{ |
||||
foreach (var c in function.Body.Descendants.OfType<BlockContainer>()) |
||||
c.SortBlocks(deleteUnreachableBlocks: true); |
||||
} |
||||
} |
||||
|
||||
// Block X (head) {
|
||||
// [stloc awaitable(<value>);] (optional: when GetAwaiter takes an address)
|
||||
// stloc awaiter(call GetAwaiter(<expr>))
|
||||
// if (call get_IsCompleted(ldloca awaiter)) br completedBlock
|
||||
// br pauseBlock
|
||||
// }
|
||||
// Block pauseBlock {
|
||||
// call AsyncHelpers.UnsafeAwaitAwaiter[<T>](ldloc awaiter)
|
||||
// br completedBlock
|
||||
// }
|
||||
// Block completedBlock {
|
||||
// call GetResult(ldloca awaiter) (possibly inlined into a containing expression)
|
||||
// ...
|
||||
// }
|
||||
// =>
|
||||
// Block X { ...; br completedBlock }
|
||||
// Block completedBlock { <Await(value)>; ... }
|
||||
static bool DetectRuntimeAsyncManualAwait(Block block, ILTransformContext context) |
||||
{ |
||||
if (block.Instructions.Count < 3) |
||||
return false; |
||||
|
||||
if (!block.Instructions[^3].MatchStLoc(out var awaiterVar, out var awaiterValue)) |
||||
return false; |
||||
if (awaiterValue is not CallInstruction getAwaiterCall) |
||||
return false; |
||||
if (getAwaiterCall.Method.Name != "GetAwaiter" || getAwaiterCall.Arguments.Count != 1) |
||||
return false; |
||||
|
||||
if (block.Instructions[^2] is not IfInstruction ifInst) |
||||
return false; |
||||
var condition = ifInst.Condition; |
||||
if (condition.MatchLogicNot(out var negated)) |
||||
condition = negated; |
||||
if (condition is not CallInstruction isCompletedCall) |
||||
return false; |
||||
if (isCompletedCall.Method.Name != "get_IsCompleted" || isCompletedCall.Arguments.Count != 1) |
||||
return false; |
||||
if (!isCompletedCall.Arguments[0].MatchLdLoca(awaiterVar)) |
||||
return false; |
||||
|
||||
Block completedBlock, pauseBlock; |
||||
if (ifInst.TrueInst.MatchBranch(out var trueBranchTarget) && block.Instructions[^1].MatchBranch(out var falseBranchTarget)) |
||||
{ |
||||
if (negated != null) |
||||
{ |
||||
// if (!IsCompleted) br pauseBlock; br completedBlock — flipped
|
||||
pauseBlock = trueBranchTarget; |
||||
completedBlock = falseBranchTarget; |
||||
} |
||||
else |
||||
{ |
||||
// if (IsCompleted) br completedBlock; br pauseBlock — canonical
|
||||
completedBlock = trueBranchTarget; |
||||
pauseBlock = falseBranchTarget; |
||||
} |
||||
} |
||||
else |
||||
{ |
||||
return false; |
||||
} |
||||
|
||||
// Block pauseBlock { call AsyncHelpers.UnsafeAwaitAwaiter(ldloc awaiter); br completedBlock }
|
||||
if (pauseBlock.Instructions.Count != 2) |
||||
return false; |
||||
if (pauseBlock.Instructions[0] is not Call pauseCall) |
||||
return false; |
||||
if (!EarlyExpressionTransforms.IsAsyncHelpersMethod(pauseCall.Method, "UnsafeAwaitAwaiter") || pauseCall.Arguments.Count != 1) |
||||
return false; |
||||
if (!pauseCall.Arguments[0].MatchLdLoc(awaiterVar)) |
||||
return false; |
||||
if (!pauseBlock.Instructions[1].MatchBranch(out var pauseTail) || pauseTail != completedBlock) |
||||
return false; |
||||
|
||||
// completedBlock starts with: GetResult(ldloca awaiter), possibly inlined.
|
||||
if (completedBlock.Instructions.Count < 1) |
||||
return false; |
||||
var getResultCall = ILInlining.FindFirstInlinedCall(completedBlock.Instructions[0]); |
||||
if (getResultCall == null) |
||||
return false; |
||||
if (getResultCall.Method.Name != "GetResult" || getResultCall.Arguments.Count != 1) |
||||
return false; |
||||
if (!getResultCall.Arguments[0].MatchLdLoca(awaiterVar)) |
||||
return false; |
||||
|
||||
// Determine the awaitable expression: the original GetAwaiter argument (often `ldloca tmp`)
|
||||
// or, if `tmp` is a fresh temporary set immediately above, the value it was set to.
|
||||
ILInstruction awaitable; |
||||
bool removeTemporaryStore = false; |
||||
if (getAwaiterCall.Arguments[0].MatchLdLoca(out var tmpVar) |
||||
&& block.Instructions.Count >= 4 |
||||
&& block.Instructions[^4].MatchStLoc(tmpVar, out var tmpValue) |
||||
&& tmpVar.StoreCount == 1 |
||||
&& tmpVar.LoadCount == 0 |
||||
&& tmpVar.AddressCount == 1) |
||||
{ |
||||
awaitable = tmpValue; |
||||
removeTemporaryStore = true; |
||||
} |
||||
else |
||||
{ |
||||
awaitable = getAwaiterCall.Arguments[0]; |
||||
} |
||||
|
||||
context.Step("Reduce manual await pattern to Await", block); |
||||
|
||||
// Capture IL ranges before we remove the suspend machinery. The new Await stands in
|
||||
// for the whole pattern (temp store + GetAwaiter + IsCompleted check + UnsafeAwaitAwaiter
|
||||
// + GetResult), and the new `br completedBlock` replaces the original `br pauseBlock`.
|
||||
var oldTailBranch = block.Instructions[^1]; |
||||
var awaitInst = new Await(awaitable.Clone()).WithILRange(getResultCall); |
||||
if (removeTemporaryStore) |
||||
awaitInst.AddILRange(block.Instructions[^4]); |
||||
awaitInst.AddILRange(block.Instructions[^3]); |
||||
awaitInst.AddILRange(block.Instructions[^2]); |
||||
foreach (var inst in pauseBlock.Instructions) |
||||
awaitInst.AddILRange(inst); |
||||
awaitInst.GetAwaiterMethod = getAwaiterCall.Method; |
||||
awaitInst.GetResultMethod = getResultCall.Method; |
||||
|
||||
// Remove the trailing 3 (or 4) instructions of the head block; replace with `br completedBlock`.
|
||||
int removeCount = removeTemporaryStore ? 4 : 3; |
||||
block.Instructions.RemoveRange(block.Instructions.Count - removeCount, removeCount); |
||||
block.Instructions.Add(new Branch(completedBlock).WithILRange(oldTailBranch)); |
||||
|
||||
getResultCall.ReplaceWith(awaitInst); |
||||
|
||||
return true; |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,95 @@
@@ -0,0 +1,95 @@
|
||||
// Copyright (c) 2026 AlphaSierraPapa for the SharpDevelop Team
|
||||
//
|
||||
// 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.IO; |
||||
using System.Threading; |
||||
|
||||
using ICSharpCode.BamlDecompiler; |
||||
using ICSharpCode.Decompiler; |
||||
using ICSharpCode.Decompiler.CSharp.ProjectDecompiler; |
||||
using ICSharpCode.Decompiler.DebugInfo; |
||||
using ICSharpCode.Decompiler.Metadata; |
||||
|
||||
namespace ICSharpCode.ILSpyCmd |
||||
{ |
||||
sealed class BamlAwareWholeProjectDecompiler : WholeProjectDecompiler |
||||
{ |
||||
readonly BamlDecompilerTypeSystem bamlTypeSystem; |
||||
readonly BamlDecompilerSettings bamlSettings; |
||||
|
||||
public BamlAwareWholeProjectDecompiler( |
||||
DecompilerSettings settings, |
||||
IAssemblyResolver assemblyResolver, |
||||
AssemblyReferenceClassifier assemblyReferenceClassifier, |
||||
IDebugInfoProvider debugInfoProvider, |
||||
BamlDecompilerTypeSystem bamlTypeSystem, |
||||
BamlDecompilerSettings bamlSettings) |
||||
: base(settings, assemblyResolver, projectWriter: null, assemblyReferenceClassifier, debugInfoProvider) |
||||
{ |
||||
this.bamlTypeSystem = bamlTypeSystem ?? throw new ArgumentNullException(nameof(bamlTypeSystem)); |
||||
this.bamlSettings = bamlSettings ?? throw new ArgumentNullException(nameof(bamlSettings)); |
||||
} |
||||
|
||||
public CancellationToken CancellationToken { get; set; } |
||||
|
||||
protected override IEnumerable<ProjectItemInfo> WriteResourceToFile(string fileName, string resourceName, Stream entryStream) |
||||
{ |
||||
if (!fileName.EndsWith(".baml", StringComparison.OrdinalIgnoreCase)) |
||||
return base.WriteResourceToFile(fileName, resourceName, entryStream); |
||||
|
||||
var decompiler = new XamlDecompiler(bamlTypeSystem, bamlSettings) { |
||||
CancellationToken = CancellationToken |
||||
}; |
||||
var result = decompiler.Decompile(entryStream); |
||||
|
||||
string xamlFileName; |
||||
PartialTypeInfo partialTypeInfo = null; |
||||
|
||||
var typeDefinition = result.TypeName.HasValue |
||||
? bamlTypeSystem.MainModule.GetTypeDefinition(result.TypeName.Value.TopLevelTypeName) |
||||
: null; |
||||
if (typeDefinition != null) |
||||
{ |
||||
xamlFileName = SanitizeFileName(typeDefinition.ReflectionName + ".xaml"); |
||||
partialTypeInfo = new PartialTypeInfo(typeDefinition); |
||||
foreach (var member in result.GeneratedMembers) |
||||
partialTypeInfo.AddDeclaredMember(member); |
||||
} |
||||
else |
||||
{ |
||||
xamlFileName = Path.ChangeExtension(fileName, ".xaml"); |
||||
} |
||||
|
||||
string fullPath = Path.Combine(TargetDirectory, xamlFileName); |
||||
string directory = Path.GetDirectoryName(fullPath); |
||||
if (!string.IsNullOrEmpty(directory)) |
||||
Directory.CreateDirectory(directory); |
||||
result.Xaml.Save(fullPath); |
||||
|
||||
var item = new ProjectItemInfo("Page", xamlFileName) |
||||
.With("Generator", "MSBuild:Compile") |
||||
.With("SubType", "Designer"); |
||||
if (partialTypeInfo != null) |
||||
item.PartialTypes = new List<PartialTypeInfo> { partialTypeInfo }; |
||||
|
||||
return new[] { item }; |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,177 @@
@@ -0,0 +1,177 @@
|
||||
// Copyright (c) 2026 AlphaSierraPapa for the SharpDevelop Team
|
||||
//
|
||||
// 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.IO; |
||||
using System.Linq; |
||||
using System.Threading; |
||||
using System.Xml.Linq; |
||||
|
||||
using ICSharpCode.BamlDecompiler; |
||||
using ICSharpCode.Decompiler.Metadata; |
||||
using ICSharpCode.Decompiler.Util; |
||||
|
||||
namespace ICSharpCode.ILSpyCmd |
||||
{ |
||||
static class ResourceExtensions |
||||
{ |
||||
public static IEnumerable<string> EnumerateResourcePaths(MetadataFile module) |
||||
{ |
||||
foreach (var r in module.Resources.Where(r => r.ResourceType == ResourceType.Embedded)) |
||||
{ |
||||
IReadOnlyList<string> entries; |
||||
if (r.Name.EndsWith(".resources", StringComparison.OrdinalIgnoreCase) |
||||
&& TryReadResourcesEntryNames(r, out entries)) |
||||
{ |
||||
foreach (var name in entries) |
||||
yield return r.Name + "/" + name; |
||||
} |
||||
else |
||||
{ |
||||
yield return r.Name; |
||||
} |
||||
} |
||||
} |
||||
|
||||
public static bool TryGetResource(MetadataFile module, string resourcePath, out object value) |
||||
{ |
||||
foreach (var r in module.Resources.Where(r => r.ResourceType == ResourceType.Embedded)) |
||||
{ |
||||
if (string.Equals(resourcePath, r.Name, StringComparison.OrdinalIgnoreCase)) |
||||
{ |
||||
using Stream stream = r.TryOpenStream(); |
||||
if (stream == null) |
||||
{ |
||||
value = null; |
||||
return false; |
||||
} |
||||
stream.Position = 0; |
||||
var ms = new MemoryStream(); |
||||
stream.CopyTo(ms); |
||||
value = ms.ToArray(); |
||||
return true; |
||||
} |
||||
|
||||
string prefix = r.Name + "/"; |
||||
if (r.Name.EndsWith(".resources", StringComparison.OrdinalIgnoreCase) |
||||
&& resourcePath.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) |
||||
{ |
||||
string entryName = resourcePath.Substring(prefix.Length); |
||||
if (TryReadResourcesEntry(r, entryName, out value)) |
||||
return true; |
||||
} |
||||
} |
||||
|
||||
value = null; |
||||
return false; |
||||
} |
||||
|
||||
public static XDocument DecompileBaml(MetadataFile module, IAssemblyResolver resolver, Stream bamlStream, BamlDecompilerSettings settings, CancellationToken cancellationToken) |
||||
{ |
||||
var typeSystem = new BamlDecompilerTypeSystem(module, resolver); |
||||
var decompiler = new XamlDecompiler(typeSystem, settings) { |
||||
CancellationToken = cancellationToken |
||||
}; |
||||
return decompiler.Decompile(bamlStream).Xaml; |
||||
} |
||||
|
||||
static bool TryReadResourcesEntryNames(Resource r, out IReadOnlyList<string> names) |
||||
{ |
||||
using Stream stream = r.TryOpenStream(); |
||||
if (stream == null) |
||||
{ |
||||
names = Array.Empty<string>(); |
||||
return false; |
||||
} |
||||
stream.Position = 0; |
||||
|
||||
ResourcesFile resourcesFile; |
||||
try |
||||
{ |
||||
resourcesFile = new ResourcesFile(stream); |
||||
} |
||||
catch (Exception ex) when (ex is BadImageFormatException || ex is EndOfStreamException) |
||||
{ |
||||
names = null; |
||||
return false; |
||||
} |
||||
|
||||
using (resourcesFile) |
||||
{ |
||||
var list = new List<string>(resourcesFile.ResourceCount); |
||||
for (int i = 0; i < resourcesFile.ResourceCount; i++) |
||||
list.Add(resourcesFile.GetResourceName(i)); |
||||
names = list; |
||||
return true; |
||||
} |
||||
} |
||||
|
||||
static bool TryReadResourcesEntry(Resource r, string entryName, out object value) |
||||
{ |
||||
using Stream stream = r.TryOpenStream(); |
||||
if (stream == null) |
||||
{ |
||||
value = null; |
||||
return false; |
||||
} |
||||
stream.Position = 0; |
||||
|
||||
ResourcesFile resourcesFile; |
||||
try |
||||
{ |
||||
resourcesFile = new ResourcesFile(stream); |
||||
} |
||||
catch (Exception ex) when (ex is BadImageFormatException || ex is EndOfStreamException) |
||||
{ |
||||
value = null; |
||||
return false; |
||||
} |
||||
|
||||
using (resourcesFile) |
||||
{ |
||||
for (int i = 0; i < resourcesFile.ResourceCount; i++) |
||||
{ |
||||
if (!string.Equals(resourcesFile.GetResourceName(i), entryName, StringComparison.OrdinalIgnoreCase)) |
||||
continue; |
||||
|
||||
object entryValue = resourcesFile.GetResourceValue(i); |
||||
if (entryValue is ResourceSerializedObject serialized) |
||||
{ |
||||
value = serialized.GetBytes(); |
||||
} |
||||
else if (entryValue is Stream s) |
||||
{ |
||||
var ms = new MemoryStream(); |
||||
s.Position = 0; |
||||
s.CopyTo(ms); |
||||
value = ms.ToArray(); |
||||
} |
||||
else |
||||
{ |
||||
value = entryValue; |
||||
} |
||||
return true; |
||||
} |
||||
} |
||||
|
||||
value = null; |
||||
return false; |
||||
} |
||||
} |
||||
} |
||||
Loading…
Reference in new issue