Repository navigation
Description
Track: false-negative
Summary
polyscan does not see the functions, structs, enums and impl blocks written inside a cfg_if! or cfg_select! invocation. They are missing from complexity, clone detection, CBO and LCOM, and no warning names the file. The file still counts toward the line total.
Rationale
cfg_if! from the cfg-if crate is the usual way to write platform- or feature-specific Rust, and cfg_select! has replaced it in the standard library since Rust 1.95. A whole collector or socket implementation can sit in one arm. When it vanishes, a high-risk function can drop out of the report and the score improves with nothing telling the user why. polyscan analyzes every branch of a C++ #if and every #[cfg] item, so the macro forms are the only conditional code it skips.
Evidence
EmbarkStudios/quilkin uses cfg_select! 19 times across 8 files. polyscan reports no function and no type for src/alloc/metrics.rs, whose 237 lines from line 17 are one cfg_select!:
{"file_path":"src/alloc/metrics.rs","lines_of_code":254,"analyzed_function_count":0}cfg_select! {
all(feature = "jemalloc", not(target_env = "msvc")) => {
use tikv_jemalloc_ctl::stats;
struct JemallocCollector {
epoch: tikv_jemalloc_ctl::epoch_mib,JemallocCollector::encode (lines 52 to 150) holds 7 if let expressions and 12 ? operators, which polyscan's own rules count as complexity 20, high risk.
src/net/packet/queue.rs:48-64 declares enum Notify and enum PacketQueueReceiver inside a cfg_select!. PacketQueueSender has a field notify: Notify, yet polyscan lists only one dependency:
{"name":"PacketQueueSender","file_path":"src/net/packet/queue.rs","coupling_count":1,"dependent_classes":["SendPacket"]}PacketQueueReceiver, which src/net.rs, io_uring.rs and poll/tokio.rs use, appears in no CBO or LCOM entry. src/net/io/poll/tokio.rs:49-106 hides 8 functions, and src/net.rs:183-202 hides DualStackLocalSocket::raw_fd, recv_from and send_to.
Repro
pub struct Visible {
a: u32,
}
impl Visible {
pub fn get(&self) -> u32 {
if self.a > 1 { self.a } else { 0 }
}
}
cfg_if::cfg_if! {
if #[cfg(unix)] {
pub struct Collector {
hits: u32,
misses: u32,
}
impl Collector {
pub fn encode(&self, verbose: bool) -> Option<u32> {
if self.hits == 0 {
return None;
}
for _ in 0..self.misses {
if verbose && self.hits > 10 {
return Some(self.hits);
}
}
Some(self.misses)
}
}
}
}polyscan analyze --format json . lists only Visible::get in complexity and only Visible in LCOM, with no warning. When the same items sit in #[cfg(unix)] mod inner { ... }, polyscan lists inner::Collector::encode at complexity 5 and an LCOM entry for inner::Collector.
Proposed change
polyscan analyzes the items in every arm of a cfg_if! or cfg_select! invocation in item position, as it does for #[cfg] items, and reports them at their real lines. Functions in an arm inside an impl block are methods of that type. Two arms that define the same function for different platforms are not reported as a clone pair of each other, and a type declared once per arm counts as one type. If an arm cannot be analyzed, a warning names the file.
Impact: med. Effort: medium. Confidence: high.
polyscan version 0.6.1.
Verification: survived refutation. The file is analyzed with zero functions and no warning, and no issue tracks it. Impact was lowered from high because about 18 of the repository's 2135 Rust functions and 3 types are hidden. The common cfg_if! { if #[cfg(x)] { mod unix; } } form loses nothing, because the module files are walked anyway.
Related: #223, decision points inside macros such as tokio::select!, where the function is visible but its branches are not counted; #177 (macro arguments invisible to LCOM, fixed).
Found via the polyscan-enhancement-audit skill in repo EmbarkStudios/quilkin@58f4f0b1b390a7c527cb0228c9267055f716d48f.
Want to work on this? Run polyscan on a project of your own first, and say in a comment which project you ran it on. See CONTRIBUTING.md, "Pull Requests".
Activity