Skip to content

Conversation

@leon-xd
Copy link
Contributor

@leon-xd leon-xd commented Feb 13, 2026

Adds and integrates wdk::fmt::WdkFormatBuffer.

Summary

  • Introduced crates/wdk/src/fmt.rs with a fixed-size, stack-friendly formatting buffer (WdkFormatBuffer<T>) implementing fmt::Write.
  • Added methods to borrow buffer as core::ffi::CStr and &str alongside tests to ensure correctness.
  • Re-exported WdkFormatBuffer from wdk::fmt via lib.rs for WDM/KMDF/UMDF driver models.
  • [BREAKING CHANGE] Swapped print.rs kernel path to use WdkFormatBuffer instead of DbgPrintBufWriter; removed that module and its tests.

Details

  • WdkFormatBuffer<T = 512> stores a zero-initialized [u8; T] and tracks used bytes. Default delegates to new() for convenience.
  • as_str() returns a UTF-8 view over written bytes only.
  • as_cstr() ensures a trailing NUL when the buffer is filled; returns FromBytesUntilNulError on missing terminator (e.g., T == 0).
  • fmt::Write implementation clamps writes to capacity and signals truncation via fmt::Error.
  • print.rs kernel path now formats into WdkFormatBuffer and calls DbgPrint directly using the resulting CStr. The previous dbg_print_buf_writer module and its flush logic/tests were removed. This results in different behavior when handling overflowing buffers.
  • Added comprehensive tests covering:
    • basic write and as_str()/as_cstr() usage
    • overflow and multi-write scenarios
    • exact-fit writes, empty writes, and zero-sized buffers
    • reference borrowing sanity checks

Rationale

  • Provides a heap-free formatting utility suitable for driver environments.
  • Exposing via wdk::fmt (and pub use fmt::WdkFormatBuffer) removes dead-code warnings and makes the type available to consumers.

Testing

cargo test --package wdk --lib --all-features -- fmt::test --nocapture

Notes / Follow-ups

  • We currently ignore fmt::write errors and silently return on as_cstr failure. A future improvement could be a chunked/flush API on WdkFormatBuffer to mimic prior semantics.

Copilot AI review requested due to automatic review settings February 13, 2026 00:10
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a new fixed-size, stack-friendly formatting buffer (WdkFormatBuffer) to support heap-free formatting in driver contexts, and integrates it into the kernel print!/println! path by replacing the previous DbgPrintBufWriter implementation.

Changes:

  • Added crates/wdk/src/fmt.rs implementing WdkFormatBuffer<const T: usize = 512> with fmt::Write, plus as_str() / as_cstr() helpers and tests.
  • Updated crates/wdk/src/print.rs (WDM/KMDF path) to format into WdkFormatBuffer and call DbgPrint directly; removed the old buffering/flushing module.
  • Adjusted the KMDF sample driver to add an explicit type annotation for the WdfDriverCreate output handle pointer.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 7 comments.

File Description
examples/sample-kmdf-driver/src/lib.rs Tweaks the WdfDriverCreate out-handle variable typing.
crates/wdk/src/print.rs Switches kernel printing to WdkFormatBuffer + direct DbgPrint.
crates/wdk/src/lib.rs Adds the new fmt module and re-exports WdkFormatBuffer.
crates/wdk/src/fmt.rs New formatting buffer type, CStr/str views, and unit tests.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +71 to +92
CStr::from_bytes_until_nul(&self.buffer)
// PR comments -- I tried to make sure that my implementation never
// passed a FromBytesUntilNulError, and the function just returned a
// &CStr. But handling this error here caused a panic which
// feels too strict given the driver environment since that would cause
// a bugcheck for kernel drivers. Determined that the most
// elegant way was for the user to always handle the errors but I'm open
// to feedback here.
//
// // if I do implement this way how should I handle an error case here?
// feels too strong to panic but this case cannot happen as the current
// logic stands. if cstr.is_err() {
// unreachable!("Buffer should always contain a trailing null
// byte"); }
// cstr.unwrap_or_else(| _err | {
// // SAFETY: We pass a single null byte into
// `from_bytes_with_nul_unchecked`. This means that it is null
// terminated and has no interior null bytes. unsafe{
// CStr::from_bytes_with_nul_unchecked(b"\0")
// }
// })
}
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as_cstr contains a large block of PR discussion and commented-out code after the return expression. This will ship as part of the crate source and makes the method hard to read/maintain; it should be removed or rewritten as a concise doc comment explaining the chosen behavior and invariants.

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will remove this after getting comments from a human :)

