Summary
When a consumer reaches a crate's API through an adapter crate that re-exports it, the
callee key keeps the adapter's prefix. A contract keyed on the crate that owns the API — which
is the KB convention — cannot match that key, so the call stays unresolved.
The existing facade handling covers the case where the crate being scanned declares the
facade module. It does not cover a consumer of that crate, which is the common case.
Reproducer
Consumer of tokio-native-tls (the standard way to use native-tls with tokio):
// Cargo.toml: tokio-native-tls = "0.3", native-tls = "0.2"
use tokio_native_tls::native_tls::{Protocol, TlsConnector};
fn configure() {
let mut b = TlsConnector::builder();
b.min_protocol_version(Some(Protocol::Tlsv12));
}
--export-callgraph emits:
tokio_native_tls::native_tls.TlsConnector.min_protocol_version(?)
(?) with empty parameter_types — the shape an absent contract takes.
Why it cannot resolve
native_tls::TlsConnector is the owning spelling, so a contract for that crate is keyed
native_tls::TlsConnector.min_protocol_version. But rustAuthoredKey
(internal/callgraph/contracts/contracts.go) performs exactly one rewrite — the receiver-type
separator — as its own comment says: "Callers build at most package.Type.method, so one
rewrite always suffices."
emitted: tokio_native_tls::native_tls.TlsConnector.min_protocol_version
after rewrite: tokio_native_tls::native_tls::TlsConnector.min_protocol_version
contract key: native_tls::TlsConnector.min_protocol_version
└── this prefix is never removed ──┘
Different strings, no match.
rustPassThroughReExport (internal/callgraph/rust_type_semantics.go) already resolves
pub mod native_tls { pub use native_tls::*; } (tokio-native-tls 0.3.1 src/lib.rs:382)
through to the owning crate — but it fires on the scanned crate's own module. In a
consumer scan the adapter's source is not parsed, so there is no pub mod to detect and the
prefix survives.
internal/callgraph/rust_reexport_facade_test.go already lists this key shape under absent,
and states the cost: "no contract keyed on the owning crate could ever match it." That
assertion holds for the adapter-scan path; the consumer path reintroduces the same key.
Why it is worth fixing before the next contract lands
There is currently no contract for native-tls in internal/callgraph/contracts/rust/, so
today every such call is unresolved and nothing stands out. The moment a native-tls contract
is added:
| Consumer |
Import |
Result |
| direct |
use native_tls::… |
resolves |
| via adapter |
use tokio_native_tls::native_tls::… |
still (?) |
Contract tests are written with direct imports, so they will pass. The gap is silent exactly
when the contract looks complete.
A second re-export form, not handled at all
tokio-rustls 0.12.0 through 0.26.x re-exports at the crate root instead:
pub use rustls;
pub use webpki; // through 0.23.x
rustPassThroughReExport's discriminator looks for pub mod <name> { pub use <name>::*; }, so
this bare crate re-export does not match it in either direction. I verified the source shape
across those versions; I did not measure the emitted key for this form, so treat the key
behaviour as inferred by analogy rather than measured.
Possible directions
- Strip leading segments at lookup, guarded. Retry the lookup after dropping the leading
segment. Unguarded this is unsafe: it would make mycrate::des::Des.new match a des
contract, which is precisely what the current local-module rule prevents. A guard on "the
leading segment is a declared dependency" helps but is not sufficient, since a dependency
may have a non-facade module named like a contracted crate.
- Declare the facade in the KB. An explicit map consulted at lookup
(tokio_native_tls::native_tls → native_tls). Precise, auditable, cannot over-match; the
cost is one small data entry per adapter crate.
- Derive it when dependency sources are present. Under
--scan-dependencies the adapter's
src/lib.rs is on disk, so the existing narrow discriminator can run against the dependency
and rewrite consumer keys. Reuses proven logic; only helps in that mode.
Whatever the route, it is worth making the miss non-silent — an unresolved crypto_call
key whose leading segment is a declared dependency is a detectable signal. Today the only way
to notice is to read (?) by hand.
Incidental finding
library.coordinates is parsed into the KB (contracts.go:59,382,470) but never consulted
during lookup — the index is built solely from contractKey(method, arity). The field reads
like it provides alias resolution and does not, which is worth either wiring up or documenting.
Verification for a fix
The reproducer above should go from (?) to a resolved signature, and the existing absent
cases in rust_reexport_facade_test.go — including the mod des local-module negative — must
stay green.
Summary
When a consumer reaches a crate's API through an adapter crate that re-exports it, the
callee key keeps the adapter's prefix. A contract keyed on the crate that owns the API — which
is the KB convention — cannot match that key, so the call stays unresolved.
The existing facade handling covers the case where the crate being scanned declares the
facade module. It does not cover a consumer of that crate, which is the common case.
Reproducer
Consumer of
tokio-native-tls(the standard way to usenative-tlswith tokio):--export-callgraphemits:(?)with emptyparameter_types— the shape an absent contract takes.Why it cannot resolve
native_tls::TlsConnectoris the owning spelling, so a contract for that crate is keyednative_tls::TlsConnector.min_protocol_version. ButrustAuthoredKey(
internal/callgraph/contracts/contracts.go) performs exactly one rewrite — the receiver-typeseparator — as its own comment says: "Callers build at most
package.Type.method, so onerewrite always suffices."
Different strings, no match.
rustPassThroughReExport(internal/callgraph/rust_type_semantics.go) already resolvespub mod native_tls { pub use native_tls::*; }(tokio-native-tls 0.3.1src/lib.rs:382)through to the owning crate — but it fires on the scanned crate's own module. In a
consumer scan the adapter's source is not parsed, so there is no
pub modto detect and theprefix survives.
internal/callgraph/rust_reexport_facade_test.goalready lists this key shape underabsent,and states the cost: "no contract keyed on the owning crate could ever match it." That
assertion holds for the adapter-scan path; the consumer path reintroduces the same key.
Why it is worth fixing before the next contract lands
There is currently no contract for
native-tlsininternal/callgraph/contracts/rust/, sotoday every such call is unresolved and nothing stands out. The moment a
native-tlscontractis added:
use native_tls::…use tokio_native_tls::native_tls::…(?)Contract tests are written with direct imports, so they will pass. The gap is silent exactly
when the contract looks complete.
A second re-export form, not handled at all
tokio-rustls0.12.0 through 0.26.x re-exports at the crate root instead:rustPassThroughReExport's discriminator looks forpub mod <name> { pub use <name>::*; }, sothis bare crate re-export does not match it in either direction. I verified the source shape
across those versions; I did not measure the emitted key for this form, so treat the key
behaviour as inferred by analogy rather than measured.
Possible directions
segment. Unguarded this is unsafe: it would make
mycrate::des::Des.newmatch adescontract, which is precisely what the current local-module rule prevents. A guard on "the
leading segment is a declared dependency" helps but is not sufficient, since a dependency
may have a non-facade module named like a contracted crate.
(
tokio_native_tls::native_tls→native_tls). Precise, auditable, cannot over-match; thecost is one small data entry per adapter crate.
--scan-dependenciesthe adapter'ssrc/lib.rsis on disk, so the existing narrow discriminator can run against the dependencyand rewrite consumer keys. Reuses proven logic; only helps in that mode.
Whatever the route, it is worth making the miss non-silent — an unresolved
crypto_callkey whose leading segment is a declared dependency is a detectable signal. Today the only way
to notice is to read
(?)by hand.Incidental finding
library.coordinatesis parsed into the KB (contracts.go:59,382,470) but never consultedduring lookup — the index is built solely from
contractKey(method, arity). The field readslike it provides alias resolution and does not, which is worth either wiring up or documenting.
Verification for a fix
The reproducer above should go from
(?)to a resolved signature, and the existingabsentcases in
rust_reexport_facade_test.go— including themod deslocal-module negative — muststay green.