(Bug found by Claude while I was implementing my deterministic .NET runtime. This report is entirely my understanding, though the code snippets are Claude.)
Interpolated strings can produce unverifiable IL. This appears to be benign on CoreCLR (the reason appears to be because every mismatched type is a reference type, so ldfld only ever sees the same FieldDesc regardless of the type), but it can break other conforming IL consumers.
There are at least two distinct verification errors you can make this way, but the repro below is only one to make it cleaner; you get two simultaneous errors if you use let f (x:int) : System.IComparable = $"{x}".
Repro steps
This is the code:
// Program.fs
let f (x : int) : obj = $"{x}"
System.Console.WriteLine (f 1)
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<Compile Include="Program.fs" />
</ItemGroup>
</Project>
(The obj annotation is what causes the bug to appear. Without the annotation, the IL is fine.)
Then dotnet build (produces a Debug binary). This is not sensitive to net8 vs net10.
Then rm -rf .tools && dotnet tool install --tool-path .tools dotnet-ilverify and dotnet .tools/.store/dotnet-ilverify/10.0.11/dotnet-ilverify/10.0.11/tools/net10.0/any/ILVerify.dll -r "<locate the runtime here>/*.dll" -r ./bin/Debug/net8.0/FSharp.Core.dll .
You can also produce a bug with let f (x:int) : System.IComparable = $"{x}" (which produces two errors) or with let x = 3 in let o : obj = $"{x}".
Expected behavior
Clean verified IL like the following:
$ dotnet .tools/.store/dotnet-ilverify/10.0.11/dotnet-ilverify/10.0.11/tools/net10.0/any/ILVerify.dll ./bin/Debug/net8.0/repro4.dll -r "/nix/store/ngvwp9ba5xg3a9wilmy3nasgjzkylrpb-dotnet-sdk-10.0.203/share/dotnet/shared/Microsoft.NETCore.App/10.0.7/*.dll" -r ./bin/Debug/net8.0/FSharp.Core.dll
All Classes and Methods in /Users/patrick/Documents/GitHub/repro4/bin/Debug/net8.0/repro4.dll Verified.
(The paths look a bit exotic here, but I'm confident this is not a problem caused by Nix.)
Actual behavior
$ dotnet .tools/.store/dotnet-ilverify/10.0.11/dotnet-ilverify/10.0.11/tools/net10.0/any/ILVerify.dll ./bin/Debug/net8.0/repro4.dll -r "/nix/store/ngvwp9ba5xg3a9wilmy3nasgjzkylrpb-dotnet-sdk-10.0.203/share/dotnet/shared/Microsoft.NETCore.App/10.0.7/*.dll" -r ./bin/Debug/net8.0/FSharp.Core.dll
[IL]: Error [StackUnexpected]: [/Users/patrick/Documents/GitHub/repro4/bin/Debug/net8.0/repro4.dll : Program::f(int32)][offset 0x0000001E][found ref '[FSharp.Core]Microsoft.FSharp.Core.PrintfFormat`5<object,object,object,object,int32>'][expected ref '[FSharp.Core]Microsoft.FSharp.Core.PrintfFormat`4<object,Microsoft.FSharp.Core.Unit,string,string>'] Unexpected type on the stack.
1 Error(s) Verifying /Users/patrick/Documents/GitHub/repro4/bin/Debug/net8.0/repro4.dll
The compiler has allocated PrintfFormat<obj, obj, obj, obj, int>, not the correct PrintfFormat<obj, unit, string, string, int>.
The silent upcast which appears to cause this problem will become visible if you turn on warnings 3559 and 3388.
The IComparable thing I mentioned earlier has this additional verification error:
[IL]: Error [StackUnexpected]: [repro4.dll : Program::cmp(int32)][offset 0x00000023]
[found ref 'object'][expected ref '[S.P.CoreLib]System.IComparable'] Unexpected type on the stack.
Known workarounds
Boxing the output works: let f (x : int) : obj = $"{x}" :> obj (or a call to box).
Related information
I've reproduced this in some combinations of the net8 and net10 SDKs and a target framework of net8.0 and net10.0, on darwin-aarch64 and linux-x64 (though not the entire Cartesian product).
$ dotnet --info
.NET SDK:
Version: 10.0.203
Commit: c23858a6d8
Workload version: 10.0.200-manifests.ef86bccd
MSBuild version: 18.3.3+c23858a6d
Runtime Environment:
OS Name: Mac OS X
OS Version: 26.6
OS Platform: Darwin
RID: osx-arm64
Base Path: /nix/store/ngvwp9ba5xg3a9wilmy3nasgjzkylrpb-dotnet-sdk-10.0.203/share/dotnet/sdk/10.0.203/
.NET workloads installed:
There are no installed workloads to display.
Configured to use workload sets when installing new manifests.
No workload sets are installed. Run "dotnet workload restore" to install a workload set.
Host:
Version: 10.0.7
Architecture: arm64
Commit: b16286c228
.NET SDKs installed:
10.0.203 [/nix/store/ngvwp9ba5xg3a9wilmy3nasgjzkylrpb-dotnet-sdk-10.0.203/share/dotnet/sdk]
.NET runtimes installed:
Microsoft.AspNetCore.App 10.0.7 [/nix/store/ngvwp9ba5xg3a9wilmy3nasgjzkylrpb-dotnet-sdk-10.0.203/share/dotnet/shared/Microsoft.AspNetCore.App]
Microsoft.NETCore.App 10.0.7 [/nix/store/ngvwp9ba5xg3a9wilmy3nasgjzkylrpb-dotnet-sdk-10.0.203/share/dotnet/shared/Microsoft.NETCore.App]
Other architectures found:
None
Environment variables:
DOTNET_CLI_TELEMETRY_OPTOUT [1]
DOTNET_LINUX_FRAMEWORK_DIR [/nix/store/fmkdxq8nv51yha9bqkc2695mcqxgrdq1-dotnet-linux-x64-framework-10.0.7]
DOTNET_NOLOGO [1]
DOTNET_RUNTIME_SRC [/nix/store/a34mfixzm2kyz64h9kqv31cdbiw4n83c-runtime-7706f54]
DOTNET_SKIP_FIRST_TIME_EXPERIENCE [1]
DOTNET_SKIP_WORKLOAD_INTEGRITY_CHECK [1]
DOTNET_USE_POLLING_FILE_WATCHER [1]
global.json file:
Not found
(Bug found by Claude while I was implementing my deterministic .NET runtime. This report is entirely my understanding, though the code snippets are Claude.)
Interpolated strings can produce unverifiable IL. This appears to be benign on CoreCLR (the reason appears to be because every mismatched type is a reference type, so
ldfldonly ever sees the same FieldDesc regardless of the type), but it can break other conforming IL consumers.There are at least two distinct verification errors you can make this way, but the repro below is only one to make it cleaner; you get two simultaneous errors if you use
let f (x:int) : System.IComparable = $"{x}".Repro steps
This is the code:
(The
objannotation is what causes the bug to appear. Without the annotation, the IL is fine.)Then
dotnet build(produces a Debug binary). This is not sensitive to net8 vs net10.Then
rm -rf .tools && dotnet tool install --tool-path .tools dotnet-ilverifyanddotnet .tools/.store/dotnet-ilverify/10.0.11/dotnet-ilverify/10.0.11/tools/net10.0/any/ILVerify.dll -r "<locate the runtime here>/*.dll" -r ./bin/Debug/net8.0/FSharp.Core.dll.You can also produce a bug with
let f (x:int) : System.IComparable = $"{x}"(which produces two errors) or withlet x = 3 in let o : obj = $"{x}".Expected behavior
Clean verified IL like the following:
(The paths look a bit exotic here, but I'm confident this is not a problem caused by Nix.)
Actual behavior
The compiler has allocated
PrintfFormat<obj, obj, obj, obj, int>, not the correctPrintfFormat<obj, unit, string, string, int>.The silent upcast which appears to cause this problem will become visible if you turn on warnings 3559 and 3388.
The
IComparablething I mentioned earlier has this additional verification error:Known workarounds
Boxing the output works:
let f (x : int) : obj = $"{x}" :> obj(or a call tobox).Related information
I've reproduced this in some combinations of the net8 and net10 SDKs and a target framework of net8.0 and net10.0, on darwin-aarch64 and linux-x64 (though not the entire Cartesian product).