Comment on lines +60 to +71
/// Returns a C string view up to the first `NUL` byte.
///
/// Ensures termination by writing a `NUL` if the buffer is completely
/// filled.
///
/// # Errors
/// Returns an error only if no terminator is found, e.g. if `T == 0`.
pub const fn as_cstr(&mut self) -> Result<&CStr, FromBytesUntilNulError> {
if self.used == T && T != 0 {
self.buffer[self.used - 1] = 0;
}
CStr::from_bytes_until_nul(&self.buffer)
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as_cstr mutates the buffer when used == T by overwriting the last written byte with NUL, which silently drops one byte of output and leaves used unchanged (so a subsequent as_str() call can include a trailing \0 and miss the last character). Consider reserving space for the terminator up-front (cap writes at T-1 and keep buffer[T-1] == 0), or have as_cstr() return an error when the contents cannot be represented as a C string without truncation.

Suggested change
/// Returns a C string view up to the first `NUL` byte.
///
/// Ensures termination by writing a `NUL` if the buffer is completely
/// filled.
///
/// # Errors
/// Returns an error only if no terminator is found, e.g. if `T == 0`.
pub const fn as_cstr(&mut self) -> Result<&CStr, FromBytesUntilNulError> {
if self.used == T && T != 0 {
self.buffer[self.used - 1] = 0;
}
CStr::from_bytes_until_nul(&self.buffer)
/// Returns a C string view over the written bytes, up to the first `NUL`.
///
/// This method does not modify the buffer. It inspects only the portion of
/// the buffer that has been written (`used` bytes) when searching for the
/// terminator.
///
/// # Errors
/// Returns an error if no terminator is found in the written bytes (for
/// example, if `used == 0` or if the contents contain no `NUL`).
pub const fn as_cstr(&mut self) -> Result<&CStr, FromBytesUntilNulError> {
CStr::from_bytes_until_nul(&self.buffer[..self.used])

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah fair, I'll wait for human input for this one. want to balance the expectations of having Rust-only strings, but given the developer experience required to build in WDR this might be over compensating for a non-helpful use case. had a comment somewhere about this design decision.

Comment on lines +101 to +110
impl<const T: usize> fmt::Write for WdkFormatBuffer<T> {
fn write_str(&mut self, s: &str) -> fmt::Result {
if s.len() + self.used >= T {
self.buffer[self.used..T].copy_from_slice(&s.as_bytes()[..T - self.used]);
self.used = T;
return Err(fmt::Error);
}
self.buffer[self.used..self.used + s.len()].copy_from_slice(s.as_bytes());
self.used += s.len();
Ok(())
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fmt::Write::write_str returns Err(fmt::Error) when s.len() + used == T even though it copies the entire string into the buffer. That makes exact-fit writes look like failures (e.g., write!(&mut buf, ...) will return Err/panic on unwrap() even though no truncation occurred for as_str). Align the boundary/return semantics with the intended capacity rules (e.g., allow exact-fit for as_str, or clearly reserve T-1 for C strings and only return Err when bytes are actually dropped).

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is reflected in my test cases as ideal behavior since converting to cstr will truncate. just need feedback on design before making any changes.

Comment on lines +93 to 108
let mut buffered_writer: WdkFormatBuffer = WdkFormatBuffer::new();
// We do not care whether this is `Ok` or `Err` right now. If `Err` will simply write all until overflow.
// TODO: create custom `flush` param in `WdkFormatBuffer` to preserve old functionality
let _ = fmt::write(&mut buffered_writer, args);

let cstr_buffer = match buffered_writer.as_cstr() {
Ok(cstr) => cstr,
Err(_e) => return, // silently return on error, no null terminator. (Should this be a placeholder string for debugging purposes?)
};

if fmt::write(&mut buffered_writer, args).is_ok() {
buffered_writer.flush();
} else {
unreachable!("DbgPrintBufWriter should never fail to write");
unsafe {
wdk_sys::ntddk::DbgPrint(
c"%s".as_ptr().cast(),
cstr_buffer.as_ptr().cast::<wdk_sys::CHAR>(),
);
}
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The WDM/KMDF docstrings above print!/println! still claim the 512-byte DbgPrint limit "does not apply" because output is chunked, but the new implementation formats once into a fixed buffer and calls DbgPrint a single time (overflow is truncated). Also, _print is documented as panicking on interior NULs, but the new CStr-based path will just stop at the first NUL. Please update the docs (and/or implementation) so behavior matches the documented guarantees.

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

great point!

Comment on lines +39 to +52
#[cfg(any(
driver_model__driver_type = "WDM",
driver_model__driver_type = "KMDF",
driver_model__driver_type = "UMDF"
))]
mod fmt;

#[cfg(any(
driver_model__driver_type = "WDM",
driver_model__driver_type = "KMDF",
driver_model__driver_type = "UMDF"
))]
pub use fmt::WdkFormatBuffer;

Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fmt is declared as a private module (mod fmt;), but the docs/PR description reference wdk::fmt::WdkFormatBuffer. As-is, downstream users cannot access wdk::fmt::* and the doctest path in fmt.rs will not compile. Make the module public (pub mod fmt;) and optionally keep the root pub use, or update the docs/exports to match the intended public path.

Copilot uses AI. Check for mistakes.
@codecov-commenter
Copy link

codecov-commenter commented Feb 13, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 77.41%. Comparing base (8046305) to head (8497b85).
⚠️ Report is 2 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #611      +/-   ##
==========================================
+ Coverage   76.52%   77.41%   +0.89%     
==========================================
  Files          24       24              
  Lines        4881     4853      -28     
  Branches     4881     4853      -28     
==========================================
+ Hits         3735     3757      +22     
+ Misses       1029      979      -50     
  Partials      117      117              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@leon-xd leon-xd changed the title feat!: stack-based allocator for WDK crate feat!: stack-based allocator for wdk crate Feb 13, 2026
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.

2 participants