1
0
Fork 0

Enable assert_no_alloc in debug builds

This commit is contained in:
Robbert van der Helm 2022-02-03 15:58:00 +01:00
parent 2ca54d220d
commit dfedd7b2c4
6 changed files with 147 additions and 106 deletions

8
Cargo.lock generated
View file

@ -8,6 +8,12 @@ version = "1.0.53"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94a45b455c14666b85fc40a019e8ab9eb75e3a124e05494f5397122bc9eb06e0" checksum = "94a45b455c14666b85fc40a019e8ab9eb75e3a124e05494f5397122bc9eb06e0"
[[package]]
name = "assert_no_alloc"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55ca83137a482d61d916ceb1eba52a684f98004f18e0cafea230fe5579c178a3"
[[package]] [[package]]
name = "autocfg" name = "autocfg"
version = "1.0.1" version = "1.0.1"
@ -142,6 +148,8 @@ dependencies = [
name = "nih_plug" name = "nih_plug"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"assert_no_alloc",
"cfg-if",
"crossbeam", "crossbeam",
"lazy_static", "lazy_static",
"nih_plug_derive", "nih_plug_derive",

View file

@ -18,9 +18,18 @@ members = [
nih_plug_derive = { path = "nih_plug_derive" } nih_plug_derive = { path = "nih_plug_derive" }
crossbeam = "0.8" crossbeam = "0.8"
cfg-if = "1.0"
lazy_static = "1.4" lazy_static = "1.4"
parking_lot = "0.12" parking_lot = "0.12"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0" serde_json = "1.0"
vst3-sys = { git = "https://github.com/robbert-vdh/vst3-sys.git", branch = "fix/atomic-reference-count" } vst3-sys = { git = "https://github.com/robbert-vdh/vst3-sys.git", branch = "fix/atomic-reference-count" }
widestring = "1.0.0-beta.1" widestring = "1.0.0-beta.1"
assert_no_alloc = { version = "1.1", optional = true }
[features]
default = []
# Enabling this feature will cause the plugin to terminate when allocations
# occur in the processing function while compiling in debug mode.
assert_process_allocs = ["assert_no_alloc"]

View file

@ -8,6 +8,6 @@ license = "GPL-3.0-or-later"
crate-type = ["cdylib"] crate-type = ["cdylib"]
[dependencies] [dependencies]
nih_plug = { path = "../../" } nih_plug = { path = "../../", features = ["assert_process_allocs"] }
parking_lot = "0.12" parking_lot = "0.12"

View file

@ -8,4 +8,4 @@ license = "GPL-3.0-or-later"
crate-type = ["cdylib"] crate-type = ["cdylib"]
[dependencies] [dependencies]
nih_plug = { path = "../../" } nih_plug = { path = "../../", features = ["assert_process_allocs"] }

View file

@ -19,6 +19,10 @@ use std::os::raw::c_char;
use vst3_sys::vst::TChar; use vst3_sys::vst::TChar;
use widestring::U16CString; use widestring::U16CString;
#[cfg(all(debug_assertions, feature = "assert_process_allocs"))]
#[global_allocator]
static A: assert_no_alloc::AllocDisabler = assert_no_alloc::AllocDisabler;
/// A Rabin fingerprint based string hash for parameter ID strings. /// A Rabin fingerprint based string hash for parameter ID strings.
pub fn hash_param_id(id: &str) -> u32 { pub fn hash_param_id(id: &str) -> u32 {
let mut overflow; let mut overflow;
@ -83,3 +87,16 @@ pub fn u16strlcpy(dest: &mut [TChar], src: &str) {
dest[..copy_len].copy_from_slice(&src_utf16_chars_signed[..copy_len]); dest[..copy_len].copy_from_slice(&src_utf16_chars_signed[..copy_len]);
dest[copy_len] = 0; dest[copy_len] = 0;
} }
/// A wrapper around the entire process function, including the plugin wrapper parts. This sets up
/// `assert_no_alloc` if needed, while also making sure that things like FTZ are set up correctly if
/// the host has not already done so.
pub fn process_wrapper<T, F: FnOnce() -> T>(f: F) -> T {
cfg_if::cfg_if! {
if #[cfg(all(debug_assertions, feature = "assert_process_allocs"))] {
assert_no_alloc::assert_no_alloc(f)
} else {
f()
}
}
}

View file

@ -47,7 +47,7 @@ use crate::param::range::Range;
use crate::param::Param; use crate::param::Param;
use crate::plugin::{BufferConfig, BusConfig, Plugin, ProcessStatus, Vst3Plugin}; use crate::plugin::{BufferConfig, BusConfig, Plugin, ProcessStatus, Vst3Plugin};
use crate::wrapper::state::{ParamValue, State}; use crate::wrapper::state::{ParamValue, State};
use crate::wrapper::util::{hash_param_id, strlcpy, u16strlcpy}; use crate::wrapper::util::{hash_param_id, process_wrapper, strlcpy, u16strlcpy};
// Alias needed for the VST3 attribute macro // Alias needed for the VST3 attribute macro
use vst3_sys as vst3_com; use vst3_sys as vst3_com;
@ -972,6 +972,9 @@ impl<P: Plugin> IAudioProcessor for Wrapper<'_, P> {
unsafe fn process(&self, data: *mut vst3_sys::vst::ProcessData) -> tresult { unsafe fn process(&self, data: *mut vst3_sys::vst::ProcessData) -> tresult {
check_null_ptr!(data); check_null_ptr!(data);
// Panic on allocations if the `assert_process_allocs` feature has been enabled, and make
// sure that FTZ is set up correctly
process_wrapper(|| {
// We need to handle incoming automation first // We need to handle incoming automation first
let data = &*data; let data = &*data;
let sample_rate = self let sample_rate = self
@ -1036,15 +1039,18 @@ impl<P: Plugin> IAudioProcessor for Wrapper<'_, P> {
(*data.outputs).buffers, (*data.outputs).buffers,
); );
// This vector has been reallocated to contain enough slices as there are output channels // This vector has been reallocated to contain enough slices as there are output
// channels
let mut output_buffer = self.inner.output_buffer.write(); let mut output_buffer = self.inner.output_buffer.write();
{ {
let output_slices = output_buffer.as_raw_vec(); let output_slices = output_buffer.as_raw_vec();
nih_debug_assert_eq!(num_output_channels, output_slices.len()); nih_debug_assert_eq!(num_output_channels, output_slices.len());
for (output_channel_idx, output_channel_slice) in output_slices.iter_mut().enumerate() { for (output_channel_idx, output_channel_slice) in
output_slices.iter_mut().enumerate()
{
// SAFETY: These pointers may not be valid outside of this function even though // SAFETY: These pointers may not be valid outside of this function even though
// their lifetime is equal to this structs. This is still safe because they are only // their lifetime is equal to this structs. This is still safe because they are
// dereferenced here later as part of this process function. // only dereferenced here later as part of this process function.
*output_channel_slice = std::slice::from_raw_parts_mut( *output_channel_slice = std::slice::from_raw_parts_mut(
*((*data.outputs).buffers as *mut *mut f32).add(output_channel_idx), *((*data.outputs).buffers as *mut *mut f32).add(output_channel_idx),
data.num_samples as usize, data.num_samples as usize,
@ -1053,8 +1059,8 @@ impl<P: Plugin> IAudioProcessor for Wrapper<'_, P> {
} }
// Most hosts process data in place, in which case we don't need to do any copying // Most hosts process data in place, in which case we don't need to do any copying
// ourselves. If the pointers do not alias, then we'll do the copy here and then the plugin // ourselves. If the pointers do not alias, then we'll do the copy here and then the
// can just do normal in place processing. // plugin can just do normal in place processing.
if !data.inputs.is_null() { if !data.inputs.is_null() {
let num_input_channels = (*data.inputs).num_channels as usize; let num_input_channels = (*data.inputs).num_channels as usize;
nih_debug_assert!( nih_debug_assert!(
@ -1090,6 +1096,7 @@ impl<P: Plugin> IAudioProcessor for Wrapper<'_, P> {
} }
_ => kResultOk, _ => kResultOk,
} }
})
} }
unsafe fn get_tail_samples(&self) -> u32 { unsafe fn get_tail_samples(&self) -> u32 {