-
Notifications
You must be signed in to change notification settings - Fork 758
Add assert for dangling references without an associated ItemId #312
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
Add assert for dangling references without an associated ItemId #312
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you move this to its own function, that is called from right here? Additionally, we want this function to be a no-op in release builds.
Also, we can remove the NOTE comment. This doesn't need testing itself, this is more of extra testing code for the rest of bindgen.
@@ -431,6 +431,18 @@ impl<'ctx> BindgenContext<'ctx> { | |||
// because we remove it before the end of this function. | |||
self.gen_ctx = Some(unsafe { mem::transmute(&ctx) }); | |||
|
|||
// NOTE (imp): I guess this should work but how do I test it? | |||
for (id, item) in self.items() { | |||
let mut sub_items = ItemSet::new(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On top of that, can we call this related_items
instead? Seems more accurate given we insert the parent.
I was trying to understand how we will use BFS for this and quite confused. Because there is no possible way of traversing it without any |
@impowski: I haven't forgotten about this PR, just a bit busy this week at an all-hands -- will get to this end of this week / early next week. Thanks for your patience :) |
@fitzgen I was little bit busy for last 2 days, I'll will try to do something right now, just need to check your example in IRC. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM! Will merge once my final nitpick is taken care of :)
@@ -441,6 +443,22 @@ impl<'ctx> BindgenContext<'ctx> { | |||
ret | |||
} | |||
|
|||
/// This function trying to find any dangling references inside of `items` | |||
fn related_items(&self) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we rename this assert_no_dangling_references
? Thanks!
Regarding the BFS stuff: let's land this now and then do that in a follow up. We talked a bit on irc, but if things are still unclear, just ping me again or comment on the issue. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great start!
I've detailed how to reconstruct the shortest path to an ItemId below with a few code snippets and requested changes.
Let me know if you have questions!
|
||
let roots = self.items().map(|(&id, _)| id); | ||
|
||
let seen: ItemSet = roots.collect(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because this is marking every item as seen, the BFS is going to go nowhere: it won't traverse any sub-item.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you want to keep the seen
set empty, and also put the self.items()
iterator as a member into RelatedItemsIter
.
where 'gen: 'ctx, | ||
{ | ||
ctx: &'ctx BindgenContext<'gen>, | ||
seen: ItemSet, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be a BTreeMap<ItemId, ItemId>
mapping from a seen item, to the item from which we first discovered it (or mapping to itself if we found it as a root).
That way, if/when we find a dangling ItemId
reference, we can walk backwards through the path with this map, something like this:
if is_dangling(id) {
let mut path = vec![];
let mut current = id;
loop {
let predecessor = self.seen.get(current)
.expect("We know we found this item id, so it must have a predecessor");
if predecessor == current {
break;
}
path.push(predecessor);
current = predecessor;
}
path.reverse();
panic!("Found reference to dangling id = {:?}\n
via path = {:?}",
id,
path);
}
id.collect_types(self.ctx, &mut sub_types, &()); | ||
|
||
for id in sub_types { | ||
if self.seen.insert(id) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Map from a seen id to the id of its predecessor:
for sub_id in sub_types {
if self.seen.insert(sub_id, id) {
...
}
}
|
||
fn next(&mut self) -> Option<Self::Item> { | ||
let id = match self.to_iterate.pop_front() { | ||
None => return None, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of eagerly returning None
, we should see if there is a next root:
let id = match self.to_iterate.pop_front() {
None => {
// We've traversed everything reachable from the previous root(s), see if
// we have any more roots.
//
// (Note: The `self.all_items` here would be the iterator returned by `ctx.items()`.)
match self.all_items.filter(|&(id, _)| !self.seen.contains_key(id)).next() {
None => return None,
Some(id) => {
// This is a new root.
self.seen.insert(id, id);
id
}
},
Some(id) => id,
};
}; | ||
|
||
debug_assert!(self.seen.contains(&id)); | ||
debug_assert!(self.ctx.items.contains_key(&id)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is where the if is_dangling(id) { ... }
snippet that finds the BFS path would go.
@@ -1066,3 +1102,38 @@ impl<'ctx, 'gen> Iterator for WhitelistedItemsIter<'ctx, 'gen> | |||
Some(id) | |||
} | |||
} | |||
|
|||
pub struct RelatedItemsIter<'ctx, 'gen> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since this iterator is going to be specific to finding BFS paths of dangling items, lets call it AssertNoDanglingItemIter
.
@@ -441,6 +443,40 @@ impl<'ctx> BindgenContext<'ctx> { | |||
ret | |||
} | |||
|
|||
/// This function trying to find any dangling references inside of `items` | |||
fn assert_no_dangling_references(&self) { | |||
if cfg!(debug_assertions) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With the changes requested below, the contents of this if branch can now simply be:
if cfg!(debug_assertions) {
for _ in self.assert_no_dangling_item_traversal() {
// The iterator's next method does the asserting for us.
}
}
} | ||
} | ||
|
||
fn related_items_traversal<'me>(&'me self) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's rename this method to assert_no_dangling_item_traversal()
It'd be nice to keep this behind an option, even in debug builds.
On stylo we're starting to use bindgen at build time, and it's already
pretty painful to use. Seems there's no solution until
rust-lang/cargo#1359 is resolved.
…On Tue, Dec 13, 2016 at 11:29:20AM -0800, Nick Fitzgerald wrote:
fitzgen requested changes on this pull request.
Great start!
I've detailed how to reconstruct the shortest path to an ItemId below with a few code snippets and requested changes.
Let me know if you have questions!
> + for sub in sub_items {
+ assert!(self.resolve_item_fallible(sub).is_some(),
+ "Should not dangle: {:?} references {:?}", id, sub);
+ }
+ }
+ }
+ }
+
+ fn related_items_traversal<'me>(&'me self)
+ -> RelatedItemsIter<'me, 'ctx> {
+ assert!(self.in_codegen_phase());
+ assert!(self.current_module == self.root_module);
+
+ let roots = self.items().map(|(&id, _)| id);
+
+ let seen: ItemSet = roots.collect();
Because this is marking every item as seen, the BFS is going to go nowhere: it won't traverse any sub-item.
> + for sub in sub_items {
+ assert!(self.resolve_item_fallible(sub).is_some(),
+ "Should not dangle: {:?} references {:?}", id, sub);
+ }
+ }
+ }
+ }
+
+ fn related_items_traversal<'me>(&'me self)
+ -> RelatedItemsIter<'me, 'ctx> {
+ assert!(self.in_codegen_phase());
+ assert!(self.current_module == self.root_module);
+
+ let roots = self.items().map(|(&id, _)| id);
+
+ let seen: ItemSet = roots.collect();
I think you want to keep the `seen` set empty, and also put the `self.items()` iterator as a member into `RelatedItemsIter`.
> @@ -1066,3 +1102,38 @@ impl<'ctx, 'gen> Iterator for WhitelistedItemsIter<'ctx, 'gen>
Some(id)
}
}
+
+pub struct RelatedItemsIter<'ctx, 'gen>
+ where 'gen: 'ctx,
+{
+ ctx: &'ctx BindgenContext<'gen>,
+ seen: ItemSet,
This should be a `BTreeMap<ItemId, ItemId>` mapping from a seen item, to the item from which we first discovered it (or mapping to itself if we found it as a root).
That way, if/when we find a dangling `ItemId` reference, we can walk backwards through the path with this map, something like this:
```rust
if is_dangling(id) {
let mut path = vec![];
let mut current = id;
loop {
let predecessor = self.seen.get(current)
.expect("We know we found this item id, so it must have a predecessor");
if predecessor == current {
break;
}
path.push(predecessor);
current = predecessor;
}
path.reverse();
panic!("Found reference to dangling id = {:?}\n
via path = {:?}",
id,
path);
}
```
> + type Item = ItemId;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ let id = match self.to_iterate.pop_front() {
+ None => return None,
+ Some(id) => id,
+ };
+
+ debug_assert!(self.seen.contains(&id));
+ debug_assert!(self.ctx.items.contains_key(&id));
+
+ let mut sub_types = ItemSet::new();
+ id.collect_types(self.ctx, &mut sub_types, &());
+
+ for id in sub_types {
+ if self.seen.insert(id) {
Map from a seen id to the id of its predecessor:
```rust
for sub_id in sub_types {
if self.seen.insert(sub_id, id) {
...
}
}
```
> +pub struct RelatedItemsIter<'ctx, 'gen>
+ where 'gen: 'ctx,
+{
+ ctx: &'ctx BindgenContext<'gen>,
+ seen: ItemSet,
+ to_iterate: VecDeque<ItemId>,
+}
+
+impl<'ctx, 'gen> Iterator for RelatedItemsIter<'ctx, 'gen>
+ where 'gen: 'ctx,
+{
+ type Item = ItemId;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ let id = match self.to_iterate.pop_front() {
+ None => return None,
Instead of eagerly returning `None`, we should see if there is a next root:
```rust
let id = match self.to_iterate.pop_front() {
None => {
// We've traversed everything reachable from the previous root(s), see if
// we have any more roots.
//
// (Note: The `self.all_items` here would be the iterator returned by `ctx.items()`.)
match self.all_items.filter(|&(id, _)| !self.seen.contains_key(id)).next() {
None => return None,
Some(id) => {
// This is a new root.
self.seen.insert(id, id);
id
}
},
Some(id) => id,
};
```
> + to_iterate: VecDeque<ItemId>,
+}
+
+impl<'ctx, 'gen> Iterator for RelatedItemsIter<'ctx, 'gen>
+ where 'gen: 'ctx,
+{
+ type Item = ItemId;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ let id = match self.to_iterate.pop_front() {
+ None => return None,
+ Some(id) => id,
+ };
+
+ debug_assert!(self.seen.contains(&id));
+ debug_assert!(self.ctx.items.contains_key(&id));
This is where the `if is_dangling(id) { ... }` snippet that finds the BFS path would go.
> @@ -1066,3 +1102,38 @@ impl<'ctx, 'gen> Iterator for WhitelistedItemsIter<'ctx, 'gen>
Some(id)
}
}
+
+pub struct RelatedItemsIter<'ctx, 'gen>
Since this iterator is going to be specific to finding BFS paths of dangling items, lets call it `AssertNoDanglingItemIter`.
> @@ -441,6 +443,40 @@ impl<'ctx> BindgenContext<'ctx> {
ret
}
+ /// This function trying to find any dangling references inside of `items`
+ fn assert_no_dangling_references(&self) {
+ if cfg!(debug_assertions) {
With the changes requested below, the contents of this if branch can now simply be:
```rust
if cfg!(debug_assertions) {
for _ in self.assert_no_dangling_item_traversal() {
// The iterator's next method does the asserting for us.
}
}
```
> + fn assert_no_dangling_references(&self) {
+ if cfg!(debug_assertions) {
+ for (id, item) in self.items() {
+ let mut sub_items = ItemSet::new();
+ item.collect_types(self, &mut sub_items, &());
+
+ sub_items.insert(item.parent_id());
+ for sub in sub_items {
+ assert!(self.resolve_item_fallible(sub).is_some(),
+ "Should not dangle: {:?} references {:?}", id, sub);
+ }
+ }
+ }
+ }
+
+ fn related_items_traversal<'me>(&'me self)
Let's rename this method to `assert_no_dangling_item_traversal()`
--
You are receiving this because you commented.
Reply to this email directly or view it on GitHub:
#312 (review)
|
We could add a feature for this, that we only enable in local + CI tests. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks great! Thanks for sticking with this all the way through :)
Can you rebase + squash this branch, and then force push it here? After that, I'll merge :)
Thanks again!
@bors-servo r+ |
📌 Commit 989a516 has been approved by |
☀️ Test successful - status-travis |
So I think this is it? Fix issue #209
r? @fitzgen