use super::*;
pub(crate) fn analyse_performance_block(
file: &SourceFile,
block: &FunctionBlock,
searchable_body: &str,
findings: &mut Vec<Finding>,
) {
let code_only_body = strip_rust_comments_after_string_mask(searchable_body);
for check in PERFORMANCE_CHECKS {
let regex = static_regex(check.regex, check.pattern);
let occurrences = loop_pattern_count_filtered(&code_only_body, regex, |line| {
should_count_perf_line(check.rule_id, line)
});
if occurrences > 0 {
push_performance_finding(file, block, check, occurrences, findings);
}
}
}
fn should_count_perf_line(rule_id: &str, line: &str) -> bool {
match rule_id {
"performance.clone-in-loop" => !clone_is_structural_in_loop(line),
"performance.format-in-loop" => !format_is_owned_value(line),
_ => true,
}
}
fn clone_is_structural_in_loop(line: &str) -> bool {
PERF_CLONE_STRUCTURAL_PATTERNS
.iter()
.any(|pattern| pattern.matches(line))
}
static PERF_FIELD_REGEX: OnceLock<Regex> = OnceLock::new();
static PERF_SOME_REGEX: OnceLock<Regex> = OnceLock::new();
static PERF_LOOKUP_REGEX: OnceLock<Regex> = OnceLock::new();
static PERF_CHAIN_REGEX: OnceLock<Regex> = OnceLock::new();
const PERF_CLONE_STRUCTURAL_PATTERNS: &[CloneOwnershipPattern] = &[
CloneOwnershipPattern {
cell: &PERF_FIELD_REGEX,
pattern: r"^\s*[A-Za-z_][A-Za-z0-9_]*\s*:\s+[^=]*\.clone\(\)\s*\)*\s*,?\s*$",
},
CloneOwnershipPattern {
cell: &PERF_SOME_REGEX,
pattern: r"\bSome\(\s*[^()]*\.clone\(\)\s*\)",
},
CloneOwnershipPattern {
cell: &PERF_LOOKUP_REGEX,
pattern: r"\.(?:entry|insert)\(\s*&?\s*\(?[^=;]*\.clone\(\)[^;]*\)",
},
CloneOwnershipPattern {
cell: &PERF_CHAIN_REGEX,
pattern: r"^\s*\.clone\(\)\s*$",
},
];
fn format_is_owned_value(line: &str) -> bool {
FORMAT_OWNED_VALUE_PATTERNS
.iter()
.any(|pattern| pattern.matches(line))
}
static FORMAT_FIELD_REGEX: OnceLock<Regex> = OnceLock::new();
static FORMAT_ERR_REGEX: OnceLock<Regex> = OnceLock::new();
static FORMAT_RETURN_REGEX: OnceLock<Regex> = OnceLock::new();
static FORMAT_PUSH_STR_REGEX: OnceLock<Regex> = OnceLock::new();
static FORMAT_STANDALONE_ARGUMENT_REGEX: OnceLock<Regex> = OnceLock::new();
static FORMAT_MATCH_ARM_REGEX: OnceLock<Regex> = OnceLock::new();
static FORMAT_LABEL_BINDING_REGEX: OnceLock<Regex> = OnceLock::new();
const FORMAT_OWNED_VALUE_PATTERNS: &[CloneOwnershipPattern] = &[
CloneOwnershipPattern {
cell: &FORMAT_FIELD_REGEX,
pattern: r"^\s*[A-Za-z_][A-Za-z0-9_]*\s*:\s+[^=]*format!\s*\(",
},
CloneOwnershipPattern {
cell: &FORMAT_ERR_REGEX,
pattern: r"\bErr\(\s*format!\s*\(",
},
CloneOwnershipPattern {
cell: &FORMAT_RETURN_REGEX,
pattern: r"^\s*return\s+format!\s*\(",
},
CloneOwnershipPattern {
cell: &FORMAT_PUSH_STR_REGEX,
pattern: r"\.(?:push_str|insert_str|write_str|write_all|write_fmt)\s*\(\s*&?\s*format!\s*\(",
},
CloneOwnershipPattern {
cell: &FORMAT_STANDALONE_ARGUMENT_REGEX,
pattern: r"^\s*&?format!\s*\(",
},
CloneOwnershipPattern {
cell: &FORMAT_MATCH_ARM_REGEX,
pattern: r"^\s*[^=;]+=>\s*&?format!\s*\(",
},
CloneOwnershipPattern {
cell: &FORMAT_LABEL_BINDING_REGEX,
pattern: r"^\s*let\s+[A-Za-z_][A-Za-z0-9_]*(?:label|message|desc|summary|title|name)[A-Za-z0-9_]*\s*=\s*&?format!\s*\(",
},
];
pub(crate) fn push_performance_finding(
file: &SourceFile,
block: &FunctionBlock,
check: &PerformanceCheck,
occurrences: usize,
findings: &mut Vec<Finding>,
) {
findings.push(block_finding_with_extras(
BlockFindingDescriptor {
rule_id: check.rule_id,
message: format!(
"Function `{}` calls {} inside a loop {} time(s).",
block.name, check.label, occurrences
),
file,
block,
severity: check.severity,
pillar: Pillar::Maintainability,
},
BlockFindingExtras {
confidence: check.confidence,
remediation: Some(check.remediation.to_string()),
metadata: json!({ "pattern": check.label, "occurrences": occurrences }),
},
));
}