-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
Lint dangerous uses of shadowing #3433
Comments
'Shadowing in a small scope' and 'Shadowing in conditional early aborts' are only an issue if the I don't get why 'Shadowing in match arm patterns' is a problem. The variable and the one it shadows are always related. If that's OK in other places why is it a problem in a match arm? |
Look at the example again: The |
Why do we need shadowing? it could destroy code readability. |
From the perspective of writing code that is as easy as possible for both humans and machines to understand, I'd agree with @dongleiw that shadowing may be intrinsically risky. I understand the desire to use shadowing for temporary variables, where the programmer does not want to create and keep track of additional names. However those situations seem like they should be relatively rare, and shadowing introduces a possible source of subtle bugs (think: hours trying to determine why the value of (this might be a radical opinion; I'm new to rust, so it's possible there are more reasons that justify shadowing that I'm yet to learn) |
I have similar thoughts about Naga/wgpu codebases. We want to disable most shadowing as dangerous, but leave a class of cases that are fine. More concretely, I think shadowing is fine when it's both related and having a different type. I.e. this one is not good: let x = x +1; But this one is totally fine: if let Some(x) = x {
// do something
} Unfortunately, the current set of lints does not allow us to have that behavior. Please consider adding a check for "same type" when shadowing. |
yea, this seems weird - shadowing seems like a way to get around immutability. Yes, it is useful when processing / parsing a variable. |
I personally think that with suitable highlighting, IDEs can greatly reduce the trouble that shadowing brings. On the other hand, re-using a suitable name can be the best option where other names are clunky. |
The Linux kernel hit a shadowing-related bug: https://lore.kernel.org/rust-for-linux/[email protected]/ This case is currently linted by The linked #2890 above would have caught the bug. That issue was closed because the If we enable the let old = unsafe { old.unwrap_unchecked() };
let val = unsafe { Pin::new_unchecked(val) };
let slot = slot.cast::<T>();
let mut item = unsafe { ListLinks::fields(T::view_links(item)) };
let ptr = ptr.cast::<Self>();
let page = NonNull::new(page).ok_or(AllocError)?;
let node = RBTreeNode { node };
let neighbor = neighbor.as_ptr();
let node = KBox::into_raw(node.node);
let ptr = ptr as *const ArcInner<T>;
let ptr = unsafe { ptr.byte_sub(val_offset) };
let ptr = unsafe { ArcInner::container_of(ptr) };
let jiffies = jiffies.try_into().unwrap_or(MAX_SCHEDULE_TIMEOUT);
let out = unsafe { &mut *(out as *mut [u8] as *mut [MaybeUninit<u8>]) };
let ptr = ptr as *mut Work<T, ID>;
let ptr = unsafe { T::work_container_of(ptr) };
let work_ptr = unsafe { Work::raw_get(work_ptr) };
let init = unsafe {
pin_init_from_closure(|slot| init.__pinned_init(slot).map_err(|e| Error::from(e)))
};
let tag_set = core::mem::size_of::<RequestDataWrapper>()
.try_into()
.map(|cmd_size| {
bindings::blk_mq_tag_set { ...
nr_maps: num_maps,
..tag_set
}
}); These seem reasonably OK, and the original binding becomes inaccessible "vertically". Of course, one can still make mistakes moving code up and down within the function. Then others: match Self::try_from_arc(arc) {
Ok(list_arc) => Some(list_arc),
Err(arc) => Arc::into_unique_or_drop(arc).map(Self::from),
}
let current = match (prev, next) {
(_, Some(next)) => next,
(Some(prev), None) => prev,
(None, None) => {
return (None, node);
}
};
let ptr = match ptr {
Some(ptr) => {
if old_layout.size() == 0 {
ptr::null()
} else {
ptr.as_ptr()
}
}
None => ptr::null(),
};
// The buggy one.
match len.checked_mul(core::mem::size_of::<T>()) {
Some(len) if len <= ISIZE_MAX => {
Ok(Self {
len,
_phantom: PhantomData,
})
}
_ => Err(LayoutError),
} For these, I think on our side we could easily enable a lint that would trigger in Moreover, we could likely also enable it in the The idea mentioned in a previous comment about linting only when the type does not change sounds interesting (alone or on top of other filters). In our case, it would have still caught the bug, and would not have linted in most of the other cases. Another way could be avoiding linting in cases that would not have compiled anyway, e.g. if the shadowed value has been consumed, like in our first |
@ojeda what kind of distinction would you suggest we make between the cases you want linted and the cases that are OK in your book? |
@llogiq Sorry, I am not sure what you mean -- there are several approaches mentioned above that could be useful to filter what a lint like
But I am not sure which one is best -- it would be a good idea to try with some popular crates out there to figure out a good threshold. Allowing existing projects to configure the lint would help them enable it (or implementing it as new finer-grained lints). From what I saw in the kernel from that quick look, I think something like "same type" (or "would still compile") plus ignoring top-level |
We have three shadowing lints
let x = consuming_operation(x);
let x = &x;
let x = ...; let x = y;
which all try to address shadowing issues. Unfortunately enabling either of these lints will also lint idiomatic and readable code. We should find a way to write a lint that only detects actually problematic cases of shadowing.
All of the cases below are even more prominent if you include mutable bindings, as it becomes very hard to distinguish which variable is changed by a
x = ...
statement.Shadowing in a small scope
Shadowing in match arm patterns
#2890
Shadowing in conditional early aborts
probably not as problematic as the first case mentioned, but would get caught by a naive implementation of the first case.
The text was updated successfully, but these errors were encountered: