Repository navigation
fix(cpp): a nullptr argument picks the std::nullptr_t overload, and no call links to a deleted overload - #2554
Merged
Conversation
…o call links to a deleted overload
A `nullptr` argument fitted any pointer parameter better than a
`std::nullptr_t` one, which counted as a class, so protobuf's
`ptr.reset(nullptr)` on an `Arena::UniquePtr` took `reset(T*)` over
`reset(std::nullptr_t)`. That `reset(T*)` is declared `= delete`, as is
`RepeatedPtrField::Add(const Element&)`, which `field.Add("foo")` reached.
`nullptr` now fits `std::nullptr_t` exactly and a pointer by conversion, and
an overload declared deleted is taken only when every overload is.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.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
Two ways a C++ call reached an overload that no compiling call reaches:
nullptragainststd::nullptr_t.cppArgumentTypeFitscored anullptrliteral 3 against any pointer parameter. It scored only 1 against astd::nullptr_tparameter, whose type counted as a class. protocolbuffers/protobuf'sArena::UniquePtr<T>declaresreset(),reset(std::nullptr_t)andreset(T* PROTOBUF_NULLABLE) = delete. Soptr.reset(nullptr)took the pointer overload. C++ takesreset(std::nullptr_t), an exact match, over a pointer conversion.reset(T*)is deleted, and so isRepeatedPtrField<Element>::Add(const Element&), besideAdd(Element&&). Eighteenfield.Add("foo")-style calls in protobuf's tests reached the deleted one. When overload resolution in C++ selects a deleted function, the program is ill-formed, so no compiling call reaches one.This came out of the interaction A/B for the #2536–#2542 batch, where
reset(nullptr)was the one wrong composed result.What changed
All in
name-matcher.ts:cppTypeCategory:std::nullptr_thas a category of its own,null.CPP_LITERAL_FIT:nullptrscores 4 againststd::nullptr_t, its own type, above a pointer's 3;0scores 1 against it, below an integer's 2;boolcan't become one (-3).cppOverloadFor: overloads declared deleted are dropped, unless every overload is.isCppDeletedFunctionreads a declaration from its name to its;or body for= delete, memoized per node and cleared with the other C++ memos.Validation
Site-keyed edge diffs of main
24286218against this branch. Both arms were built from one tree, kernel included.Node and file counts are identical. All 19 are right:
ptr.reset(nullptr)(arena_unittest.cc) reachesreset(std::nullptr_t)instead of the deletedreset(T*).RepeatedPtrField::Addcalls in repeated_ptr_field_unittest.cc reachAdd(Element&&), by its out-of-line definition, instead of the deletedAdd(const Element&). They includefield.Add("foo"),field.Add(std::move(test_all_types)),rep.Add(v)on aconst char*, andelements.Add(absl::StrCat(…)).Tests
__tests__/cpp-nullptr-deleted-overloads.test.ts, 3 tests:ptr.reset(nullptr)reachesreset(std::nullptr_t)andptr.reset()reachesreset(). With both fixes off, as on main, it fails.Set(nullptr)againstSet(Widget*),Set(std::nullptr_t)andSet(int), with nothing deleted. It reachesSet(std::nullptr_t), andSet(0)reachesSet(int).Scale(2)against a deletedScale(double),Scale(int), and a deletedScale(long)declared over two lines. It reachesScale(int).Mutation check (each guard broken alone; every one fails a test, at its own assertion):
std::nullptr_tis its own categorySet(nullptr)nullptrfitsstd::nullptr_tover a pointer (4 vs 3)Set(nullptr)0fits anintoverstd::nullptr_tSet(0)Scale(2);or bodyScale(2)(Scale(int)would read the deletedScale(long)after it)In protobuf's case either fix alone is enough, so each has a fixture only it decides.
Full suite: 7,047 passed, 82 skipped. The 9 failures, in 3 files and all from load on this machine, pass when those files run alone (24/24).
🤖 Generated with Claude Code