Fix NullReferenceException for null optional non-blittable struct parameters#1740
Draft
jevansaks wants to merge 1 commit into
Draft
Fix NullReferenceException for null optional non-blittable struct parameters#1740jevansaks wants to merge 1 commit into
jevansaks wants to merge 1 commit into
Conversation
Optional [In] pointer parameters to non-blittable (managed) structs were emitted with an `in` modifier because a pointer to a managed type is illegal. The friendly overload then passed `ref Unsafe.NullRef<T>()` on the null path, which caused the P/Invoke marshaler to dereference a null reference and throw a NullReferenceException (e.g. MiniDumpWriteDump(..., CallbackParam: null)). Emit such parameters as a `T[]` array instead, as is already done for non-blittable struct fields. A null array marshals to a null pointer and a single-element array marshals to a pointer to the struct. The friendly overload continues to expose the parameter as a nullable `T?`. Fixes #1739 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes #1739.
Calling an extern with a
nulloptional pointer-to-non-blittable-struct parameter threw aNullReferenceExceptioninstead of passing a null pointer. The canonical repro from the issue:Root cause
MINIDUMP_CALLBACK_INFORMATIONis non-blittable in the defaultAllowMarshalingmode because it contains a delegate field (MINIDUMP_CALLBACK_ROUTINE). A pointer to a managed type is illegal, so CsWin32 fell back to emitting the extern parameter with aninmodifier. The friendly overload exposed it asT?and took the null path viaref Unsafe.NullRef<T>(). Because the struct is non-blittable, the P/Invoke marshaler must build a native copy and dereferences the null reference while marshaling it, throwingNullReferenceException.This affects any optional
[In](not[Out]) parameter that points to a non-blittable struct, not justMiniDumpWriteDump.MiniDumpWriteDumpwas actually the motivating case for the original optional-pointer feature (#578), but its test only checked the friendly signature shape, never the runtime null behavior.Fix
Emit such parameters as a
T[]array instead of aninparameter — exactly what CsWin32 already does for non-blittable struct fields:nullarray marshals to a null pointer.The friendly overload continues to expose the parameter as a nullable
T?, and now forwardsparam.HasValue ? new[] { param.Value } : nullto the extern. The public friendly API is unchanged (stillT?); only the internal extern signature changes fromin TtoT[]for these non-blittable cases.Generated code before/after (friendly overload argument for
CallbackParam):Blittable optional struct pointers (e.g.
ExceptionParam,UserStreamParam) are unaffected and keep the existing pointer projection.Tests
GenerationSandbox.Teststhat actually callMiniDumpWriteDump:NullOptionalPointerParametersDoNotThrow— the regression:CallbackParam: nullnow succeeds instead of throwing.CallbackParameterIsMarshaled— passes a real callback and asserts it is invoked, exercising the single-element-array marshaling path.GeneratorTests.MiniDumpWriteDump_AllOptionalPointerParametersAreOptionalcontinues to pass (friendly params are stillT?).Validation
0x1(success) withCallbackParam: null, no NRE.Everything_NoFriendlyOverloadsand theFullMarshaling"Everything" generations pass (all APIs + friendly overloads still compile).