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, OPclass, PADLIST, SV, svtype};
25
26use crate::{Cop, Gv, Op, PadNames, Perl};
27
28/// Non-null pointer to a Perl `CV`. Same ABI as `*mut CV`.
29#[derive(Clone, Copy)]
30#[repr(transparent)]
31pub struct Cv(NonNull<CV>);
32
33impl Cv {
34    /// Wrap a raw `*mut CV` without checking for null.
35    ///
36    /// # Safety
37    /// Caller must guarantee `p` is non-null and points to a valid CV
38    /// for at least the lifetime of the resulting `Cv`.
39    #[inline]
40    pub unsafe fn from_raw_unchecked(p: *mut CV) -> Self {
41        debug_assert!(!p.is_null(), "Cv::from_raw_unchecked received a null pointer");
42        Cv(unsafe { NonNull::new_unchecked(p) })
43    }
44
45    /// Wrap a raw `*mut CV`, returning `None` on null input.
46    #[inline]
47    pub fn from_raw(p: *mut CV) -> Option<Self> {
48        NonNull::new(p).map(Cv)
49    }
50
51    /// Dereference a Perl-level coderef SV (`\&sub`, `sub {...}`)
52    /// into its CV. Returns `None` when `sv` is null, not a
53    /// reference, or references something other than a CODE value.
54    #[inline]
55    pub fn from_coderef(sv: *mut SV) -> Option<Cv> {
56        if sv.is_null() || unsafe { libperl_sys::SvROK(sv) } == 0 {
57            return None;
58        }
59        let target = unsafe { libperl_sys::SvRV(sv) };
60        if unsafe { libperl_sys::SvTYPE(target) } != svtype::SVt_PVCV {
61            return None;
62        }
63        Some(unsafe { Cv::from_raw_unchecked(target as *mut CV) })
64    }
65
66    /// Raw `*mut CV` for FFI calls.
67    #[inline]
68    pub fn as_ptr(&self) -> *mut CV {
69        self.0.as_ptr()
70    }
71
72    /// True when this CV is an XSUB (C-implemented). XSUBs have no
73    /// OP tree; `root` / `start` / `padlist` return null for them.
74    #[inline]
75    pub fn is_xsub(&self) -> bool {
76        unsafe { libperl_sys::CvISXSUB(self.as_ptr() as *const _) != 0 }
77    }
78
79    /// Root of the CV's OP tree (`CvROOT`), or null for XSUBs.
80    #[inline]
81    pub fn root(&self) -> *const OP {
82        if self.is_xsub() {
83            std::ptr::null()
84        } else {
85            unsafe { libperl_sys::CvROOT(self.as_ptr() as *const _) }
86        }
87    }
88
89    /// First OP in execution order (`CvSTART`), or null for XSUBs.
90    #[inline]
91    pub fn start(&self) -> *const OP {
92        if self.is_xsub() {
93            std::ptr::null()
94        } else {
95            unsafe { libperl_sys::CvSTART(self.as_ptr() as *const _) }
96        }
97    }
98
99    /// The CV's PADLIST (lexical scratchpad), or null for XSUBs.
100    #[inline]
101    pub fn padlist(&self) -> *const PADLIST {
102        if self.is_xsub() {
103            std::ptr::null()
104        } else {
105            unsafe { libperl_sys::CvPADLIST(self.as_ptr() as *const _) }
106        }
107    }
108
109    /// Source file the sub was compiled from (`CvFILE`);
110    /// `"(eval N)"` for string-eval'd subs.
111    pub fn file(&self) -> Option<String> {
112        let p = unsafe { libperl_sys::CvFILE(self.as_ptr() as *const _) };
113        if p.is_null() {
114            None
115        } else {
116            Some(
117                unsafe { std::ffi::CStr::from_ptr(p) }
118                    .to_string_lossy()
119                    .into_owned(),
120            )
121        }
122    }
123
124    /// [`Cv::root`] as an [`Op`] handle (`None` for XSUBs).
125    #[inline]
126    pub fn root_op(&self) -> Option<Op> {
127        Op::from_raw(self.root())
128    }
129
130    /// [`Cv::start`] as an [`Op`] handle (`None` for XSUBs).
131    #[inline]
132    pub fn start_op(&self) -> Option<Op> {
133        Op::from_raw(self.start())
134    }
135
136    /// The GV the sub was defined through (`CvGV`), if any. Gives
137    /// access to the sub's package-qualified name and the glob's
138    /// file / line.
139    #[inline]
140    pub fn gv(&self, perl: &Perl) -> Option<Gv> {
141        // Inferred cast: the generated `CvGV` takes `*const SV` on
142        // most perls but `*const CV` on exactly 5.32 (apidoc type
143        // normalisation difference); `as *const _` fits both.
144        let gv = unsafe { crate::thx_call!(perl, CvGV, self.as_ptr() as *const _) };
145        Gv::from_raw(gv)
146    }
147
148    /// The sub's `(qualified, unqualified)` name pair
149    /// (`("Foo::bar", "bar")`), resolved via [`Cv::gv`]. `None` for
150    /// nameless subs.
151    pub fn names(&self, perl: &Perl) -> Option<(String, String)> {
152        let gv = self.gv(perl)?;
153        let name = gv.name()?;
154        let full = match gv.stash_name() {
155            Some(pkg) => format!("{pkg}::{name}"),
156            None => name.clone(),
157        };
158        Some((full, name))
159    }
160
161    /// The first COP (`nextstate`) in the sub's OP tree, in tree
162    /// order — i.e. the sub's first statement, whose
163    /// [`line`](Cop::line) / [`file`](Cop::file) locate the sub body
164    /// in its source. `None` for XSUBs and bodiless subs.
165    ///
166    /// Walks tree order (preorder), not the `op_next` chain, so loop
167    /// back-edges cannot cycle the search.
168    pub fn first_cop(&self, perl: &Perl) -> Option<Cop> {
169        let mut stack: Vec<Op> = self.root_op().into_iter().collect();
170        while let Some(op) = stack.pop() {
171            if op.class(perl) == OPclass::OPclass_COP {
172                return op.as_cop(perl);
173            }
174            // Push the sibling below the first kid so the kid is
175            // taken first (preorder).
176            if let Some(sib) = op.sibling() {
177                stack.push(sib);
178            }
179            if let Some(kid) = op.first() {
180                stack.push(kid);
181            }
182        }
183        None
184    }
185
186    /// Iterate the sub's lexical-name slots (pad names), in pad-offset
187    /// order starting at offset 0. Empty for XSUBs. See
188    /// [`PadNames`](crate::PadNames).
189    #[inline]
190    pub fn pad_names(&self) -> PadNames {
191        PadNames::from_padlist(self.padlist())
192    }
193
194    /// The sub's prototype string (`CvPROTO`), if any. A CV stores
195    /// its prototype in its own PV slot, so this is `SvPOK` +
196    /// `SvPVX_const`/`SvCUR` on the CV itself.
197    pub fn proto(&self) -> Option<String> {
198        let sv = self.as_ptr() as *mut SV;
199        unsafe {
200            if libperl_sys::SvPOK(sv) == 0 {
201                return None;
202            }
203            let pv = libperl_sys::SvPVX_const(sv);
204            if pv.is_null() {
205                return None;
206            }
207            let len = libperl_sys::SvCUR(sv);
208            let bytes = std::slice::from_raw_parts(pv as *const u8, len as usize);
209            Some(String::from_utf8_lossy(bytes).into_owned())
210        }
211    }
212}