diff --git a/cargo-pgrx/src/command/init.rs b/cargo-pgrx/src/command/init.rs index b0a33b2411..41741b94f6 100644 --- a/cargo-pgrx/src/command/init.rs +++ b/cargo-pgrx/src/command/init.rs @@ -70,6 +70,13 @@ pub(crate) struct Init { base_testing_port: Option, #[clap(long, help = "Additional flags to pass to the configure script")] configure_flag: Vec, + /// Compile PostgreSQL with the necessary flags to detect a good amount of + /// memory errors when run under Valgrind. + /// + /// Building PostgreSQL with these flags requires that Valgrind be + /// installed, but the resulting build is usable without valgrind. + #[clap(long)] + valgrind: bool, } impl CommandExecute for Init { @@ -280,8 +287,20 @@ fn configure_postgres(pg_config: &PgConfig, pgdir: &PathBuf, init: &Init) -> eyr let mut configure_path = pgdir.clone(); configure_path.push("configure"); let mut command = std::process::Command::new(configure_path); + // Some of these are redundant with `--enable-debug`. + let mut existing_cppflags = std::env::var("CPPFLAGS").unwrap_or_default(); + existing_cppflags += " -DMEMORY_CONTEXT_CHECKING=1 -DMEMORY_CONTEXT_CHECKING=1 \ + -DCLOBBER_FREED_MEMORY=1 -DRANDOMIZE_ALLOCATED_MEMORY=1 "; + if init.valgrind { + // `-Og` turns on light optimizations, which is useful for getting + // something usable. `USE_VALGRIND` allows valgrind to understand PG's + // memory context shenanigans. + let valgrind_flags = "-DUSE_VALGRIND=1 -Og "; + existing_cppflags += valgrind_flags; + } command + .env("CPPFLAGS", existing_cppflags) .arg(format!("--prefix={}", get_pg_installdir(pgdir).display())) .arg(format!("--with-pgport={}", pg_config.port()?)) .arg("--enable-debug") diff --git a/pgrx-tests/src/framework.rs b/pgrx-tests/src/framework.rs index 69b6d9f72f..9174473866 100644 --- a/pgrx-tests/src/framework.rs +++ b/pgrx-tests/src/framework.rs @@ -446,8 +446,31 @@ fn start_pg(loglines: LogLines) -> eyre::Result { wait_for_pidfile()?; let pg_config = get_pg_config()?; - let mut command = - Command::new(pg_config.postmaster_path().wrap_err("unable to determine postmaster path")?); + let postmaster_path = + pg_config.postmaster_path().wrap_err("unable to determine postmaster path")?; + + let mut command = if use_valgrind() { + let mut cmd = Command::new("valgrind").args([ + "--leak-check=no", + "--gen-suppressions=all", + "--time-stamp=yes", + "--error-markers=VALGRINDERROR-BEGIN,VALGRINDERROR-END", + "--log-file=target/pg-valgrind-%p.log", + "--trace-children=yes", + ]); + // Try to provide a suppressions file, we'll likely get false positives + // if we can't, but that might be better than nothing. + if let Ok(path) = valgrind_suppressions_path(&pg_config) { + if path.exists() { + cmd.arg(format!("--suppressions={}", path.display())); + } + } + + cmd.arg(postmaster_path); + cmd + } else { + Command::new(postmaster_path) + }; command .arg("-D") .arg(get_pgdata_path()?.to_str().unwrap()) @@ -469,6 +492,13 @@ fn start_pg(loglines: LogLines) -> eyre::Result { Ok(session_id) } +fn valgrind_suppressions_path(pg_config: &PgConfig) -> Result { + let mut home = Pgrx::home()?; + home.push(pg_config.version()?); + home.push("src/tools/valgrind.supp"); + Ok(home) +} + fn wait_for_pidfile() -> Result<(), eyre::Report> { const MAX_PIDFILE_RETRIES: usize = 10; @@ -756,3 +786,7 @@ fn find_on_path(program: &str) -> Option { let paths = std::env::var_os("PATH")?; std::env::split_paths(&paths).map(|p| p.join(program)).find(|abs| abs.exists()) } + +fn use_valgrind() -> bool { + std::env::var_os("USE_VALGRIND").is_some() +}