pgrx_pg_sys/submodules/panic.rs
1//LICENSE Portions Copyright 2019-2021 ZomboDB, LLC.
2//LICENSE
3//LICENSE Portions Copyright 2021-2023 Technology Concepts & Design, Inc.
4//LICENSE
5//LICENSE Portions Copyright 2023-2023 PgCentral Foundation, Inc. <contact@pgcentral.org>
6//LICENSE
7//LICENSE All rights reserved.
8//LICENSE
9//LICENSE Use of this source code is governed by the MIT license that can be found in the LICENSE file.
10#![deny(unsafe_op_in_unsafe_fn)]
11#![allow(non_snake_case)]
12
13use core::ffi::CStr;
14use std::any::Any;
15use std::borrow::Cow;
16use std::cell::Cell;
17use std::fmt::{Display, Formatter};
18use std::hint::unreachable_unchecked;
19use std::panic::{
20 AssertUnwindSafe, Location, PanicHookInfo, UnwindSafe, catch_unwind, panic_any, resume_unwind,
21};
22
23use crate::elog::PgLogLevel;
24use crate::errcodes::PgSqlErrorCode;
25use crate::{AsPgCStr, MemoryContextSwitchTo, pfree};
26
27/// Indicates that something can be reported as a Postgres ERROR, if that's what it might represent.
28pub trait ErrorReportable {
29 type Inner;
30
31 /// Raise a Postgres ERROR if appropriate, otherwise return a value
32 fn unwrap_or_report(self) -> Self::Inner;
33}
34
35impl<T, E> ErrorReportable for Result<T, E>
36where
37 E: Any + Display,
38{
39 type Inner = T;
40
41 /// If this [`Result`] represents the `Ok` variant, that value is returned.
42 ///
43 /// If this [`Result`] represents the `Err` variant, raise it as an error. If it happens to
44 /// be an [`ErrorReport`], then that is specifically raised. Otherwise it's just a general
45 /// [`ereport!`] as a [`PgLogLevel::ERROR`].
46 fn unwrap_or_report(self) -> Self::Inner {
47 self.unwrap_or_else(|e| {
48 let any: Box<&dyn Any> = Box::new(&e);
49 if any.downcast_ref::<ErrorReport>().is_some() {
50 let any: Box<dyn Any> = Box::new(e);
51 any.downcast::<ErrorReport>().unwrap().report(PgLogLevel::ERROR);
52 unreachable!();
53 } else {
54 ereport!(ERROR, PgSqlErrorCode::ERRCODE_DATA_EXCEPTION, format!("{e}"));
55 }
56 })
57 }
58}
59
60#[derive(Debug)]
61pub struct ErrorReportLocation {
62 pub(crate) file: String,
63 pub(crate) funcname: Option<String>,
64 pub(crate) line: u32,
65 pub(crate) col: u32,
66 pub(crate) backtrace: Option<std::backtrace::Backtrace>,
67}
68
69impl Default for ErrorReportLocation {
70 fn default() -> Self {
71 Self {
72 file: std::string::String::from("<unknown>"),
73 funcname: None,
74 line: 0,
75 col: 0,
76 backtrace: None,
77 }
78 }
79}
80
81impl Display for ErrorReportLocation {
82 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
83 match &self.funcname {
84 Some(funcname) => {
85 // mimic's Postgres' output for this, but includes a column number
86 write!(f, "{}, {}:{}:{}", funcname, self.file, self.line, self.col)?;
87 }
88
89 None => {
90 write!(f, "{}:{}:{}", self.file, self.line, self.col)?;
91 }
92 }
93
94 if let Some(backtrace) = &self.backtrace
95 && backtrace.status() == std::backtrace::BacktraceStatus::Captured
96 {
97 write!(f, "\n{backtrace}")?;
98 }
99
100 Ok(())
101 }
102}
103
104impl From<&Location<'_>> for ErrorReportLocation {
105 fn from(location: &Location<'_>) -> Self {
106 Self {
107 file: location.file().to_string(),
108 funcname: None,
109 line: location.line(),
110 col: location.column(),
111 backtrace: None,
112 }
113 }
114}
115
116impl From<&PanicHookInfo<'_>> for ErrorReportLocation {
117 fn from(pi: &PanicHookInfo<'_>) -> Self {
118 pi.location().map(|l| l.into()).unwrap_or_default()
119 }
120}
121
122/// Represents the set of information necessary for pgrx to promote a Rust `panic!()` to a Postgres
123/// `ERROR` (or any [`PgLogLevel`] level)
124#[derive(Debug)]
125pub struct ErrorReport {
126 pub(crate) sqlerrcode: PgSqlErrorCode,
127 pub(crate) message: Cow<'static, str>,
128 pub(crate) hint: Option<String>,
129 pub(crate) detail: Option<String>,
130 pub(crate) domain: Option<String>,
131 pub(crate) location: ErrorReportLocation,
132}
133
134impl Display for ErrorReport {
135 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
136 write!(f, "{}: {}", self.sqlerrcode, self.message)?;
137 if let Some(hint) = &self.hint {
138 write!(f, "\nHINT: {hint}")?;
139 }
140 if let Some(detail) = &self.detail {
141 write!(f, "\nDETAIL: {detail}")?;
142 }
143 write!(f, "\nLOCATION: {}", self.location)
144 }
145}
146
147#[derive(Debug)]
148pub struct ErrorReportWithLevel {
149 pub(crate) level: PgLogLevel,
150 pub(crate) inner: ErrorReport,
151}
152
153impl ErrorReportWithLevel {
154 fn report(self) {
155 match self.level {
156 // ERRORs get converted into panics so they can perform proper stack unwinding
157 PgLogLevel::ERROR => panic_any(self),
158
159 // FATAL and PANIC are reported directly to Postgres -- they abort the process
160 PgLogLevel::FATAL | PgLogLevel::PANIC => {
161 do_ereport(self);
162 unreachable!()
163 }
164
165 // Everything else (INFO, WARN, LOG, DEBUG, etc) are reported to Postgres too but they only emit messages
166 _ => do_ereport(self),
167 }
168 }
169
170 /// Returns the logging level of this error report
171 pub fn level(&self) -> PgLogLevel {
172 self.level
173 }
174
175 /// Returns the sql error code of this error report
176 pub fn sql_error_code(&self) -> PgSqlErrorCode {
177 self.inner.sqlerrcode
178 }
179
180 /// Returns the error message of this error report
181 pub fn message(&self) -> &str {
182 self.inner.message()
183 }
184
185 /// Returns the detail line of this error report, if there is one
186 pub fn detail(&self) -> Option<&str> {
187 self.inner.detail()
188 }
189
190 /// Get the detail line with backtrace. If backtrace is not available, it will just return the detail.
191 pub fn detail_with_backtrace(&self) -> Option<String> {
192 match (self.detail(), self.backtrace()) {
193 (Some(detail), Some(bt))
194 if bt.status() == std::backtrace::BacktraceStatus::Captured =>
195 {
196 Some(format!("{detail}\n{bt}"))
197 }
198 (Some(d), _) => Some(d.to_string()),
199 (None, Some(bt)) if bt.status() == std::backtrace::BacktraceStatus::Captured => {
200 Some(format!("\n{bt}"))
201 }
202 (None, _) => None,
203 }
204 }
205
206 /// Returns the hint line of this error report, if there is one
207 pub fn hint(&self) -> Option<&str> {
208 self.inner.hint()
209 }
210
211 /// Returns the domain of this error report, if set
212 pub fn domain(&self) -> Option<&str> {
213 self.inner.domain()
214 }
215
216 /// Returns the name of the source file that generated this error report
217 pub fn file(&self) -> &str {
218 &self.inner.location.file
219 }
220
221 /// Returns the line number of the source file that generated this error report
222 pub fn line_number(&self) -> u32 {
223 self.inner.location.line
224 }
225
226 /// Returns the backtrace when the error is reported
227 pub fn backtrace(&self) -> Option<&std::backtrace::Backtrace> {
228 self.inner.location.backtrace.as_ref()
229 }
230
231 /// Returns the name of the function that generated this error report, if we were able to figure it out
232 pub fn function_name(&self) -> Option<&str> {
233 self.inner.location.funcname.as_deref()
234 }
235
236 /// Returns the context message of this error report, if any
237 fn context_message(&self) -> Option<String> {
238 // NB: holding this here for future use
239 None
240 }
241}
242
243impl ErrorReport {
244 /// Create an [ErrorReport] which can be raised via Rust's [std::panic::panic_any()] or as
245 /// a specific Postgres "ereport()` level via [ErrorReport::unwrap_or_report(self, PgLogLevel)]
246 ///
247 /// Embedded "file:line:col" location information is taken from the caller's location
248 #[track_caller]
249 pub fn new<S: Into<Cow<'static, str>>>(
250 sqlerrcode: PgSqlErrorCode,
251 message: S,
252 funcname: &'static str,
253 ) -> Self {
254 let mut location: ErrorReportLocation = Location::caller().into();
255 location.funcname = Some(funcname.to_string());
256
257 Self {
258 sqlerrcode,
259 message: message.into(),
260 hint: None,
261 detail: None,
262 domain: None,
263 location,
264 }
265 }
266
267 /// Create an [ErrorReport] which can be raised via Rust's [std::panic::panic_any()] or as
268 /// a specific Postgres "ereport()` level via [ErrorReport::unwrap_or_report(self, PgLogLevel)].
269 ///
270 /// For internal use only
271 fn with_location<S: Into<Cow<'static, str>>>(
272 sqlerrcode: PgSqlErrorCode,
273 message: S,
274 location: ErrorReportLocation,
275 ) -> Self {
276 Self {
277 sqlerrcode,
278 message: message.into(),
279 hint: None,
280 detail: None,
281 domain: None,
282 location,
283 }
284 }
285
286 /// Set the `detail` property, whose default is `None`
287 pub fn set_detail<S: Into<String>>(mut self, detail: S) -> Self {
288 self.detail = Some(detail.into());
289 self
290 }
291
292 /// Set the `hint` property, whose default is `None`
293 pub fn set_hint<S: Into<String>>(mut self, hint: S) -> Self {
294 self.hint = Some(hint.into());
295 self
296 }
297
298 /// Set the `domain` property for message translation/internationalization, whose default is `None`.
299 ///
300 /// This corresponds to the `domain` argument in Postgres' `ereport_domain` C macro.
301 /// When set, the domain is passed to `errstart()` to indicate the gettext text domain
302 /// for message translation.
303 pub fn set_domain<S: Into<String>>(mut self, domain: S) -> Self {
304 self.domain = Some(domain.into());
305 self
306 }
307
308 /// Returns the error message of this error report
309 pub fn message(&self) -> &str {
310 &self.message
311 }
312
313 /// Returns the detail message of this error report
314 pub fn detail(&self) -> Option<&str> {
315 self.detail.as_deref()
316 }
317
318 /// Returns the hint message of this error report
319 pub fn hint(&self) -> Option<&str> {
320 self.hint.as_deref()
321 }
322
323 /// Returns the domain of this error report, if set
324 pub fn domain(&self) -> Option<&str> {
325 self.domain.as_deref()
326 }
327
328 /// Report this [ErrorReport], which will ultimately be reported by Postgres at the specified [PgLogLevel]
329 ///
330 /// If the provided `level` is >= [`PgLogLevel::ERROR`] this function will not return.
331 pub fn report(self, level: PgLogLevel) {
332 ErrorReportWithLevel { level, inner: self }.report()
333 }
334}
335
336thread_local! { static PANIC_LOCATION: Cell<Option<ErrorReportLocation>> = const { Cell::new(None) }}
337
338fn take_panic_location() -> ErrorReportLocation {
339 PANIC_LOCATION.with(|p| p.take().unwrap_or_default())
340}
341
342pub fn register_pg_guard_panic_hook() {
343 use super::thread_check::is_os_main_thread;
344
345 let default_hook = std::panic::take_hook();
346 std::panic::set_hook(Box::new(move |info: _| {
347 // Always capture the backtrace into PANIC_LOCATION so that
348 // downcast_panic_payload can attach it to CaughtError::PostgresError.
349 // This is a thread-local, so it's harmless on non-main threads.
350 PANIC_LOCATION.with(|thread_local| {
351 thread_local.replace({
352 let mut info: ErrorReportLocation = info.into();
353 info.backtrace = Some(std::backtrace::Backtrace::capture());
354 Some(info)
355 })
356 });
357
358 if is_os_main_thread() == Some(false) {
359 // definitely not the main thread -- we don't know which connection
360 // to associate the panic with, so also call the default hook
361 default_hook(info)
362 }
363 }))
364}
365
366/// What kind of error was caught?
367#[derive(Debug)]
368pub enum CaughtError {
369 /// An error raised from within Postgres
370 PostgresError(ErrorReportWithLevel),
371
372 /// A `pgrx::error!()` or `pgrx::ereport!(ERROR, ...)` raised from within Rust
373 ErrorReport(ErrorReportWithLevel),
374
375 /// A Rust `panic!()` or `std::panic::panic_any()`
376 RustPanic { ereport: ErrorReportWithLevel, payload: Box<dyn Any + Send> },
377}
378
379impl CaughtError {
380 /// Rethrow this [CaughtError].
381 ///
382 /// This is the same as [std::panic::resume_unwind()] and has the same semantics.
383 pub fn rethrow(self) -> ! {
384 // we resume_unwind here as [CaughtError] represents a previously caught panic, not a new
385 // one to be thrown
386 resume_unwind(Box::new(self))
387 }
388}
389
390#[derive(Debug)]
391enum GuardAction<R> {
392 Return(R),
393 ReThrow,
394 Report(ErrorReportWithLevel),
395}
396
397/// Guard a closure such that Rust Panics are properly converted into Postgres ERRORs.
398///
399/// Note that any Postgres ERRORs raised within the supplied closure are transparently converted
400/// to Rust panics.
401///
402/// Generally, this function won't need to be used directly, as it's also the implementation
403/// behind the `#[pg_guard]` and `#[pg_extern]` macros. Which means the function you'd like to guard
404/// is likely already guarded.
405///
406/// Where it does need to be used is as a wrapper around Rust `extern "C-unwind"` function pointers
407/// given to Postgres, and the `#[pg_guard]` macro takes care of this for you.
408///
409/// In other words, this isn't the function you're looking for.
410///
411/// You're probably looking for the `#[pg_guard]` macro.
412///
413/// Alternatively, if you're trying to mimic Postgres' C `PG_TRY/PG_CATCH` API, then you instead
414/// want [`crate::pg_try::PgTryBuilder`].
415///
416/// # Safety
417/// The function needs to only have [trivially-deallocated stack frames]
418/// above it. That is, the caller (and their caller, etc) cannot have
419/// objects with pending destructors in their stack frames, unless those
420/// objects have already been dropped.
421///
422/// In practice, this should only ever be called at the top level of an
423/// `extern "C-unwind" fn` implemented in Rust.
424///
425/// [trivially-deallocated stack frames](https://github.com/rust-lang/rfcs/blob/master/text/2945-c-unwind-abi.md#plain-old-frames)
426#[doc(hidden)]
427// FIXME: previously, R was bounded on Copy, but this prevents using move-only POD types
428// what we really want is a bound of R: !Drop, but negative bounds don't exist yet
429pub unsafe fn pgrx_extern_c_guard<Func, R>(f: Func) -> R
430where
431 Func: FnOnce() -> R,
432{
433 match unsafe { run_guarded(AssertUnwindSafe(f)) } {
434 GuardAction::Return(r) => r,
435 GuardAction::ReThrow => {
436 #[cfg_attr(target_os = "windows", link(name = "postgres"))]
437 unsafe extern "C-unwind" {
438 fn pg_re_throw() -> !;
439 }
440 unsafe {
441 crate::CurrentMemoryContext = crate::ErrorContext;
442 pg_re_throw()
443 }
444 }
445 GuardAction::Report(ereport) => {
446 do_ereport(ereport);
447 unreachable!("pgrx reported a CaughtError that wasn't raised at ERROR or above");
448 }
449 }
450}
451
452// SAFETY: similar constraints as pgrx_extern_c_guard
453#[inline(never)]
454unsafe fn run_guarded<F, R>(f: F) -> GuardAction<R>
455where
456 F: FnOnce() -> R + UnwindSafe,
457{
458 match catch_unwind(f) {
459 Ok(v) => GuardAction::Return(v),
460 Err(e) => match downcast_panic_payload(e) {
461 CaughtError::PostgresError(_) => {
462 // Return to the caller to rethrow the original Postgres ErrorData unchanged. We
463 // can't do it here because this function has non-POF frames.
464 GuardAction::ReThrow
465 }
466 CaughtError::ErrorReport(ereport) | CaughtError::RustPanic { ereport, .. } => {
467 GuardAction::Report(ereport)
468 }
469 },
470 }
471}
472
473/// convert types of `e` that we understand/expect into the representative [CaughtError]
474pub(crate) fn downcast_panic_payload(e: Box<dyn Any + Send>) -> CaughtError {
475 if e.downcast_ref::<CaughtError>().is_some() {
476 // caught a previously caught CaughtError that is being rethrown
477 let mut caught = *e.downcast::<CaughtError>().unwrap();
478
479 // For PostgresErrors (originating from a pg_sys FFI longjmp caught by
480 // pg_guard_ffi_boundary), the panic hook captured a Rust backtrace into
481 // PANIC_LOCATION. Attach it now so PgTryBuilder catch handlers can inspect it without
482 // changing the original Postgres error that will be rethrown.
483 if let CaughtError::PostgresError(ref mut ereport) = caught {
484 if ereport.inner.location.backtrace.is_none() {
485 let panic_location = take_panic_location();
486 ereport.inner.location.backtrace = panic_location.backtrace;
487 }
488 }
489
490 caught
491 } else if e.downcast_ref::<ErrorReportWithLevel>().is_some() {
492 // someone called `panic_any(ErrorReportWithLevel)`
493 CaughtError::ErrorReport(*e.downcast().unwrap())
494 } else if e.downcast_ref::<ErrorReport>().is_some() {
495 // someone called `panic_any(ErrorReport)` so we convert it to be PgLogLevel::ERROR
496 CaughtError::ErrorReport(ErrorReportWithLevel {
497 level: PgLogLevel::ERROR,
498 inner: *e.downcast().unwrap(),
499 })
500 } else if let Some(message) = e.downcast_ref::<&str>() {
501 // something panic'd with a &str, so it gets raised as an INTERNAL_ERROR at the ERROR level
502 CaughtError::RustPanic {
503 ereport: ErrorReportWithLevel {
504 level: PgLogLevel::ERROR,
505 inner: ErrorReport::with_location(
506 PgSqlErrorCode::ERRCODE_INTERNAL_ERROR,
507 message.to_string(),
508 take_panic_location(),
509 ),
510 },
511 payload: e,
512 }
513 } else if let Some(message) = e.downcast_ref::<String>() {
514 // something panic'd with a String, so it gets raised as an INTERNAL_ERROR at the ERROR level
515 CaughtError::RustPanic {
516 ereport: ErrorReportWithLevel {
517 level: PgLogLevel::ERROR,
518 inner: ErrorReport::with_location(
519 PgSqlErrorCode::ERRCODE_INTERNAL_ERROR,
520 message.clone(),
521 take_panic_location(),
522 ),
523 },
524 payload: e,
525 }
526 } else {
527 // not a type we understand, so it gets raised as an INTERNAL_ERROR at the ERROR level
528 CaughtError::RustPanic {
529 ereport: ErrorReportWithLevel {
530 level: PgLogLevel::ERROR,
531 inner: ErrorReport::with_location(
532 PgSqlErrorCode::ERRCODE_INTERNAL_ERROR,
533 "Box<Any>",
534 take_panic_location(),
535 ),
536 },
537 payload: e,
538 }
539 }
540}
541
542/// This is a (as faithful as possible) Rust unrolling of Postgres' `#define ereport(...)` macro.
543///
544/// Different implementations are provided for different postgres version ranges to ensure
545/// best performance. Care is taken to avoid work if `errstart` signals we can finish early.
546///
547/// We localize the definition of the various `err*()` functions involved in reporting a Postgres
548/// error (and purposely exclude them from `build.rs`) to ensure users can't get into trouble
549/// trying to roll their own error handling.
550fn do_ereport(ereport: ErrorReportWithLevel) {
551 const PERCENT_S: &CStr = c"%s";
552 const DEFAULT_DOMAIN: *const ::std::os::raw::c_char = std::ptr::null_mut();
553
554 // the following code is definitely thread-unsafe -- not-the-main-thread can't be creating Postgres
555 // ereports. Our secret `extern "C"` definitions aren't wrapped by #[pg_guard] so we need to
556 // manually do the active thread check
557 crate::thread_check::check_active_thread();
558
559 //
560 // only declare these functions here. They're explicitly excluded from bindings generation in
561 // `build.rs` and we'd prefer pgrx users not have access to them at all
562 //
563
564 #[cfg_attr(target_os = "windows", link(name = "postgres"))]
565 unsafe extern "C-unwind" {
566 fn errcode(sqlerrcode: ::std::os::raw::c_int) -> ::std::os::raw::c_int;
567 fn errmsg(fmt: *const ::std::os::raw::c_char, ...) -> ::std::os::raw::c_int;
568 fn errdetail(fmt: *const ::std::os::raw::c_char, ...) -> ::std::os::raw::c_int;
569 fn errhint(fmt: *const ::std::os::raw::c_char, ...) -> ::std::os::raw::c_int;
570 fn errcontext_msg(fmt: *const ::std::os::raw::c_char, ...) -> ::std::os::raw::c_int;
571 }
572
573 // we only allocate file, lineno and funcname if `errstart` returns true
574 #[cfg_attr(target_os = "windows", link(name = "postgres"))]
575 unsafe extern "C-unwind" {
576 fn errstart(elevel: ::std::os::raw::c_int, domain: *const ::std::os::raw::c_char) -> bool;
577 fn errfinish(
578 filename: *const ::std::os::raw::c_char,
579 lineno: ::std::os::raw::c_int,
580 funcname: *const ::std::os::raw::c_char,
581 );
582 }
583
584 let level = ereport.level();
585
586 // Use the domain from the ErrorReport if one was set, otherwise use the null default
587 let domain_cstring = ereport.inner.domain.as_ref().map(|d| {
588 std::ffi::CString::new(d.as_str()).expect("domain must not contain interior NUL bytes")
589 });
590 let domain_ptr = domain_cstring.as_ref().map_or(DEFAULT_DOMAIN, |c| c.as_ptr());
591
592 unsafe {
593 if errstart(level as _, domain_ptr) {
594 let sqlerrcode = ereport.sql_error_code();
595 let message = ereport.message().as_pg_cstr();
596 let detail = ereport.detail_with_backtrace().as_pg_cstr();
597 let hint = ereport.hint().as_pg_cstr();
598 let context = ereport.context_message().as_pg_cstr();
599 let lineno = ereport.line_number();
600
601 // SAFETY: We know that `crate::ErrorContext` is a valid memory context pointer and one
602 // that Postgres will clean up for us in the event of an ERROR, and we know it'll live long
603 // enough for Postgres to use `file` and `funcname`, which it expects to be `const char *`s
604
605 let prev_cxt = MemoryContextSwitchTo(crate::ErrorContext);
606 let file = ereport.file().as_pg_cstr();
607 let funcname = ereport.function_name().as_pg_cstr();
608 MemoryContextSwitchTo(prev_cxt);
609
610 // do not leak the Rust `ErrorReportWithLocation` instance
611 drop(ereport);
612
613 // SAFETY
614 //
615 // The following functions are all FFI into Postgres, so they're inherently unsafe.
616 //
617 // The various pointers used as arguments to these functions might have been allocated above
618 // or they might be the null pointer, so we guard against that possibility for each usage.
619 errcode(sqlerrcode as _);
620 if !message.is_null() {
621 errmsg(PERCENT_S.as_ptr(), message);
622 pfree(message.cast());
623 }
624 if !detail.is_null() {
625 errdetail(PERCENT_S.as_ptr(), detail);
626 pfree(detail.cast());
627 }
628 if !hint.is_null() {
629 errhint(PERCENT_S.as_ptr(), hint);
630 pfree(hint.cast());
631 }
632 if !context.is_null() {
633 errcontext_msg(PERCENT_S.as_ptr(), context);
634 pfree(context.cast());
635 }
636
637 if level >= PgLogLevel::ERROR {
638 crate::submodules::thread_check::active_thread::clear();
639 }
640 errfinish(file, lineno as _, funcname);
641
642 if level >= PgLogLevel::ERROR {
643 // SAFETY: `crate::errstart() is guaranteed to have returned true if >=ERROR and
644 // `crate::errfinish()` is guaranteed to not have not returned at all if >= ERROR, which
645 // means we won't either
646 unreachable_unchecked()
647 } else {
648 // if it wasn't an ERROR we need to free up the things that Postgres wouldn't have
649 //
650 // ... except on Postgres 19, whose `errfinish()` resets `ErrorContext` (where we
651 // allocated `file` and `funcname`) on its way out, making a `pfree()` here a
652 // use-after-free. In the nested-error case where pg19 doesn't reset the context,
653 // these allocations just live in `ErrorContext` until it's eventually reset
654 #[cfg(not(feature = "pg19"))]
655 {
656 if !file.is_null() {
657 pfree(file.cast());
658 }
659 if !funcname.is_null() {
660 pfree(funcname.cast());
661 }
662 }
663 }
664 }
665 }
666}