Skip to content

Fix TypeBuilder canonicalization - #3578

Merged
tlively merged 4 commits into
WebAssembly:mainfrom
tlively:fix-type-builder
Feb 19, 2021
Merged

tlively merged 4 commits into
WebAssembly:mainfrom
tlively:fix-type-builder

Conversation

@tlively

@tlively tlively commented Feb 18, 2021

Copy link
Copy Markdown
Member

When types or heap types were used multiple times in a TypeBuilder instance, it
was possible for the canonicalization algorithm to canonicalize a parent type
before canonicalizing all of its component child types, leaking the temporary
types into globally interned types. This bug led to incorrect canonicalization
results and use-after free bugs.

The cause of the bug was that types were canonicalized in the reverse of the
order that they were visited in, but children were visited after the first
occurrence of their parents, not necessarily after the last occurrence of their
parents. One fix could have been to remove the logic that prevented types from
being visited multiple times so that children would always be visited after
their parents. That simple fix, however, would not scale gracefully to handle
recursive types because it would require some way to detect recursions without
accidentally reintroducing these bugs.

This PR implements a more robust solution: topologically sorting the traversed
types to ensure that children are canonicalized before their parents. This
solution will be trivial to adapt for recursive types because recursive types
are trivial to detect from the reachability graph used to perform the
topological sort.

This supersedes the incomplete fix in #3571.

When types or heap types were used multiple times in a TypeBuilder instance, it
was possible for the canonicalization algorithm to canonicalize a parent type
before canonicalizing all of its component child types, leaking the temporary
types into globally interned types. This bug led to incorrect canonicalization
results and use-after free bugs.

The cause of the bug was that types were canonicalized in the reverse of the
order that they were visited in, but children were visited after the first
occurrence of their parents, not necessarily after the last occurrence of their
parents. One fix could have been to remove the logic that prevented types from
being visited multiple times so that children would always be visited after
their parents. That simple fix, however, would not scale gracefully to handle
recursive types because it would require some way to detect recursions without
accidentally reintroducing these bugs.

This PR implements a more robust solution: topologically sorting the traversed
types to ensure that children are canonicalized before their parents. This
solution will be trivial to adapt for recursive types because recursive types
are trivial to detect from the reachability graph used to perform the
topological sort.
@tlively
tlively requested a review from kripken February 18, 2021 01:57
Comment thread src/wasm/wasm-type.cpp Resolved
Comment thread src/wasm/wasm-type.cpp Outdated
auto childrenIt = ancestors.find(i);
if (childrenIt != ancestors.end()) {
for (auto child : childrenIt->second) {
visit(child);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I'm missing something. ancestors is defined as

  // Maps Type and HeapType IDs to the IDs of their ancestor Types and HeapTypes
  // in the type graph. Only considers compound Types and HeapTypes.

From this description I expect ancestors[id] to be a list of the ancestors of id. But the loop here instead calls those items "children" - ?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Oh yeah, I got myself confused about the correct direction of the relation when I was writing this and I guess I forgot to update this code. I'll fix this. ancestors and your expectation are correct.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

@kripken this should be fixed now.

tlively and others added 2 commits February 17, 2021 21:32
Co-authored-by: Alon Zakai <alonzakai@gmail.com>
Comment thread src/wasm/wasm-type.cpp Outdated

// Collect the items to be sorted, including all the result HeapTypes and any
// type that participates in the ancestor relation.
std::set<TypeID> allIDs;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
std::set<TypeID> allIDs;
std::unordered_set<TypeID> allIDs;

I doubt it matters for speed, but it may be clearer to read, as it indicates there is no sorting order in this data structure?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I intentionally used a set here so that the result of the topological would be deterministic. I'll add a comment.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Ah, then I missed something.

Do you mean it keeps the sort at each topological depth deterministic?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I meant for this std::set to keep the top-level driving of the sort deterministic and for the std::set in ancestors to keep each recursive step of the sort deterministic. But now that I think about it, the TypeIDs themselves aren't deterministic, so using a std::set here is not sufficient to make the sort deterministic. Furthermore, it shouldn't matter whether this sort is deterministic; that shouldn't be visible to the user or affect anything else. I'll switch to using std::unordered_set here and in ancestors.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks! Sounds good.

@tlively
tlively merged commit 22fc60d into WebAssembly:main Feb 19, 2021
@tlively
tlively deleted the fix-type-builder branch February 19, 2021 01:48
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