-
Notifications
You must be signed in to change notification settings - Fork 2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Initial code review #1
Conversation
lib/src/internal/voidstar_handler.rs
Outdated
let payload = value.to_string(); | ||
unsafe { | ||
let json_data_func: Symbol<unsafe extern fn(s: *const c_char)> = self.voidstar_lib.get(b"fuzz_json_data").unwrap(); | ||
let payload = CString::new(value.to_string())?; | ||
json_data_func(payload.as_ptr()); | ||
let json_data_func: Symbol<unsafe extern fn(s: *const c_char, l: size_t)> = self.voidstar_lib.get(b"fuzz_json_data").unwrap(); | ||
json_data_func(payload.as_bytes().as_ptr() as *const c_char, payload.len()); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fuzz_json_data
in instrumentation.h
accepts a data pointer and a length (in bytes), instead of a null-terminated C-style string. Update this call to pass in the correct parameters.
let mut tracker = ASSERT_TRACKER.lock().unwrap(); | ||
let info = tracker.entry(self.id.clone()).or_default(); | ||
// Record the condition in the associated TrackingInfo entry, | ||
// and emit the assertion when first seeing a condition | ||
let emitting = if self.condition { | ||
info.pass_count += 1; | ||
info.pass_count == 1 | ||
} else { | ||
info.fail_count += 1; | ||
info.fail_count == 1 | ||
}; | ||
|
||
drop(tracker); // release the lock asap | ||
if emitting { | ||
self.emit(); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minimize the critical section in which we hold the lock to ASSERT_TRACKER
.
self.emit() | ||
} | ||
let mut tracker = ASSERT_TRACKER.lock().unwrap(); | ||
let info = tracker.entry(self.id.clone()).or_default(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Uses the entry API to simplify constructing a default TrackingInfo
for a vacant tracking key.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With the access to the ASSERT_TRACKER contained (is it?) entirely here, consider relocating ASSERT_TRACKER to this function.
@@ -260,7 +243,7 @@ pub fn assert_impl( | |||
|
|||
|
|||
let assertion = AssertionInfo::new(assert_type, display_type, condition, message, class, function, file, begin_line, begin_column, hit, must_hit, id, details); | |||
let _ = &assertion.track_entry(ASSERT_TRACKER.lock().as_deref_mut().unwrap()); | |||
let _ = &assertion.track_entry(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let track_entry
obtain the lock to ASSERT_TRACKER
internally (see above) instead of having the caller locking it and passing in as a parameter. This helps with minimizing the critical section.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need to ignore the return value from .track_entry() - there is no longer a return value defined
static PROTOCOL_VERSION: &str = "1.0.0"; | ||
const PROTOCOL_VERSION: &str = "1.0.0"; | ||
|
||
// Tracks SDK releases | ||
static SDK_VERSION: &str = "0.1.1"; | ||
const SDK_VERSION: &str = "0.1.1"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const
is preferred over static
in these cases, as suggested in https://doc.rust-lang.org/reference/items/static-items.html#using-statics-or-consts.
@@ -9,7 +9,7 @@ libloading = "0.8.3" | |||
libc = "0.2.153" | |||
rand = "0.8.5" | |||
rustc_version_runtime = "0.3.0" | |||
lazy_static = "1.4.0" | |||
once_cell = "1" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lazy_static
is effectively unmaintained and would be deprecated (rust-lang-nursery/lazy-static.rs#214). once_cell
is the recommended migration path.
If we decide to only support rust 1.70+ (which is higher than our current internal toolchain version 1.69), we can implement the functionality from once_cell
using OnceLock
and cutoff the once_cell
dependencies.
A more direct std
substitute for once_cell::sync::Lazy
is tracked in the unstable lazy_cell
feature.
@@ -27,7 +26,7 @@ impl LocalHandler { | |||
// Seems like LocalHandler gets bound to a reference with | |||
// a 'static lifetime. | |||
LocalHandler{ | |||
maybe_writer: Some(BufWriter::with_capacity(0, Box::new(f))) | |||
maybe_writer: Some(f) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure about the point of using a zero-capacity BufWriter
. Since BufWriter
is a wrapper over some underlying writer W
, and writing to BufWriter
only potentially writes (but not flush) to W
, writing to a zero-capacity BufWriter
should be equivalent to directly writing to W
.
pub struct LocalHandler { | ||
maybe_writer: Option<BufWriter<Box<dyn Write + Send>>> | ||
maybe_writer: Option<File> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I hard coded the inner writer to File
for now as it avoids a dynamic dispatch redirection introduced by Box<dyn Write>
. Might have to revisit when we ever have to support dynamically switching the write sink for some reason?
// static mut LIB_HANDLER: Option<Box<dyn LibHandler>> = None; | ||
static LIB_HANDLER: Mutex<Option<Box<dyn LibHandler + Send>>> = Mutex::new(None); | ||
static LIB_HANDLER: Lazy<Box<dyn LibHandler + Sync + Send>> = Lazy::new(|| { | ||
match VoidstarHandler::try_load() { | ||
Ok(handler) => Box::new(handler), | ||
Err(_) => Box::new(LocalHandler::new()), | ||
} | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Switch to using a Lazy
cell for the global handler. Now the initialization logic for LIB_HANDLER
would be called when it's content is access for the first time. So we can also remove manual calls to instantiate_handler()
further down.
Lazy
should have lower overhead compared to a Mutex
, and we can remove the Mutex
to guard against concurrent calls on LIB_HANDLER
as we changed how LibHandler
is implemented below.
trait LibHandler { | ||
fn output(&mut self, value: &Value) -> Result<(), Error>; | ||
fn output(&self, value: &Value) -> Result<(), Error>; | ||
fn random(&self) -> u64; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ensure all LibHandler
methods only takes &self
. This has the benefit that all calls to those methods on LIB_HANDLER
would be thread-safe without an extra Mutex
.
The original user of the &mut self
parameter is the implementation of LocalHandler::output
, but we can avoid that since now LocalHandler
stores an inner File
, and &File
implements Write
already.
lib/src/internal/voidstar_handler.rs
Outdated
pub fn new() -> Self { | ||
load_voidstar(); | ||
let lib = LIB_VOIDSTAR.lock().unwrap().as_ref().unwrap().clone(); | ||
VoidstarHandler{ | ||
voidstar_lib: lib, | ||
} | ||
pub fn try_load() -> Result<Self, libloading::Error> { | ||
let lib = unsafe { Library::new(LIB_NAME) }?; | ||
Ok(VoidstarHandler { voidstar_lib: lib }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should be able to avoid maintaining a local static for initializing the voidstar library here if the initialization logic at internal/mod.rs
is enough?
lib/src/internal/voidstar_handler.rs
Outdated
voidstar_lib: Library, | ||
// Not used directly but exists to ensure the library is loaded | ||
// and all the following function pointers points to valid memory. | ||
_lib: Library, | ||
// SAFETY: The memory pointed by `s` must be valid up to `l` bytes. | ||
fuzz_json_data: unsafe fn(s: *const c_char, l: size_t), | ||
fuzz_get_random: fn() -> u64, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Switch to storing the function pointers of the symbols inline in the struct, so we can avoid repeated get
for symbols in the output
and random
calls. See the SAFETY comments for all the unsafe blocks.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Really should verify/enforce that calls to fuzz_json_data() and fuzz_get_random() are thread-safe.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To enforce safe use, mark fuzz_get_random() as unsafe (and handle this constraint in callers), and use a mutex when calling either one of these ffi symbols. Check if there are any guarantees in libvoidstar() for thread safe operation.
#[derive(Debug)] | ||
pub struct VoidstarHandler { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is the Debug
implementation used anywhere?
pub fn random_choice<T: Copy>(slice: &[T]) -> Option<T> { | ||
let lx: usize = slice.len(); | ||
match lx { | ||
0 => None, | ||
1 => Some(slice[0]), | ||
pub fn random_choice<T>(slice: &[T]) -> Option<&T> { | ||
match slice { | ||
[] => None, | ||
[x] => Some(x), | ||
_ => { | ||
let idx: usize = (get_random() as usize) % lx; | ||
Some(slice[idx]) | ||
let idx: usize = (get_random() as usize) % slice.len(); | ||
Some(&slice[idx]) | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Requiring T: Copy
is a bit too restrictive, returns a borrow to an item in the slice instead (similar to how the first
method on slice is declared: https://doc.rust-lang.org/std/primitive.slice.html#method.first). Also switch to using slice pattern in the match
.
Some general comments/notes:
|
info.pass_count == 1 | ||
} else { | ||
info.fail_count += 1; | ||
info.fail_count == 1 | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps comment out the setting of emitting
here as whether or not, pass_count/fail_count is exactly '1' at this point, and emit is required.
let mut text_line = value.to_string(); | ||
text_line.push('\n'); | ||
b2w.write_all(text_line.as_bytes())?; | ||
let mut b2w = b2w; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider using a separate name for the mutable b2w, rather than shadowing the Some(b2w)
In progress branch to track some suggested changes.
I'll put some comments alongside my changes as CR comments.