Skip to content

Fix null CustomDebugInfos in PdbDocument constructors - #585

Open
pcshrosbree wants to merge 1 commit into
0xd4d:masterfrom
pcshrosbree:fix/pdbdocument-customdebuginfos
Open

pcshrosbree wants to merge 1 commit into
0xd4d:masterfrom
pcshrosbree:fix/pdbdocument-customdebuginfos

Conversation

@pcshrosbree

Copy link
Copy Markdown

Fixes #584

new PdbDocument() and the six-argument constructor left CustomDebugInfos null. Writing a Portable PDB that contains such a document then threw NullReferenceException in Metadata.AddCustomDebugInformationList. This PR creates the list in those two constructors.

Why this shape: 628db9b replaced the field initializer with an assignment in Initialize(SymbolDocument). When PdbState reads a PDB, it creates a partial document for each lookup (including one per sequence point) and calls Initialize only for documents it has not seen before. So a lookup that finds an existing document allocates no list. This change keeps that: the private constructor behind the partial document is untouched, and documents read from a PDB still get their list in Initialize. Restoring the field initializer would allocate a list on every lookup. Making the writer null-tolerant would still leave doc.CustomDebugInfos.Add(...) throwing on a new document.

What I ran (dnlib's net6.0 build, .NET SDK 10.0.401, Linux x64):

  • The repro below prints True and throws NullReferenceException at master. With this change it prints False and writes the module and the PDB.
  • In a larger probe using the six-argument constructor, I read the resulting PDB back with System.Reflection.Metadata. It has one Document row with the given name, hash algorithm, hash and language, and no CustomDebugInformation rows. A document from new PdbDocument { ... } gives the same result.
  • I also added an EmbeddedSource custom debug info to a new document. At master, CustomDebugInfos.Add throws. With this change, the PDB has one CustomDebugInformation row whose parent is that document, with the same blob.
  • Loaded documents are unaffected. I loaded a Roslyn-built library with embedded sources (each of its four documents has an EmbeddedSource custom debug info) together with its PDB, then wrote it again. The PDB written with this change is byte-identical to the one written at master. The two DLLs differ only in the CodeView PDB path and its SizeOfData field, because I wrote them to different directories.
  • Each constructor change is needed: reverting either one alone brings the exception back for documents made with that constructor, and only for those.

Read, not run: the Windows PDB writer also dereferences the null list, when it emits a new document that a method's sequence point uses. WindowsPdbWriter.Add(PdbDocument) calls TryGetCustomDebugInfo, which reads CustomDebugInfos.Count. I couldn't run that path, because it needs the native DiaSymReader and I'm on Linux.

dnlib has no test project, so this PR adds no test. Here is the repro:

using System;
using dnlib.DotNet;
using dnlib.DotNet.Pdb;
using dnlib.DotNet.Writer;

var mod = new ModuleDefUser("Repro.dll") { Kind = ModuleKind.Dll };
new AssemblyDefUser("Repro", new Version(1, 0, 0, 0)).Modules.Add(mod);
mod.CreatePdbState(PdbFileKind.PortablePDB);

var doc = new PdbDocument("/src/C.cs", PdbDocumentConstants.LanguageCSharp,
	PdbDocumentConstants.LanguageVendorMicrosoft, PdbDocumentConstants.DocumentTypeText,
	PdbDocumentConstants.HashSHA256, new byte[32]);
Console.WriteLine(doc.CustomDebugInfos is null); // True at master, False with this change
mod.PdbState.Add(doc);

mod.Write("Repro.dll", new ModuleWriterOptions(mod) { WritePdb = true }); // throws at master

new PdbDocument() and the six-argument constructor left CustomDebugInfos
null; only Initialize(SymbolDocument) assigned it. The Portable PDB
writer reads CustomDebugInfos.Count for every document, so writing a PDB
that contains such a document threw NullReferenceException, and a caller
could not add custom debug info to a new document.

Create the list in those two constructors. The partial document that
PdbState creates to look up an existing document still allocates no
list, and documents read from a PDB still get theirs in Initialize().
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

New PdbDocument has null CustomDebugInfos, so writing a Portable PDB throws NullReferenceException

1 participant