Skip to main content

libperl_rs/
cv.rs

1//! `Cv` newtype — a non-null handle to a Perl `CV` (code value).
2//!
3//! This is the first slice of "Step 2" in `docs/plan/README.md`
4//! (Cv/Op newtypes for OP-tree walkers). The accessors below cover
5//! what a static-analysis client needs to get from a coderef to its
6//! OP tree: `root` / `start` / `padlist` / `file` / `proto`, plus the
7//! `is_xsub` guard that must be checked before touching the
8//! `xcv_root_u` / `xcv_padlist_u` unions.
9//!
10//! All accessors delegate to the macrogen-emitted official API
11//! (`CvROOT`, `CvSTART`, `CvPADLIST`, `CvFILE`, `CvISXSUB`) — no
12//! hand-written struct pokes. `proto()` composes `SvPOK` +
13//! `SvPVX_const` + `SvCUR` because `CvPROTO` itself is on the
14//! macrogen skip list (see `libperl-sys/skip-codegen.txt`).
15//!
16//! Like [`Sv`](crate::Sv), a `Cv` does **not** own its referent:
17//! dropping it is a no-op. The `#[xs_sub]` proc-macro recognises a
18//! bare `Cv` parameter as "caller must pass a CODE reference" and
19//! generates the `SvROK` + `SVt_PVCV` check + croak in the
20//! trampoline (see `xs_sub.rs` `ArgKind::InCvRef`).
21
22use std::ptr::NonNull;
23
24use libperl_sys::{CV, OP, PADLIST, SV, svtype};
25
26/// Non-null pointer to a Perl `CV`. Same ABI as `*mut CV`.
27#[derive(Clone, Copy)]
28#[repr(transparent)]
29pub struct Cv(NonNull<CV>);
30
31impl Cv {
32    /// Wrap a raw `*mut CV` without checking for null.
33    ///
34    /// # Safety
35    /// Caller must guarantee `p` is non-null and points to a valid CV
36    /// for at least the lifetime of the resulting `Cv`.
37    #[inline]
38    pub unsafe fn from_raw_unchecked(p: *mut CV) -> Self {
39        debug_assert!(!p.is_null(), "Cv::from_raw_unchecked received a null pointer");
40        Cv(unsafe { NonNull::new_unchecked(p) })
41    }
42
43    /// Wrap a raw `*mut CV`, returning `None` on null input.
44    #[inline]
45    pub fn from_raw(p: *mut CV) -> Option<Self> {
46        NonNull::new(p).map(Cv)
47    }
48
49    /// Dereference a Perl-level coderef SV (`\&sub`, `sub {...}`)
50    /// into its CV. Returns `None` when `sv` is null, not a
51    /// reference, or references something other than a CODE value.
52    #[inline]
53    pub fn from_coderef(sv: *mut SV) -> Option<Cv> {
54        if sv.is_null() || unsafe { libperl_sys::SvROK(sv) } == 0 {
55            return None;
56        }
57        let target = unsafe { libperl_sys::SvRV(sv) };
58        if unsafe { libperl_sys::SvTYPE(target) } != svtype::SVt_PVCV {
59            return None;
60        }
61        Some(unsafe { Cv::from_raw_unchecked(target as *mut CV) })
62    }
63
64    /// Raw `*mut CV` for FFI calls.
65    #[inline]
66    pub fn as_ptr(&self) -> *mut CV {
67        self.0.as_ptr()
68    }
69
70    /// True when this CV is an XSUB (C-implemented). XSUBs have no
71    /// OP tree; `root` / `start` / `padlist` return null for them.
72    #[inline]
73    pub fn is_xsub(&self) -> bool {
74        unsafe { libperl_sys::CvISXSUB(self.as_ptr()) != 0 }
75    }
76
77    /// Root of the CV's OP tree (`CvROOT`), or null for XSUBs.
78    #[inline]
79    pub fn root(&self) -> *const OP {
80        if self.is_xsub() {
81            std::ptr::null()
82        } else {
83            unsafe { libperl_sys::CvROOT(self.as_ptr()) }
84        }
85    }
86
87    /// First OP in execution order (`CvSTART`), or null for XSUBs.
88    #[inline]
89    pub fn start(&self) -> *const OP {
90        if self.is_xsub() {
91            std::ptr::null()
92        } else {
93            unsafe { libperl_sys::CvSTART(self.as_ptr()) }
94        }
95    }
96
97    /// The CV's PADLIST (lexical scratchpad), or null for XSUBs.
98    #[inline]
99    pub fn padlist(&self) -> *const PADLIST {
100        if self.is_xsub() {
101            std::ptr::null()
102        } else {
103            unsafe { libperl_sys::CvPADLIST(self.as_ptr()) }
104        }
105    }
106
107    /// Source file the sub was compiled from (`CvFILE`);
108    /// `"(eval N)"` for string-eval'd subs.
109    pub fn file(&self) -> Option<String> {
110        let p = unsafe { libperl_sys::CvFILE(self.as_ptr()) };
111        if p.is_null() {
112            None
113        } else {
114            Some(
115                unsafe { std::ffi::CStr::from_ptr(p) }
116                    .to_string_lossy()
117                    .into_owned(),
118            )
119        }
120    }
121
122    /// The sub's prototype string (`CvPROTO`), if any. A CV stores
123    /// its prototype in its own PV slot, so this is `SvPOK` +
124    /// `SvPVX_const`/`SvCUR` on the CV itself.
125    pub fn proto(&self) -> Option<String> {
126        let sv = self.as_ptr() as *mut SV;
127        unsafe {
128            if libperl_sys::SvPOK(sv) == 0 {
129                return None;
130            }
131            let pv = libperl_sys::SvPVX_const(sv);
132            if pv.is_null() {
133                return None;
134            }
135            let len = libperl_sys::SvCUR(sv);
136            let bytes = std::slice::from_raw_parts(pv as *const u8, len as usize);
137            Some(String::from_utf8_lossy(bytes).into_owned())
138        }
139    }
140}