Skip to content

Commit 760addb

Browse files
author
RandomCrocodile
committed
feature: add best-effort target framework to diagnostic report (#65)
Reads each input module's TargetFrameworkAttribute (via dnlib, from an in-memory copy so the file is never locked) and adds a 'Target Framework' line to the report's configuration section. Best-effort: silently omitted when a module is missing, not a valid assembly, or predates the attribute (net2.0-3.5). Verified end-to-end — a net8 library reports '.NETCoreApp,Version=v8.0'; a failing run on an invalid assembly still produces a clean FAILED report. 4 new tests.
1 parent 557dad9 commit 760addb

2 files changed

Lines changed: 92 additions & 0 deletions

File tree

Confuser.Core/Diagnostics/DiagnosticReport.cs

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
using System;
22
using System.Collections.Generic;
33
using System.Globalization;
4+
using System.IO;
45
using System.Linq;
56
using System.Runtime.InteropServices;
67
using System.Text;
78
using Confuser.Core.Project;
9+
using dnlib.DotNet;
810
using Microsoft.Extensions.Logging;
911

1012
namespace Confuser.Core.Diagnostics {
@@ -69,6 +71,14 @@ static void AppendProject(StringBuilder sb, ConfuserProject project, string user
6971
.Where(p => !string.IsNullOrEmpty(p)).ToList();
7072
sb.AppendLine("- Modules: " + (modules.Count > 0 ? string.Join(", ", modules) : "(none)"));
7173

74+
var targetFrameworks = modules
75+
.Select(m => TryReadTargetFramework(ResolveModulePath(project, m)))
76+
.Where(tfm => !string.IsNullOrEmpty(tfm))
77+
.Distinct()
78+
.ToList();
79+
if (targetFrameworks.Count > 0)
80+
sb.AppendLine("- Target Framework: " + string.Join(", ", targetFrameworks));
81+
7282
var externals = project.Where(m => m.IsExternal).Select(m => m.Path)
7383
.Where(p => !string.IsNullOrEmpty(p)).ToList();
7484
if (externals.Count > 0)
@@ -174,6 +184,53 @@ static string Prefix(LogLevel level) {
174184

175185
static string Show(string value) => string.IsNullOrEmpty(value) ? "(not set)" : value;
176186

187+
static string ResolveModulePath(ConfuserProject project, string modulePath) {
188+
try {
189+
if (!string.IsNullOrEmpty(project.BaseDirectory))
190+
return Path.Combine(project.BaseDirectory, modulePath);
191+
}
192+
catch {
193+
// Fall through to the bare module path.
194+
}
195+
196+
return modulePath;
197+
}
198+
199+
/// <summary>
200+
/// Best-effort read of an assembly's target-framework moniker (e.g.
201+
/// <c>.NETCoreApp,Version=v8.0</c>) from its <c>TargetFrameworkAttribute</c>. Returns
202+
/// <c>null</c> if the file is missing, is not a valid assembly, or carries no such
203+
/// attribute. The file is read into memory so it is never locked.
204+
/// </summary>
205+
public static string TryReadTargetFramework(string assemblyPath) {
206+
try {
207+
if (string.IsNullOrEmpty(assemblyPath) || !File.Exists(assemblyPath))
208+
return null;
209+
210+
using (var module = ModuleDefMD.Load(File.ReadAllBytes(assemblyPath))) {
211+
var assembly = module.Assembly;
212+
if (assembly == null)
213+
return null;
214+
215+
foreach (var attr in assembly.CustomAttributes) {
216+
if (attr.TypeFullName != "System.Runtime.Versioning.TargetFrameworkAttribute")
217+
continue;
218+
if (attr.ConstructorArguments.Count == 0)
219+
continue;
220+
221+
var moniker = attr.ConstructorArguments[0].Value?.ToString();
222+
if (!string.IsNullOrEmpty(moniker))
223+
return moniker;
224+
}
225+
}
226+
}
227+
catch {
228+
// Diagnostic best-effort: any failure to read the framework is non-fatal.
229+
}
230+
231+
return null;
232+
}
233+
177234
static string SafeOsDescription() {
178235
try {
179236
return RuntimeInformation.OSDescription.Trim();

Tests/Confuser.Core.Test/Diagnostics/DiagnosticReportTest.cs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using System.IO;
12
using Confuser.Core;
23
using Confuser.Core.Diagnostics;
34
using Confuser.Core.Project;
@@ -93,5 +94,39 @@ public void Redact_ReplacesUserProfilePrefix(string text, string userProfile, st
9394
public void Redact_LeavesTextWithoutProfileUntouched() {
9495
Assert.Equal("no paths here", DiagnosticRedactor.Redact("no paths here", @"C:\Users\alice"));
9596
}
97+
98+
[Fact]
99+
public void TryReadTargetFramework_ReadsMoniker_FromRealAssembly() {
100+
var path = typeof(DiagnosticCollector).Assembly.Location;
101+
var tfm = DiagnosticReport.TryReadTargetFramework(path);
102+
Assert.NotNull(tfm);
103+
Assert.Contains("Version=v", tfm);
104+
}
105+
106+
[Fact]
107+
public void TryReadTargetFramework_ReturnsNull_ForMissingFile() {
108+
Assert.Null(DiagnosticReport.TryReadTargetFramework(@"C:\does\not\exist.dll"));
109+
}
110+
111+
[Fact]
112+
public void TryReadTargetFramework_ReturnsNull_ForNonAssemblyFile() {
113+
var tmp = Path.GetTempFileName();
114+
File.WriteAllText(tmp, "definitely not a PE file");
115+
try {
116+
Assert.Null(DiagnosticReport.TryReadTargetFramework(tmp));
117+
}
118+
finally {
119+
File.Delete(tmp);
120+
}
121+
}
122+
123+
[Fact]
124+
public void Generate_IncludesTargetFramework_WhenModuleResolves() {
125+
var coreDll = typeof(DiagnosticCollector).Assembly.Location;
126+
var project = new ConfuserProject { BaseDirectory = Path.GetDirectoryName(coreDll) };
127+
project.Add(new ProjectModule { Path = Path.GetFileName(coreDll) });
128+
129+
Assert.Contains("Target Framework:", CollectorFor(project).GenerateReport());
130+
}
96131
}
97132
}

0 commit comments

Comments
 (0)