Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NonZero constructor in debug-builds should assert input is actually nonzero. #31217

Closed
pnkfelix opened this issue Jan 26, 2016 · 13 comments
Closed
Labels
C-enhancement Category: An issue proposing an enhancement or a PR with one. T-libs-api Relevant to the library API team, which will review and decide on the PR/issue.

Comments

@pnkfelix
Copy link
Member

NonZero constructor in debug-builds should assert input is actually nonzero.

In particular, I am a bit concerned by the somewhat free-wheeling way that we are adding uses of the unsafe NonZero::new constructor function without any checks that the given input actually is non-zero.

I freely admit that I do not have many examples of current bugs that such a check would have prevented. However, I can point to some questionable code in a previous iteration of the libstd btree code that such a check would have at least caught: prior to PR #30426, the btree node.rs code had this line:

self.keys = unsafe { Unique::new(ptr::null_mut()) };

Was this a bug? Well, ... according to the invariants of Unique and NonZero, the keys.is_null() check should in principle be optimizable to be "always false", right?

  • The assignment occurs at the end of a drop method implementation, but the struct in question was marked #[unsafe_no_drop_flag], and so that drop code can (and will) be run repeatedly, so that keys.is_null() call can well occur in a context where the data is in a "bad state"
  • (indeed, the whole point of the keys.is_null() check is to catch the null being used as a sentinel value, even though it is in fact illegal in that context.)
@pnkfelix
Copy link
Member Author

To actually put in this check, the language would need to be extended to some degree, since NonZero::new is marked as a const fn, and thus does not have an easy way to embed a debug_assert! statement before the trailing expression.

(I have discussion here rust-lang/rfcs#1383 of potentially trying to change/extend const evaluation to support debug_assert! so that it could be used with const-evaluatable expressions, and cause compilation failure when the assert fails.)

@pnkfelix
Copy link
Member Author

another necessary extension to the language implementation would probably be #28809 (see why in #28809 (comment) )

@pnkfelix
Copy link
Member Author

cc #27730

@pnkfelix
Copy link
Member Author

In case its not clear, I strongly encourage the libs team to not stabilize NonZero until this is addressed in some way.

@pnkfelix
Copy link
Member Author

(and see also #27573 )

@eddyb
Copy link
Member

eddyb commented Jan 29, 2016

NonZero::new should return Option<Self> and we could maybe provide a new_unchecked method, if really needed.

@Gankra
Copy link
Contributor

Gankra commented Jan 29, 2016

new_unchecked is literally the only thing a consumer would ever use. You're asserting the value is statically NonZero in low-level unsafe code.

Note that adding the debug assert is an exception-safety concern -- though it will only trigger if you were about to do UB, so it's ok I guess?

@eddyb
Copy link
Member

eddyb commented Jan 29, 2016

@gankro How do you know the value is non-zero to begin with?

@apasel422
Copy link
Contributor

@eddyb One place this could be used where the value is known to be non-zero is with HashMap's SafeHash.

@Gankra
Copy link
Contributor

Gankra commented Feb 1, 2016

@eddyb usually because we're already checking -- e.g. it came out of an allocator and we didn't abort. Unique is pretty universally used for "we're Box but not".

The problem code exists because someone was using null as a sentinel instead of heap::EMPTY for whatever reason. Probably because of the old zeroing drop?

@eddyb
Copy link
Member

eddyb commented Feb 1, 2016

@gankro There's your answer. If it's known to be not NULL, then the allocator should return NonZero<*mut u8> or even Unique.
The allocator is what uses NonZero::new and matches on the result.

@Gankra
Copy link
Contributor

Gankra commented Feb 1, 2016

Yep, that'd be great. 's in the allocator API RFC to some extent already.

@steveklabnik steveklabnik added T-libs-api Relevant to the library API team, which will review and decide on the PR/issue. and removed A-libs labels Mar 24, 2017
@Mark-Simulacrum Mark-Simulacrum added the C-enhancement Category: An issue proposing an enhancement or a PR with one. label Jul 24, 2017
@steveklabnik
Copy link
Member

Triage: NonZero has been replaced with many types, like NonZeroU8. These have both checked and unsafe unchecked constructors. Closing!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
C-enhancement Category: An issue proposing an enhancement or a PR with one. T-libs-api Relevant to the library API team, which will review and decide on the PR/issue.
Projects
None yet
Development

No branches or pull requests

6 participants