From 0d135fa0e58620e0a39e49bd3086b0080c20624f Mon Sep 17 00:00:00 2001 From: Drew Scoggins Date: Tue, 5 May 2026 10:55:15 -0700 Subject: [PATCH 1/3] Handle minimal MethodTables in dotnet-pgo trace processing BulkType ETW events emit `TypeNameID = th.GetCl()` (eventtrace_bulktype.cpp). For "minimal" MethodTables created by `CreateMinimalMethodTable` (used for Reflection.Emit DynamicMethod hosts in dynamicmethod.cpp and the IL stub cache in ilstubcache.cpp), `SetCl` is never called, so `GetCl` returns `mdTypeDefNil` (0x02000000, rid 0). dotnet-pgo previously treated this as a valid TypeDef token, and the lazy `EcmaType.Name` access would throw `BadImageFormatException: Read out of bounds` because rid 0 is before the typedef table. Validate the typedef row before constructing the `TypeDefinitionHandle` and treat out-of-range rids (rid 0, or rid greater than the typedef table size) as unresolvable dynamic types so trace processing can continue. This started failing on the SDK training pipeline after #126330 (Migrate CLR to COM stubs to be IL stubs) and #125352 (Move struct marshaling to transient IL) flowed through, which routed new categories of stubs through the minimal-MT path and added their BulkType events to the trace. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../TraceRuntimeDescToTypeSystemDesc.cs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/coreclr/tools/dotnet-pgo/TraceRuntimeDescToTypeSystemDesc.cs b/src/coreclr/tools/dotnet-pgo/TraceRuntimeDescToTypeSystemDesc.cs index c7513ad80c1478..da460a4cddf761 100644 --- a/src/coreclr/tools/dotnet-pgo/TraceRuntimeDescToTypeSystemDesc.cs +++ b/src/coreclr/tools/dotnet-pgo/TraceRuntimeDescToTypeSystemDesc.cs @@ -405,7 +405,20 @@ public TypeDesc ResolveTypeHandle(long handle, ref bool dependsOnKnownNonLoadabl throw new Exception($"Invalid typedef {tinfo.TypeValue.TypeNameID:4x}"); } - TypeDefinitionHandle typedef = MetadataTokens.TypeDefinitionHandle(tinfo.TypeValue.TypeNameID & 0xFFFFFF); + int typedefRow = tinfo.TypeValue.TypeNameID & 0xFFFFFF; + // The runtime emits BulkType events for "minimal" MethodTables created by + // CreateMinimalMethodTable in coreclr (used for Reflection.Emit DynamicMethod + // hosts and the IL stub cache). These MTs have no typedef token and surface + // here as TypeNameID == 0x02000000 (rid 0). Treat them as unresolvable + // dynamic types so trace processing can continue. Out-of-range rids (which + // would happen if the trace and reference assembly are mismatched) are + // handled the same way. + if (typedefRow == 0 || typedefRow > ecmaModule.MetadataReader.GetTableRowCount(TableIndex.TypeDef)) + { + dependsOnKnownNonLoadableType = true; + return null; + } + TypeDefinitionHandle typedef = MetadataTokens.TypeDefinitionHandle(typedefRow); MetadataType uninstantiatedType = (MetadataType)ecmaModule.GetType(typedef); // Instantiate the type if requested if ((tinfo.TypeValue.TypeParameters.Length != 0) && uninstantiatedType != null) From fe452d2896ef7432d98e8c02de24c88c73087ca1 Mon Sep 17 00:00:00 2001 From: Drew Scoggins Date: Wed, 20 May 2026 11:12:18 -0700 Subject: [PATCH 2/3] Warn on out-of-range TypeDef rid instead of silently ignoring Split the typedefRow == 0 (minimal MethodTable) and out-of-range rid cases into separate checks. The rid == 0 case is expected for dynamic types and remains silent. Out-of-range rids now emit a warning via Program.PrintWarning so users can diagnose trace/assembly mismatches. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../dotnet-pgo/TraceRuntimeDescToTypeSystemDesc.cs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/coreclr/tools/dotnet-pgo/TraceRuntimeDescToTypeSystemDesc.cs b/src/coreclr/tools/dotnet-pgo/TraceRuntimeDescToTypeSystemDesc.cs index da460a4cddf761..fa69e065fb195c 100644 --- a/src/coreclr/tools/dotnet-pgo/TraceRuntimeDescToTypeSystemDesc.cs +++ b/src/coreclr/tools/dotnet-pgo/TraceRuntimeDescToTypeSystemDesc.cs @@ -410,14 +410,20 @@ public TypeDesc ResolveTypeHandle(long handle, ref bool dependsOnKnownNonLoadabl // CreateMinimalMethodTable in coreclr (used for Reflection.Emit DynamicMethod // hosts and the IL stub cache). These MTs have no typedef token and surface // here as TypeNameID == 0x02000000 (rid 0). Treat them as unresolvable - // dynamic types so trace processing can continue. Out-of-range rids (which - // would happen if the trace and reference assembly are mismatched) are - // handled the same way. - if (typedefRow == 0 || typedefRow > ecmaModule.MetadataReader.GetTableRowCount(TableIndex.TypeDef)) + // dynamic types so trace processing can continue. + if (typedefRow == 0) { dependsOnKnownNonLoadableType = true; return null; } + int typeDefRowCount = ecmaModule.MetadataReader.GetTableRowCount(TableIndex.TypeDef); + if (typedefRow > typeDefRowCount) + { + Program.PrintWarning($"TypeDef rid 0x{typedefRow:X} in module '{ecmaModule}' is out of range (max {typeDefRowCount}). " + + "The trace and reference assembly may be mismatched."); + dependsOnKnownNonLoadableType = true; + return null; + } TypeDefinitionHandle typedef = MetadataTokens.TypeDefinitionHandle(typedefRow); MetadataType uninstantiatedType = (MetadataType)ecmaModule.GetType(typedef); // Instantiate the type if requested From b33775c3677282f53aebc01f0451a6133f109930 Mon Sep 17 00:00:00 2001 From: Drew Scoggins Date: Wed, 20 May 2026 11:32:44 -0700 Subject: [PATCH 3/3] Remove out-of-range rid guard; let GetType throw naturally The callers already wrap resolution in try/catch and report exceptions. Silently masking a mismatch as a non-loadable type hides real errors. Dropping the range check is consistent with the rest of the method which throws for invalid trace content (e.g. Invalid TypeParameterCount). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../tools/dotnet-pgo/TraceRuntimeDescToTypeSystemDesc.cs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/coreclr/tools/dotnet-pgo/TraceRuntimeDescToTypeSystemDesc.cs b/src/coreclr/tools/dotnet-pgo/TraceRuntimeDescToTypeSystemDesc.cs index fa69e065fb195c..c1a0e142e8c8d0 100644 --- a/src/coreclr/tools/dotnet-pgo/TraceRuntimeDescToTypeSystemDesc.cs +++ b/src/coreclr/tools/dotnet-pgo/TraceRuntimeDescToTypeSystemDesc.cs @@ -416,14 +416,6 @@ public TypeDesc ResolveTypeHandle(long handle, ref bool dependsOnKnownNonLoadabl dependsOnKnownNonLoadableType = true; return null; } - int typeDefRowCount = ecmaModule.MetadataReader.GetTableRowCount(TableIndex.TypeDef); - if (typedefRow > typeDefRowCount) - { - Program.PrintWarning($"TypeDef rid 0x{typedefRow:X} in module '{ecmaModule}' is out of range (max {typeDefRowCount}). " + - "The trace and reference assembly may be mismatched."); - dependsOnKnownNonLoadableType = true; - return null; - } TypeDefinitionHandle typedef = MetadataTokens.TypeDefinitionHandle(typedefRow); MetadataType uninstantiatedType = (MetadataType)ecmaModule.GetType(typedef); // Instantiate the type if requested