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

Resolve paths in kani::stub attributes (ignoring use statements) #1999

Merged

Conversation

aaronbembenek-aws
Copy link
Contributor

Description of changes:

Currently, when we resolve paths in kani::stub attributes we do not take into account the module of the harness and we do not support the path qualifiers crate, self, super, and initial ::. This PR improves our path resolution algorithm to take into account the module of the harness (so we resolve relative paths correctly) and adds support for those qualifiers.

It does not handle use statements (as in use XXX; or use XXX as YYY;); they will be addressed in a separate PR (tracked in #1998).

Resolved issues:

Related to #1866
Resolves #1894

Call-outs:

Previously, we represented a stub mapping (mapping original functions/methods to their replacements) as a FxHashMap<String, String>, and then did a linear lookup in the HIR during the MIR transformation to try to find a function/method with the same string representation. Since we are actually resolving paths now when processing the attributes (and constructing the stub mappings), this PR uses a FxHashMap<DefPathHash, DefPathHash> instead, which is more robust and removes the need for that linear lookup. A DefPathHash is stable across crate and compilation boundaries.

Testing:

  • How is this change tested? Existing tests and new tests

  • Is this a refactor change? No

Checklist

  • Each commit message has a non-empty body, explaining why the change was made
  • Methods or procedures are documented
  • Regression or unit tests are included, or existing tests cover the modified code
  • My PR is restricted to a single feature or bugfix

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses.

Copy link
Contributor

@celinval celinval left a comment

Choose a reason for hiding this comment

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

I still have to go through the tests in this PR, but overall I think this is a great improvement to the user experience.

The logic here is fairly complicated, so I would like to ask you to add some debug!() and trace!() statements to help future development. Especially when things go wrong. There are a lot of places inside resolve.rs that you return None, which basically means that we fail resolution. It would be great to know which path we took to get there.

Another thing to consider is to give a more informative error when resolution fails. Let's say we fail in the following resolution:

::my_crate::my_module::my_function

If we fail while processing my_crate, the error could say something like fail to find crate "my_crate". But if we have found my_crate, but not my_module, we can say couldn't find "my_module" inside "my_crate".

I'm OK if we leave the more detailed error for later, but you might want to consider at least what mechanism we should use to pass this error. For example, right now we discard whatever we process. We are also returning Option<> everywhere, should we consider using Result<> instead or should we just trigger the error whenever we find it?

#[derive(Debug)]
struct Path {
base: Base,
segments: Segments,
Copy link
Contributor

Choose a reason for hiding this comment

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

can you please document that if may_be_external_path is true, then segments cannot be empty?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

// `self` may be followed be `super` (handled below).
let mut may_be_external_path = true;
let first = segments[0].as_str();
if first == ROOT {
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you please replace this by match block?

match first {
  ROOT => {}
  SELF => {}
  ...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

/// structure, resolving qualifiers (like `crate`, etc.) along the way.
fn to_path(tcx: TyCtxt, current_module: LocalDefId, name: &str) -> Option<Path> {
const CRATE: &str = "crate";
const ROOT: &str = "{{root}}";
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this how the compiler maps initial "::"?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Exactly. I added a comment


// Resolve qualifiers `crate`, initial `::`, and `self`. The qualifier
// `self` may be followed be `super` (handled below).
let mut may_be_external_path = true;
Copy link
Contributor

Choose a reason for hiding this comment

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

For readability, I would recommend inspecting the first element and assigning the proper value here. Something like:

let may_be_external_path = !matches!(first, CRATE, SELF, SUPER);

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is cleaner! Done

type Item = (u64, u64);
let item_to_def_id = |item: Item| -> DefId {
let hash = DefPathHash(Fingerprint::new(item.0, item.1));
tcx.def_path_hash_to_def_id(hash, &mut || panic!())
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe a friendly error would be nice here. Something on the terms that this is likely due to a Kani issue, please file a bug report.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I checked, and the panic is caught by our panic handler, so the user does receive a message to that effect

Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks!

@@ -0,0 +1,236 @@
// Copyright Kani Contributors
// SPDX-License-Identifier: Apache-2.0 OR MIT
//! This module contains code for resolving strings to `DefId`s.
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you please expand this description? What kind of strings are we talking about? Does this module cover any string that represents a path? Do we support fully qualified paths? Maybe adding a link to the path syntax might help too: https://doc.rust-lang.org/reference/paths.html.

Which brings me a question, do we support qualified paths?

Copy link
Contributor

Choose a reason for hiding this comment

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

Looking through the open issues kind of answers my last question about qualified paths: #1997

But I think it would be great if we can explicitly state that we are only handling simple paths and add a link to its definition: https://doc.rust-lang.org/reference/paths.html#simple-paths

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've updated some comments to make this clearer (and included the link for simple paths)

Copy link
Contributor

@celinval celinval left a comment

Choose a reason for hiding this comment

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

Thank you! BTW, great tests!

@celinval
Copy link
Contributor

BTW, I create #2013 to track improvements to the stubbing error formats.

@aaronbembenek-aws
Copy link
Contributor Author

Thanks for taking a look! I've added traces so that we now get debug output like this:

┐kani_compiler::kani_middle::resolve::path_resolution
├───0ms DEBUG kani_compiler::kani_middle::resolve Normalizing path `local_mod::pub_fn`
├───0ms DEBUG kani_compiler::kani_middle::resolve Resolving `local_mod::pub_fn` in local crate root
├───0ms DEBUG kani_compiler::kani_middle::resolve Resolving `pub_fn` in local module `local_mod`
├───0ms DEBUG kani_compiler::kani_middle::resolve Resolved `pub_fn` as a function in local module `local_mod`
┘

BTW, I create #2013 to track improvements to the stubbing error formats.

Thanks! I think we should be able to support better errors by (as you suggest) changing the Options to Results where the error case records where resolution gets stuck.

@aaronbembenek-aws aaronbembenek-aws merged commit 1d94b6b into model-checking:main Dec 16, 2022
@aaronbembenek-aws aaronbembenek-aws deleted the resolve-stub-names branch December 16, 2022 20:11
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.

Improve performance of stub lookup
2 participants