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

Display traits don't mask #52

Closed
bbaldino opened this issue Dec 5, 2022 · 12 comments · Fixed by #59
Closed

Display traits don't mask #52

bbaldino opened this issue Dec 5, 2022 · 12 comments · Fixed by #59

Comments

@bbaldino
Copy link
Collaborator

bbaldino commented Dec 5, 2022

Today I noticed the following code:

let mut x = u1::new(1);
x <<= 1;
println("{x}");

prints 2, which is an invalid value for u1.

I looked at the implementation of ShlAssign and saw:

fn shl_assign(&mut self, rhs: T) {
    *self = self.mask();
    self.0.shl_assign(rhs);
}

I think the mask call should be after the shl_assign call (or, perhaps, in both places)? If that sounds right, happy to open a PR. There are unit tests for ShlAssign, but none involve shifting beyond the type's 'size'.

EDIT: Updated the title of this ticket after discovering the current strategy is to mask on read, so the issue isn't with ShlAssign, but actually with the various display traits. See discussion below.

@bbaldino
Copy link
Collaborator Author

bbaldino commented Feb 3, 2023

@chrysn @Kijewski does this seem legitimate to you? If so I'll clean up some local changes I have and do a PR.

@chrysn
Copy link
Member

chrysn commented Feb 3, 2023

The current shl_assign code uses shl(), which already masks the result of the left shift. Can you verify that this is still an issue with the latest git version?

@bbaldino
Copy link
Collaborator Author

bbaldino commented Feb 3, 2023

On master? Still looks like I pasted above from what I can tell: https://github.com/rust-ux/uX/blob/10819303ffeb8093556bbdd81fca94fb77b58237/src/lib.rs

@chrysn
Copy link
Member

chrysn commented Feb 4, 2023

My apologies, I was on the pr-niche branch (which would, consequently, fix this issue).

The fix you propose looks good; could you provide a PR?

(If we go through with #49, which I'm generally positive about, it'll be after a breaking version bump, and this would be a good fix to have in 0.1.6).

@chrysn
Copy link
Member

chrysn commented Feb 4, 2023

A more correct version (consistent with documented behavior) would be to panic when in debug mode, so feel free to address that in the PR as well, but fixing the correctness issue has high enough priority over the debug panic that a fix for it alone would be good just as well.

@bbaldino
Copy link
Collaborator Author

bbaldino commented Feb 5, 2023

Ah that's interesting, is that a stdlib doc for shlassign? Or something here? I was thinking it'd just silently drop, just like you can shift-assign a top bit "out" of u8, u16, etc.

@chrysn
Copy link
Member

chrysn commented Feb 5, 2023 via email

@bbaldino
Copy link
Collaborator Author

bbaldino commented Feb 7, 2023

So as part of digging into this a bit, I noticed it's not straightforward to test, because PartialEq, for example, does masking. Looking around, it looks like many trait implementations do the masking there (on "read"), as opposed to doing the mask on modification (on "write").

I suspect that some trait implementations may be missing the mask call--unfortunately I don't remember which one I was trying to use when I stumbled across this before. I took a look through, though, and did find some:

Display doesn't mask, and will show an "unmasked" value (e.g. a u9 could show a value of 512 when printed). Same for UpperHex, LowerHex, Octal, Binary. I'd think there's value in enforcing that a uX type is always in a "valid" state: i.e. masking should be done on write, not read, but I assume this was done intentionally, so perhaps there was good reason. So perhaps the fix should just to change the display traits mentioned above to mask.

@kjetilkjeka
Copy link
Collaborator

I don't really remember the complete picture here. But I assume the inconsistent masking on read/write is due to changing the strategy somewhere along the way.

I guess the two valid options are:

  1. Always ensure consistent state on write. Hope the redundant masks will be optimized away when writes are done consecutively in the same function.
  2. Accept that leftmost bits cannot be made assumptions on. Mask when it makes a difference (PartialEq, Display, right shift, etc). Ignore it when it doesn't (wrapping mul, left shift, etc).

I'm not sure which one is the correct one to use. Maybe the greatest virtue of (2) is that a buggy implementation will be observed when calling the function itself, and not on different and later function calls. But if there are notable performance differences those should be valued higher as we cannot assume it's not important for such a basic library.

@bbaldino
Copy link
Collaborator Author

bbaldino commented Feb 7, 2023

@kjetilkjeka thanks for the info. That sounds right, and, fwiw, (2) there is pretty nice. My thought is to make the display trait implementations consistent with the rest (add a call to mask); a potential refactor to shift consistency to "write" could be something to consider in the future.

@bbaldino bbaldino changed the title ShlAssign doesn't mask after shift Display traits don't mask Feb 8, 2023
@bbaldino
Copy link
Collaborator Author

bbaldino commented Feb 8, 2023

Opened #59

@chrysn
Copy link
Member

chrysn commented Feb 8, 2023 via email

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 a pull request may close this issue.

3 participants