Skip to content

Commit fb08f3e

Browse files
authored
Create concrete playback temp files in source directory (rust-lang#2804)
Concrete playback seeks to generate unit tests in the original source file. An intermediate temporary file is used to reduce the risk of corrupting original source files. Once the temporary file has been completely written to, it is to be atomically moved to replace the source file. This can only be done on the same file system. We now create the temporary file in the same source directory as the original source file to ensure we are on the same file system. The implementation uses NamedTempFile from the tempfile crate, which renders our own tempfile implementation redundant. Tested on Ubuntu 20.04 with /tmp on a different partition (where our regression tests would previously fail). Resolves: rust-lang#2705
1 parent 62f136c commit fb08f3e

File tree

4 files changed

+35
-89
lines changed

4 files changed

+35
-89
lines changed

Cargo.lock

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -374,6 +374,12 @@ dependencies = [
374374
"libc",
375375
]
376376

377+
[[package]]
378+
name = "fastrand"
379+
version = "2.0.1"
380+
source = "registry+https://github.com/rust-lang/crates.io-index"
381+
checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5"
382+
377383
[[package]]
378384
name = "getopts"
379385
version = "0.2.21"
@@ -504,6 +510,7 @@ dependencies = [
504510
"serde_json",
505511
"strum 0.25.0",
506512
"strum_macros 0.25.2",
513+
"tempfile",
507514
"toml",
508515
"tracing",
509516
"tracing-subscriber",
@@ -1190,6 +1197,19 @@ dependencies = [
11901197
"unicode-ident",
11911198
]
11921199

1200+
[[package]]
1201+
name = "tempfile"
1202+
version = "3.8.0"
1203+
source = "registry+https://github.com/rust-lang/crates.io-index"
1204+
checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef"
1205+
dependencies = [
1206+
"cfg-if",
1207+
"fastrand",
1208+
"redox_syscall",
1209+
"rustix",
1210+
"windows-sys 0.48.0",
1211+
]
1212+
11931213
[[package]]
11941214
name = "thiserror"
11951215
version = "1.0.49"

kani-driver/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ rayon = "1.5.3"
2929
comfy-table = "7.0.1"
3030
strum = {version = "0.25.0"}
3131
strum_macros = {version = "0.25.2"}
32+
tempfile = "3"
3233
tracing = {version = "0.1", features = ["max_level_trace", "release_max_level_debug"]}
3334
tracing-subscriber = {version = "0.3.8", features = ["env-filter", "json", "fmt"]}
3435
rand = "0.8"

kani-driver/src/concrete_playback/test_generator.rs

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
use crate::args::ConcretePlaybackMode;
88
use crate::call_cbmc::VerificationResult;
99
use crate::session::KaniSession;
10-
use crate::util::tempfile::TempFile;
1110
use anyhow::{Context, Result};
1211
use concrete_vals_extractor::{extract_harness_values, ConcreteVal};
1312
use kani_metadata::HarnessMetadata;
@@ -18,6 +17,7 @@ use std::hash::{Hash, Hasher};
1817
use std::io::{BufRead, BufReader, Write};
1918
use std::path::Path;
2019
use std::process::Command;
20+
use tempfile::NamedTempFile;
2121

2222
impl KaniSession {
2323
/// The main driver for generating concrete playback unit tests and adding them to source code.
@@ -158,25 +158,28 @@ impl KaniSession {
158158

159159
// Create temp file
160160
if !unit_tests.is_empty() {
161-
let mut temp_file = TempFile::try_new("concrete_playback.tmp")?;
161+
let source_basedir = Path::new(source_path).parent().unwrap_or(Path::new("."));
162+
let mut temp_file = NamedTempFile::with_prefix_in("concrete_playback", source_basedir)?;
162163
let mut curr_line_num = 0;
163164

164165
// Use a buffered reader/writer to generate the unit test line by line
165166
for line in source_reader.lines().flatten() {
166167
curr_line_num += 1;
167-
if let Some(temp_writer) = temp_file.writer.as_mut() {
168-
writeln!(temp_writer, "{line}")?;
169-
if curr_line_num == proof_harness_end_line {
170-
for unit_test in unit_tests.iter() {
171-
for unit_test_line in unit_test.code.iter() {
172-
curr_line_num += 1;
173-
writeln!(temp_writer, "{unit_test_line}")?;
174-
}
168+
writeln!(temp_file, "{line}")?;
169+
if curr_line_num == proof_harness_end_line {
170+
for unit_test in unit_tests.iter() {
171+
for unit_test_line in unit_test.code.iter() {
172+
curr_line_num += 1;
173+
writeln!(temp_file, "{unit_test_line}")?;
175174
}
176175
}
177176
}
178177
}
179-
temp_file.rename(source_path).expect("Could not rename file");
178+
179+
// Renames are usually automic, so we won't corrupt the user's source file during a
180+
// crash; but first flush all updates to disk, which persist wouldn't take care of.
181+
temp_file.as_file().sync_all()?;
182+
temp_file.persist(source_path).expect("Could not rename file");
180183
}
181184

182185
Ok(!unit_tests.is_empty())

kani-driver/src/util.rs

Lines changed: 0 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -14,84 +14,6 @@ use std::ffi::OsString;
1414
use std::path::{Path, PathBuf};
1515
use std::process::Command;
1616

17-
pub mod tempfile {
18-
use std::{
19-
env,
20-
fs::{self, rename, File},
21-
io::{BufWriter, Error, Write},
22-
path::PathBuf,
23-
};
24-
25-
use crate::util;
26-
use ::rand;
27-
use anyhow::Context;
28-
use rand::Rng;
29-
30-
/// Handle a writable temporary file which will be deleted when the object is dropped.
31-
/// To save the contents of the file, users can invoke `rename` which will move the file to
32-
/// its new location and no further deletion will be performed.
33-
pub struct TempFile {
34-
pub file: File,
35-
pub temp_path: PathBuf,
36-
pub writer: Option<BufWriter<File>>,
37-
renamed: bool,
38-
}
39-
40-
impl TempFile {
41-
/// Create a temp file
42-
pub fn try_new(suffix_name: &str) -> Result<Self, Error> {
43-
let mut temp_path = env::temp_dir();
44-
45-
// Generate a unique name for the temporary directory
46-
let hash: u32 = rand::thread_rng().gen();
47-
let file_name: &str = &format!("kani_tmp_{hash}_{suffix_name}");
48-
49-
temp_path.push(file_name);
50-
let temp_file = File::create(&temp_path)?;
51-
let writer = BufWriter::new(temp_file.try_clone()?);
52-
53-
Ok(Self { file: temp_file, temp_path, writer: Some(writer), renamed: false })
54-
}
55-
56-
/// Rename the temporary file to the new path, replacing the original file if the path points to a file that already exists.
57-
pub fn rename(mut self, source_path: &str) -> Result<(), String> {
58-
// flush here
59-
self.writer.as_mut().unwrap().flush().unwrap();
60-
self.writer = None;
61-
// Renames are usually automic, so we won't corrupt the user's source file during a crash.
62-
rename(&self.temp_path, source_path)
63-
.with_context(|| format!("Error renaming file {}", self.temp_path.display()))
64-
.unwrap();
65-
self.renamed = true;
66-
Ok(())
67-
}
68-
}
69-
70-
/// Ensure that the bufwriter is flushed and temp variables are dropped
71-
/// everytime the tempfile is out of scope
72-
/// note: the fields for the struct are dropped automatically by destructor
73-
impl Drop for TempFile {
74-
fn drop(&mut self) {
75-
// if writer is not flushed, flush it
76-
if self.writer.as_ref().is_some() {
77-
// couldn't use ? as drop does not handle returns
78-
if let Err(e) = self.writer.as_mut().unwrap().flush() {
79-
util::warning(
80-
format!("failed to flush {}: {e}", self.temp_path.display()).as_str(),
81-
);
82-
}
83-
self.writer = None;
84-
}
85-
86-
if !self.renamed {
87-
if let Err(_e) = fs::remove_file(&self.temp_path.clone()) {
88-
util::warning(&format!("Error removing file {}", self.temp_path.display()));
89-
}
90-
}
91-
}
92-
}
93-
}
94-
9517
/// Replace an extension with another one, in a new PathBuf. (See tests for examples)
9618
pub fn alter_extension(path: &Path, ext: &str) -> PathBuf {
9719
path.with_extension(ext)

0 commit comments

Comments
 (0)