I hate to be the that OpenBSD guy, but "the people who do the work are the ones to decide how it's done". Yes, people are paid to maintain OpenZFS, but so far nobody is ready to pay for (or volunteer to) meet your bar.
Side note: OpenZFS already has an extensive test suite. Merely hitting a code branch wouldn't have caught this one.
I'm not sure why C is blamed in this case when you can do exactly the same strong typing fix in C, and with C99 struct literals it's also not much worse to work with:
...and in reverse, Rust wouldn't protect you from that exact same bug if the programmer decided to use usize like in the original C code.
IME overly strong typing is a double-edged sword though, on one hand it makes the code more robust, but on the other hand also more 'change resistant'.
I still would like to see a strong typedef variant in C so that the struct wrapping wouldn't be needed, e.g.:
I missed it, but I was distracted by cols variable being initialised with the original width, but then being immediately overwritten with the logical width.
Wouldn't this have been caught by an exhaustive unit test or even a fuzz test? I don't know what kind of testing practices are applied to projects like zfs, nor what kinds or amounts of tests functions like this are subject to, but I would imagine that especially for low-level functions like this, unit tests with all of the known edge cases would be written.
(yes this is very much an armchair opinion, I mostly do front-end JS for a living)
I found it. All that tells you is that it's a simple problem. Had I not been told it was broken I likely would not have.
It's the kind of bug that makes you stop breathing for a brief moment. So I ran this function through Gemini 2.5 Pro, ChatGPT o3 and Grok 3. No context other than the source code fragment was provided. All three also clearly identified the problem.
There is no longer a reason for trivial flaws such as this surviving to release code. We're past that now, and that's an astonishing thing.
These are the questions I ponder: Should we consider the possibility that the ongoing, incomplete and decades long pursuit of "better" languages is now misguided? Or, that perhaps "better" might now mean whatever properties make code easier for LLMs to analyze and validate against specifications, as opposed to merely preventing the flaws humans are prone to make?
The pursuit of "better" languages might be incomplete, but it has long been known that this specific problem can be solved by the newtype pattern. It might have originated in Haskell but it's equally applicable in more traditional languages like C++. All you need is to define a named struct containing an integer called PhysicalSize and another one called AllocatedSize. In C++ add operator overloading to it so you can do arithmetic. In Haskell add instances such as Integral and Num.
Having run into similar problems several times, I've also never really been satisfied with the solutions. You have to have different types to cause compile errors, but then you have throw those types away whenever you perform any actual operations on them.
struct TypeA { int inner; };
struct TypeB { int inner; };
struct TypeA a1, a2;
// whoops
TypeB result = {.inner = a1.inner + a2.inner};
Don't get me wrong, where I've used this sort of pattern it's definitely caught issues, and it definitely massively lowers the surface area for this type of bug. But I still feel like it's quite fragile.
The use case I felt worked best was seperating out UTC datetime vs localised datetime structs - mainly since it's something you already had to use function calls to add/subtract from, you couldn't play with raw values.
The blog post suggests that, probably because it’s never been used, it’s just too noisy to turn on.
Arguably every unused variable in the code base is a potential bug like this waiting to chomp on some poor user’s data though, so arguably they should be turning it on & dealing with the consequences?
>The provided code snippet is a function that converts an allocation size (asize) into the largest physical size (psize) that can be safely written to an allocation for a given virtual device (vdev). Here are some observations and potential issues in the code:
Return Value: The function currently returns asize instead of psize. This seems to be a mistake, as the purpose of the function is to compute and return the psize. The correct return statement should be:
return psize;
Comment Clarity: The comments mention that the function does not take into account the effect of gang headers. It might be helpful to clarify what "gang headers" are and how they affect the allocation sizes, especially for someone unfamiliar with the context.
Variable Initialization: The variable psize is calculated based on asize, ashift, cols, and nparity. Ensure that these values are correctly initialized and that vdev_raidz_get_logical_width returns a valid value for cols. If cols is zero, it could lead to division by zero in the DIV_ROUND_UP macro.
Assertions: The assertion ASSERT0(asize % (1 << ashift)); checks that asize is a multiple of 2^ashift. If this assertion fails, it will cause the program to terminate. Ensure that this condition is always met before calling this function, or consider handling this case more gracefully.
Macro Usage: The DIV_ROUND_UP macro should be checked to ensure it handles edge cases correctly, such as when psize is zero. If psize is zero, the division could lead to unexpected behavior.
Code Readability: Consider adding more comments to explain the logic behind the calculations, especially for someone who may not be familiar with the RAID-Z implementation.
Error Handling: There is no error handling in the function. Depending on the context in which this function is used, it might be beneficial to add checks for invalid input values and return an error code or handle exceptions accordingly.
Here is a revised version of the function with the return value corrected:
.....(Snipped for formatting issues)
Make sure to test the function thoroughly to ensure it behaves as expected under various conditions.
I'm surprised their testing didn't catch it. And while I get that turning on warnings in a code-base not designed with them can be annoying, `-Wunused` is certainly within reach. I've enabled and maintained `-Weverything` in a 30m LoC C code base that is older than myself. OpenZFS should try harder to enable such warnings in their CI.
For my personal use, I have no need for ZFS on Linux (ext4) or *BSD (UFS/ffs).
I have been playing with NetBSD for a bit and I discovered their FS has a log parameter, using that on mount to me makes their file system almost as good as ext4. Interesting to me it is not advertised too much.
24 comments
[ 3.3 ms ] story [ 41.4 ms ] threadAt the same time, the tooling has gotten much better in the last years.
Clang-analyzer is fast enough to run as part of the CI. Newer gcc also give quite a few more warnings for unused results.
My recommendation to the project is to
- Remove all compiler warnings and enable warning-as-error
- Increase the coverage of unit tests to >80%
That is a lot of work. But that's what is required for high-criticality systems engineering.
Side note: OpenZFS already has an extensive test suite. Merely hitting a code branch wouldn't have caught this one.
IME overly strong typing is a double-edged sword though, on one hand it makes the code more robust, but on the other hand also more 'change resistant'.
I still would like to see a strong typedef variant in C so that the struct wrapping wouldn't be needed, e.g.:
https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3320.htm
(yes this is very much an armchair opinion, I mostly do front-end JS for a living)
It's the kind of bug that makes you stop breathing for a brief moment. So I ran this function through Gemini 2.5 Pro, ChatGPT o3 and Grok 3. No context other than the source code fragment was provided. All three also clearly identified the problem.
There is no longer a reason for trivial flaws such as this surviving to release code. We're past that now, and that's an astonishing thing.
These are the questions I ponder: Should we consider the possibility that the ongoing, incomplete and decades long pursuit of "better" languages is now misguided? Or, that perhaps "better" might now mean whatever properties make code easier for LLMs to analyze and validate against specifications, as opposed to merely preventing the flaws humans are prone to make?
The use case I felt worked best was seperating out UTC datetime vs localised datetime structs - mainly since it's something you already had to use function calls to add/subtract from, you couldn't play with raw values.
If you want free software I recommend IKOS - a is a sound static analyzer for C/C++ developed at NASA. Checks: https://github.com/NASA-SW-VnV/ikos/blob/master/analyzer/REA... Numerical abstract domains: https://github.com/NASA-SW-VnV/ikos/blob/master/analyzer/REA...
Commercial tool like Astree https://www.absint.com/astree/index.htm if you have money.
It seems you are dooming your project the minute you start ignoring your first warning.
I would have expected that the compiler should complain that psize is computed but unused.
Why isn't -Wunused-variable enabled in OpenZFS?
Arguably every unused variable in the code base is a potential bug like this waiting to chomp on some poor user’s data though, so arguably they should be turning it on & dealing with the consequences?
>The provided code snippet is a function that converts an allocation size (asize) into the largest physical size (psize) that can be safely written to an allocation for a given virtual device (vdev). Here are some observations and potential issues in the code:
Return Value: The function currently returns asize instead of psize. This seems to be a mistake, as the purpose of the function is to compute and return the psize. The correct return statement should be:
Comment Clarity: The comments mention that the function does not take into account the effect of gang headers. It might be helpful to clarify what "gang headers" are and how they affect the allocation sizes, especially for someone unfamiliar with the context.Variable Initialization: The variable psize is calculated based on asize, ashift, cols, and nparity. Ensure that these values are correctly initialized and that vdev_raidz_get_logical_width returns a valid value for cols. If cols is zero, it could lead to division by zero in the DIV_ROUND_UP macro.
Assertions: The assertion ASSERT0(asize % (1 << ashift)); checks that asize is a multiple of 2^ashift. If this assertion fails, it will cause the program to terminate. Ensure that this condition is always met before calling this function, or consider handling this case more gracefully.
Macro Usage: The DIV_ROUND_UP macro should be checked to ensure it handles edge cases correctly, such as when psize is zero. If psize is zero, the division could lead to unexpected behavior.
Code Readability: Consider adding more comments to explain the logic behind the calculations, especially for someone who may not be familiar with the RAID-Z implementation.
Error Handling: There is no error handling in the function. Depending on the context in which this function is used, it might be beneficial to add checks for invalid input values and return an error code or handle exceptions accordingly.
Here is a revised version of the function with the return value corrected:
.....(Snipped for formatting issues)
Make sure to test the function thoroughly to ensure it behaves as expected under various conditions.
accept the world has changed my friend!!
And more so, as the NSA, China, Russia etc will be using llms to comb repo's to find zero day vulnerabilities.
I have been playing with NetBSD for a bit and I discovered their FS has a log parameter, using that on mount to me makes their file system almost as good as ext4. Interesting to me it is not advertised too much.