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

Audit use of unsafe in header/name.rs #428

Closed
wants to merge 8 commits into from

Conversation

sbosnick
Copy link
Contributor

This PR documents and improves the use of unsafe in the header::name module. That module has two kinds of usages of unsafe: the use of uninitialized memory and calls to ByteStr::from_utf8_unchecked().

The uninitialized memory in header::name is used as a temporary buffer when normalizing the bytes that represent a name before matching them against the standard header names. This PR upgrades the uninitialized memory from the deprecated mem::unititialized() to an array of MaybeUninit<u8> . This upgrade has the useful side effect of mostly localizing the use of unsafe related to uninitialized memory to the two implementations of parse_hdr().

The PR documents (through comments) the preconditions and post-conditions that make the use of uninitialized memory sound. The performance sensitive nature of parse_hdr() make these comments harder to follow than they might have been had it been possible to refactor parse_hdr() without causing a performance regression (I started such a refactoring but backed off when it caused regressions). The documented preconditions and post-conditions show the reasoning on how each MaybeUninit<u8> is initialized before it is accessed for reading and before it is used as a part of the return from parse_hdr().

The PR also document the preconditions, post-conditions, and invariant that make the use of unsafe related to ByteStr::from_utf8_unchecked sound. These comments are also more extensive than they might have been because of the performance sensitive nature of parse_hdr().

Finally the PR adds a new set of micro-benchmarks related to parsing the standard header names.

The new benchmarks make use of the criterion micro-benchmarking framework. This adds a new dev-dependency which is a major downside of this change. I suggest, though, that this downside is offset by the following upsides:

  • criterion allows for running benchmarks on stable
  • criterion makes it easy to run performance comparisons with either the last benchmark run or with a named baseline (and reports statistically significant improvements or regressions)
  • criterion allows for more reliable benchmarks because of its separate warmup and measuring phases
  • criterion is a well written, well designed, and actively maintained crate

This is part of #412.

sbosnick added 8 commits May 9, 2020 17:38
The use of MaybeUninit to replace the depreciated mem::uninitialized()
is supported because the minimum supported version of Rust is 1.39. This
change moves most of the uninitialized memory related use of unsafe into
the parse_hdr() function.
The general pattern follows the "Initilizing an array
element-by-element" example in the documentation of MaybeUninit. This
change removes some unnecessary use of unsafe.
This introduces a dev-dependency on the criterion crate. The new
benchmark for HeaderName uses different standard header names of
increasing lengths.
A previous commit had extracted the comparison of a presumed-initialized
MaybeUninit<u8> and a static u8 to its own function. While clearer, this
lead to a performance regression so this commit manually inlines this
method again.

This restores most (but not yet all) of the performance that regressed.
Move the eq! macro into the parsh_hdr() function which locallized all of
the unsafe code related to MaybeUninit.
The comments describe the pre-condition and post-conditions on the
various different parts of the two parse_hdr() implementations that
combine to make the use of MaybeUninit in that function sound.

The process of assessing the soundness of the use of MaybeUninit also
included manually checking that the number of parameters to each all
eq!() invocation matches the number of bytes initilized by the
immediatly preceeding invocation of to_lower!() (which is necessary to
avoid undefined behavior). To avoid being overly repetative the general
pattern that assures soundness is documented in the comments but not
each instance of that pattern. Each instance, though, was checked.
The comments document the invariant, preconditions, and post-conditions
that together ensure that the use of unsafe related to UTF-8 assumptions
(in calls to ByteStr::from_utf8_unchecked()) are sound.
@saethlin
Copy link

saethlin commented Feb 5, 2022

@seanmonstar It's been a little while since this PR was opened. Is there anything community members can do to move this PR forward?

jeddenlea added a commit to jeddenlea/http that referenced this pull request Apr 1, 2022
@jeddenlea jeddenlea mentioned this pull request Apr 1, 2022
jeddenlea added a commit to jeddenlea/http that referenced this pull request Apr 6, 2022
@LucioFranco
Copy link
Member

I think it makes sense to merge #499 then apply this patch on top since a lot of code was removed in #499 that was touched here.

jeddenlea added a commit to jeddenlea/http that referenced this pull request Apr 15, 2022
@seanmonstar
Copy link
Member

Merged in #555 (thanks so much!)

@seanmonstar seanmonstar closed this Jun 6, 2022
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.

4 participants