Fix: Prevent cross-contamination of cache data across analysis modes for analyze tool (#5075)
This commit is contained in:
@@ -5,7 +5,7 @@ use std::sync::{Arc, Mutex};
|
|||||||
use std::time::SystemTime;
|
use std::time::SystemTime;
|
||||||
|
|
||||||
use super::lock_or_recover;
|
use super::lock_or_recover;
|
||||||
use crate::developer::analyze::types::AnalysisResult;
|
use crate::developer::analyze::types::{AnalysisMode, AnalysisResult};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct AnalysisCache {
|
pub struct AnalysisCache {
|
||||||
@@ -18,6 +18,7 @@ pub struct AnalysisCache {
|
|||||||
struct CacheKey {
|
struct CacheKey {
|
||||||
path: PathBuf,
|
path: PathBuf,
|
||||||
modified: SystemTime,
|
modified: SystemTime,
|
||||||
|
mode: AnalysisMode,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AnalysisCache {
|
impl AnalysisCache {
|
||||||
@@ -35,30 +36,43 @@ impl AnalysisCache {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get(&self, path: &PathBuf, modified: SystemTime) -> Option<AnalysisResult> {
|
pub fn get(
|
||||||
|
&self,
|
||||||
|
path: &PathBuf,
|
||||||
|
modified: SystemTime,
|
||||||
|
mode: &AnalysisMode,
|
||||||
|
) -> Option<AnalysisResult> {
|
||||||
let mut cache = lock_or_recover(&self.cache, |c| c.clear());
|
let mut cache = lock_or_recover(&self.cache, |c| c.clear());
|
||||||
let key = CacheKey {
|
let key = CacheKey {
|
||||||
path: path.clone(),
|
path: path.clone(),
|
||||||
modified,
|
modified,
|
||||||
|
mode: *mode,
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(result) = cache.get(&key) {
|
if let Some(result) = cache.get(&key) {
|
||||||
tracing::trace!("Cache hit for {:?}", path);
|
tracing::trace!("Cache hit for {:?} in {:?} mode", path, mode);
|
||||||
Some((**result).clone())
|
Some((**result).clone())
|
||||||
} else {
|
} else {
|
||||||
tracing::trace!("Cache miss for {:?}", path);
|
tracing::trace!("Cache miss for {:?} in {:?} mode", path, mode);
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn put(&self, path: PathBuf, modified: SystemTime, result: AnalysisResult) {
|
pub fn put(
|
||||||
|
&self,
|
||||||
|
path: PathBuf,
|
||||||
|
modified: SystemTime,
|
||||||
|
mode: &AnalysisMode,
|
||||||
|
result: AnalysisResult,
|
||||||
|
) {
|
||||||
let mut cache = lock_or_recover(&self.cache, |c| c.clear());
|
let mut cache = lock_or_recover(&self.cache, |c| c.clear());
|
||||||
let key = CacheKey {
|
let key = CacheKey {
|
||||||
path: path.clone(),
|
path: path.clone(),
|
||||||
modified,
|
modified,
|
||||||
|
mode: *mode,
|
||||||
};
|
};
|
||||||
|
|
||||||
tracing::trace!("Caching result for {:?}", path);
|
tracing::trace!("Caching result for {:?} in {:?} mode", path, mode);
|
||||||
cache.put(key, Arc::new(result));
|
cache.put(key, Arc::new(result));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -179,7 +179,7 @@ impl CodeAnalyzer {
|
|||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
if let Some(cached) = self.cache.get(&path.to_path_buf(), modified) {
|
if let Some(cached) = self.cache.get(&path.to_path_buf(), modified, mode) {
|
||||||
tracing::trace!("Using cached result for {:?}", path);
|
tracing::trace!("Using cached result for {:?}", path);
|
||||||
return Ok(cached);
|
return Ok(cached);
|
||||||
}
|
}
|
||||||
@@ -224,7 +224,8 @@ impl CodeAnalyzer {
|
|||||||
|
|
||||||
result.line_count = line_count;
|
result.line_count = line_count;
|
||||||
|
|
||||||
self.cache.put(path.to_path_buf(), modified, result.clone());
|
self.cache
|
||||||
|
.put(path.to_path_buf(), modified, mode, result.clone());
|
||||||
|
|
||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Tests for the cache module
|
// Tests for the cache module
|
||||||
|
|
||||||
use crate::developer::analyze::cache::AnalysisCache;
|
use crate::developer::analyze::cache::AnalysisCache;
|
||||||
use crate::developer::analyze::types::{AnalysisResult, FunctionInfo};
|
use crate::developer::analyze::types::{AnalysisMode, AnalysisResult, FunctionInfo};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::time::SystemTime;
|
use std::time::SystemTime;
|
||||||
|
|
||||||
@@ -32,15 +32,15 @@ fn test_cache_hit_miss() {
|
|||||||
let result = create_test_result();
|
let result = create_test_result();
|
||||||
|
|
||||||
// Initial miss
|
// Initial miss
|
||||||
assert!(cache.get(&path, time).is_none());
|
assert!(cache.get(&path, time, &AnalysisMode::Semantic).is_none());
|
||||||
|
|
||||||
// Store and hit
|
// Store and hit
|
||||||
cache.put(path.clone(), time, result.clone());
|
cache.put(path.clone(), time, &AnalysisMode::Semantic, result.clone());
|
||||||
assert!(cache.get(&path, time).is_some());
|
assert!(cache.get(&path, time, &AnalysisMode::Semantic).is_some());
|
||||||
|
|
||||||
// Different time = miss
|
// Different time = miss
|
||||||
let later = time + std::time::Duration::from_secs(1);
|
let later = time + std::time::Duration::from_secs(1);
|
||||||
assert!(cache.get(&path, later).is_none());
|
assert!(cache.get(&path, later, &AnalysisMode::Semantic).is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -50,18 +50,39 @@ fn test_cache_eviction() {
|
|||||||
let time = SystemTime::now();
|
let time = SystemTime::now();
|
||||||
|
|
||||||
// Fill cache
|
// Fill cache
|
||||||
cache.put(PathBuf::from("file1.rs"), time, result.clone());
|
cache.put(
|
||||||
cache.put(PathBuf::from("file2.rs"), time, result.clone());
|
PathBuf::from("file1.rs"),
|
||||||
|
time,
|
||||||
|
&AnalysisMode::Semantic,
|
||||||
|
result.clone(),
|
||||||
|
);
|
||||||
|
cache.put(
|
||||||
|
PathBuf::from("file2.rs"),
|
||||||
|
time,
|
||||||
|
&AnalysisMode::Semantic,
|
||||||
|
result.clone(),
|
||||||
|
);
|
||||||
assert_eq!(cache.len(), 2);
|
assert_eq!(cache.len(), 2);
|
||||||
|
|
||||||
// Add third item, should evict first
|
// Add third item, should evict first
|
||||||
cache.put(PathBuf::from("file3.rs"), time, result.clone());
|
cache.put(
|
||||||
|
PathBuf::from("file3.rs"),
|
||||||
|
time,
|
||||||
|
&AnalysisMode::Semantic,
|
||||||
|
result.clone(),
|
||||||
|
);
|
||||||
assert_eq!(cache.len(), 2);
|
assert_eq!(cache.len(), 2);
|
||||||
|
|
||||||
// First item should be evicted
|
// First item should be evicted
|
||||||
assert!(cache.get(&PathBuf::from("file1.rs"), time).is_none());
|
assert!(cache
|
||||||
assert!(cache.get(&PathBuf::from("file2.rs"), time).is_some());
|
.get(&PathBuf::from("file1.rs"), time, &AnalysisMode::Semantic)
|
||||||
assert!(cache.get(&PathBuf::from("file3.rs"), time).is_some());
|
.is_none());
|
||||||
|
assert!(cache
|
||||||
|
.get(&PathBuf::from("file2.rs"), time, &AnalysisMode::Semantic)
|
||||||
|
.is_some());
|
||||||
|
assert!(cache
|
||||||
|
.get(&PathBuf::from("file3.rs"), time, &AnalysisMode::Semantic)
|
||||||
|
.is_some());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -71,12 +92,12 @@ fn test_cache_clear() {
|
|||||||
let time = SystemTime::now();
|
let time = SystemTime::now();
|
||||||
let result = create_test_result();
|
let result = create_test_result();
|
||||||
|
|
||||||
cache.put(path.clone(), time, result);
|
cache.put(path.clone(), time, &AnalysisMode::Semantic, result);
|
||||||
assert!(!cache.is_empty());
|
assert!(!cache.is_empty());
|
||||||
|
|
||||||
cache.clear();
|
cache.clear();
|
||||||
assert!(cache.is_empty());
|
assert!(cache.is_empty());
|
||||||
assert!(cache.get(&path, time).is_none());
|
assert!(cache.get(&path, time, &AnalysisMode::Semantic).is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -89,6 +110,31 @@ fn test_cache_default() {
|
|||||||
let time = SystemTime::now();
|
let time = SystemTime::now();
|
||||||
let result = create_test_result();
|
let result = create_test_result();
|
||||||
|
|
||||||
cache.put(path.clone(), time, result);
|
cache.put(path.clone(), time, &AnalysisMode::Semantic, result);
|
||||||
assert!(cache.get(&path, time).is_some());
|
assert!(cache.get(&path, time, &AnalysisMode::Semantic).is_some());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_cache_mode_separation() {
|
||||||
|
let cache = AnalysisCache::new(10);
|
||||||
|
let path = PathBuf::from("test.rs");
|
||||||
|
let time = SystemTime::now();
|
||||||
|
let result = create_test_result();
|
||||||
|
|
||||||
|
// Store in structure mode
|
||||||
|
cache.put(path.clone(), time, &AnalysisMode::Structure, result.clone());
|
||||||
|
assert!(cache.get(&path, time, &AnalysisMode::Structure).is_some());
|
||||||
|
|
||||||
|
// Different mode should be a miss
|
||||||
|
assert!(cache.get(&path, time, &AnalysisMode::Semantic).is_none());
|
||||||
|
|
||||||
|
// Store in semantic mode
|
||||||
|
cache.put(path.clone(), time, &AnalysisMode::Semantic, result.clone());
|
||||||
|
|
||||||
|
// Both modes should now have cached results
|
||||||
|
assert!(cache.get(&path, time, &AnalysisMode::Structure).is_some());
|
||||||
|
assert!(cache.get(&path, time, &AnalysisMode::Semantic).is_some());
|
||||||
|
|
||||||
|
// Cache should contain 2 entries (one per mode)
|
||||||
|
assert_eq!(cache.len(), 2);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -131,7 +131,7 @@ pub struct FocusedAnalysisData<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Analysis modes
|
/// Analysis modes
|
||||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
pub enum AnalysisMode {
|
pub enum AnalysisMode {
|
||||||
Structure, // Directory overview
|
Structure, // Directory overview
|
||||||
Semantic, // File details
|
Semantic, // File details
|
||||||
|
|||||||
Reference in New Issue
Block a user