• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

davidcole1340 / ext-php-rs / 16323306954

16 Jul 2025 03:10PM UTC coverage: 22.222% (+0.6%) from 21.654%
16323306954

Pull #482

github

web-flow
Merge de76d2402 into 1166e2910
Pull Request #482: feat(cargo-php)!: escalate privilege and to copy extension and edit ini file

0 of 48 new or added lines in 1 file covered. (0.0%)

193 existing lines in 10 files now uncovered.

870 of 3915 relevant lines covered (22.22%)

3.63 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

81.61
/src/args.rs
1
//! Builder and objects relating to function and method arguments.
2

3
use std::{ffi::CString, ptr};
4

5
use crate::{
6
    convert::{FromZvalMut, IntoZvalDyn},
7
    describe::{abi, Parameter},
8
    error::{Error, Result},
9
    ffi::{
10
        _zend_expected_type, _zend_expected_type_Z_EXPECTED_ARRAY,
11
        _zend_expected_type_Z_EXPECTED_BOOL, _zend_expected_type_Z_EXPECTED_DOUBLE,
12
        _zend_expected_type_Z_EXPECTED_LONG, _zend_expected_type_Z_EXPECTED_OBJECT,
13
        _zend_expected_type_Z_EXPECTED_RESOURCE, _zend_expected_type_Z_EXPECTED_STRING,
14
        zend_internal_arg_info, zend_wrong_parameters_count_error,
15
    },
16
    flags::DataType,
17
    types::Zval,
18
    zend::ZendType,
19
};
20

21
/// Represents an argument to a function.
22
#[must_use]
23
#[derive(Debug)]
24
pub struct Arg<'a> {
25
    name: String,
26
    r#type: DataType,
27
    as_ref: bool,
28
    allow_null: bool,
29
    pub(crate) variadic: bool,
30
    default_value: Option<String>,
31
    zval: Option<&'a mut Zval>,
32
    variadic_zvals: Vec<Option<&'a mut Zval>>,
33
}
34

35
impl<'a> Arg<'a> {
36
    /// Creates a new argument.
37
    ///
38
    /// # Parameters
39
    ///
40
    /// * `name` - The name of the parameter.
41
    /// * `_type` - The type of the parameter.
42
    pub fn new<T: Into<String>>(name: T, r#type: DataType) -> Self {
41✔
43
        Arg {
44
            name: name.into(),
123✔
45
            r#type,
46
            as_ref: false,
47
            allow_null: false,
48
            variadic: false,
49
            default_value: None,
50
            zval: None,
51
            variadic_zvals: vec![],
41✔
52
        }
53
    }
54

55
    /// Sets the argument as a reference.
56
    #[allow(clippy::wrong_self_convention)]
57
    pub fn as_ref(mut self) -> Self {
1✔
58
        self.as_ref = true;
1✔
59
        self
1✔
60
    }
61

62
    /// Sets the argument as variadic.
63
    pub fn is_variadic(mut self) -> Self {
3✔
64
        self.variadic = true;
3✔
65
        self
3✔
66
    }
67

68
    /// Sets the argument as nullable.
69
    pub fn allow_null(mut self) -> Self {
10✔
70
        self.allow_null = true;
10✔
71
        self
10✔
72
    }
73

74
    /// Sets the default value for the argument.
75
    pub fn default<T: Into<String>>(mut self, default: T) -> Self {
3✔
76
        self.default_value = Some(default.into());
6✔
77
        self
3✔
78
    }
79

80
    /// Attempts to consume the argument, converting the inner type into `T`.
81
    /// Upon success, the result is returned in a [`Result`].
82
    ///
83
    /// As this function consumes, it cannot return a reference to the
84
    /// underlying zval.
85
    ///
86
    /// # Errors
87
    ///
88
    /// If the conversion fails (or the argument contains no value), the
89
    /// argument is returned in an [`Err`] variant.
90
    pub fn consume<T>(mut self) -> Result<T, Self>
2✔
91
    where
92
        for<'b> T: FromZvalMut<'b>,
93
    {
94
        self.zval
2✔
95
            .as_mut()
96
            .and_then(|zv| T::from_zval_mut(zv.dereference_mut()))
4✔
97
            .ok_or(self)
4✔
98
    }
99

100
    /// Attempts to retrieve the value of the argument.
101
    /// This will be None until the [`ArgParser`] is used to parse
102
    /// the arguments.
103
    pub fn val<T>(&'a mut self) -> Option<T>
4✔
104
    where
105
        T: FromZvalMut<'a>,
106
    {
107
        self.zval
4✔
108
            .as_mut()
109
            .and_then(|zv| T::from_zval_mut(zv.dereference_mut()))
10✔
110
    }
111

112
    /// Retrice all the variadic values for this Rust argument.
113
    pub fn variadic_vals<T>(&'a mut self) -> Vec<T>
1✔
114
    where
115
        T: FromZvalMut<'a>,
116
    {
117
        self.variadic_zvals
1✔
118
            .iter_mut()
119
            .filter_map(|zv| zv.as_mut())
5✔
120
            .filter_map(|zv| T::from_zval_mut(zv.dereference_mut()))
5✔
121
            .collect()
122
    }
123

124
    /// Attempts to return a reference to the arguments internal Zval.
125
    ///
126
    /// # Returns
127
    ///
128
    /// * `Some(&Zval)` - The internal zval.
129
    /// * `None` - The argument was empty.
130
    // TODO: Figure out if we can change this
131
    #[allow(clippy::mut_mut)]
132
    pub fn zval(&mut self) -> Option<&mut &'a mut Zval> {
2✔
133
        self.zval.as_mut()
4✔
134
    }
135

136
    /// Attempts to call the argument as a callable with a list of arguments to
137
    /// pass to the function. Note that a thrown exception inside the
138
    /// callable is not detectable, therefore you should check if the return
139
    /// value is valid rather than unwrapping. Returns a result containing the
140
    /// return value of the function, or an error.
141
    ///
142
    /// You should not call this function directly, rather through the
143
    /// [`call_user_func`](crate::call_user_func) macro.
144
    ///
145
    /// # Parameters
146
    ///
147
    /// * `params` - A list of parameters to call the function with.
148
    ///
149
    /// # Errors
150
    ///
151
    /// * `Error::Callable` - The argument is not callable.
152
    // TODO: Measure this
153
    #[allow(clippy::inline_always)]
154
    #[inline(always)]
155
    pub fn try_call(&self, params: Vec<&dyn IntoZvalDyn>) -> Result<Zval> {
2✔
156
        self.zval.as_ref().ok_or(Error::Callable)?.try_call(params)
10✔
157
    }
158

159
    /// Returns the internal PHP argument info.
160
    pub(crate) fn as_arg_info(&self) -> Result<ArgInfo> {
5✔
161
        Ok(ArgInfo {
×
162
            name: CString::new(self.name.as_str())?.into_raw(),
20✔
163
            type_: ZendType::empty_from_type(
×
164
                self.r#type,
×
165
                self.as_ref,
×
166
                self.variadic,
×
167
                self.allow_null,
×
168
            )
169
            .ok_or(Error::InvalidCString)?,
×
170
            default_value: match &self.default_value {
1✔
171
                Some(val) if val.as_str() == "None" => CString::new("null")?.into_raw(),
2✔
172
                Some(val) => CString::new(val.as_str())?.into_raw(),
2✔
173
                None => ptr::null(),
4✔
174
            },
175
        })
176
    }
177
}
178

179
impl From<Arg<'_>> for _zend_expected_type {
180
    fn from(arg: Arg) -> Self {
16✔
181
        let type_id = match arg.r#type {
32✔
182
            DataType::False | DataType::True => _zend_expected_type_Z_EXPECTED_BOOL,
4✔
183
            DataType::Long => _zend_expected_type_Z_EXPECTED_LONG,
2✔
184
            DataType::Double => _zend_expected_type_Z_EXPECTED_DOUBLE,
2✔
185
            DataType::String => _zend_expected_type_Z_EXPECTED_STRING,
2✔
186
            DataType::Array => _zend_expected_type_Z_EXPECTED_ARRAY,
2✔
187
            DataType::Object(_) => _zend_expected_type_Z_EXPECTED_OBJECT,
2✔
188
            DataType::Resource => _zend_expected_type_Z_EXPECTED_RESOURCE,
2✔
189
            _ => unreachable!(),
190
        };
191

192
        if arg.allow_null {
193
            type_id + 1
8✔
194
        } else {
195
            type_id
8✔
196
        }
197
    }
198
}
199

200
impl From<Arg<'_>> for Parameter {
201
    fn from(val: Arg<'_>) -> Self {
3✔
202
        Parameter {
203
            name: val.name.into(),
9✔
204
            ty: Some(val.r#type).into(),
9✔
205
            nullable: val.allow_null,
6✔
206
            default: val.default_value.map(abi::RString::from).into(),
6✔
207
        }
208
    }
209
}
210

211
/// Internal argument information used by Zend.
212
pub type ArgInfo = zend_internal_arg_info;
213

214
/// Parses the arguments of a function.
215
#[must_use]
216
pub struct ArgParser<'a, 'b> {
217
    args: Vec<&'b mut Arg<'a>>,
218
    min_num_args: Option<usize>,
219
    arg_zvals: Vec<Option<&'a mut Zval>>,
220
}
221

222
impl<'a, 'b> ArgParser<'a, 'b> {
223
    /// Builds a new function argument parser.
224
    pub fn new(arg_zvals: Vec<Option<&'a mut Zval>>) -> Self {
4✔
225
        ArgParser {
226
            args: vec![],
8✔
227
            min_num_args: None,
228
            arg_zvals,
229
        }
230
    }
231

232
    /// Adds a new argument to the parser.
233
    ///
234
    /// # Parameters
235
    ///
236
    /// * `arg` - The argument to add to the parser.
237
    pub fn arg(mut self, arg: &'b mut Arg<'a>) -> Self {
3✔
238
        self.args.push(arg);
9✔
239
        self
3✔
240
    }
241

242
    /// Sets the next arguments to be added as not required.
243
    pub fn not_required(mut self) -> Self {
2✔
244
        self.min_num_args = Some(self.args.len());
2✔
245
        self
2✔
246
    }
247

248
    /// Uses the argument parser to parse the arguments contained in the given
249
    /// `ExecuteData` object. Returns successfully if the arguments were
250
    /// parsed.
251
    ///
252
    /// This function can only be safely called from within an exported PHP
253
    /// function.
254
    ///
255
    /// # Parameters
256
    ///
257
    /// * `execute_data` - The execution data from the function.
258
    ///
259
    /// # Errors
260
    ///
261
    /// Returns an [`Error`] type if there were too many or too little arguments
262
    /// passed to the function. The user has already been notified so you
263
    /// should break execution after seeing an error type.
264
    ///
265
    /// Also returns an error if the number of min/max arguments exceeds
266
    /// `u32::MAX`
267
    pub fn parse(mut self) -> Result<()> {
2✔
268
        let max_num_args = self.args.len();
6✔
269
        let mut min_num_args = self.min_num_args.unwrap_or(max_num_args);
8✔
270
        let num_args = self.arg_zvals.len();
6✔
271
        let has_variadic = self.args.last().is_some_and(|arg| arg.variadic);
6✔
272
        if has_variadic {
2✔
UNCOV
273
            min_num_args = min_num_args.saturating_sub(1);
×
274
        }
275

276
        if num_args < min_num_args || (!has_variadic && num_args > max_num_args) {
6✔
277
            // SAFETY: Exported C function is safe, return value is unused and parameters
278
            // are copied.
279
            unsafe {
280
                zend_wrong_parameters_count_error(
281
                    min_num_args.try_into()?,
×
UNCOV
282
                    max_num_args.try_into()?,
×
283
                );
284
            };
UNCOV
285
            return Err(Error::IncorrectArguments(num_args, min_num_args));
×
286
        }
287

288
        for (i, arg_zval) in self.arg_zvals.into_iter().enumerate() {
2✔
UNCOV
289
            let arg = match self.args.get_mut(i) {
×
290
                Some(arg) => Some(arg),
4✔
291
                // Only select the last item if it's variadic
UNCOV
292
                None => self.args.last_mut().filter(|arg| arg.variadic),
×
293
            };
294
            if let Some(arg) = arg {
2✔
295
                if arg.variadic {
×
UNCOV
296
                    arg.variadic_zvals.push(arg_zval);
×
297
                } else {
298
                    arg.zval = arg_zval;
2✔
299
                }
300
            }
301
        }
302

UNCOV
303
        Ok(())
×
304
    }
305
}
306

307
#[cfg(test)]
308
mod tests {
309
    #![allow(clippy::unwrap_used)]
310
    #[cfg(feature = "embed")]
311
    use crate::embed::Embed;
312

313
    use super::*;
314

315
    #[test]
316
    fn test_new() {
317
        let arg = Arg::new("test", DataType::Long);
318
        assert_eq!(arg.name, "test");
319
        assert_eq!(arg.r#type, DataType::Long);
320
        assert!(!arg.as_ref);
321
        assert!(!arg.allow_null);
322
        assert!(!arg.variadic);
323
        assert!(arg.default_value.is_none());
324
        assert!(arg.zval.is_none());
325
        assert!(arg.variadic_zvals.is_empty());
326
    }
327

328
    #[test]
329
    fn test_as_ref() {
330
        let arg = Arg::new("test", DataType::Long).as_ref();
331
        assert!(arg.as_ref);
332
    }
333

334
    #[test]
335
    fn test_is_variadic() {
336
        let arg = Arg::new("test", DataType::Long).is_variadic();
337
        assert!(arg.variadic);
338
    }
339

340
    #[test]
341
    fn test_allow_null() {
342
        let arg = Arg::new("test", DataType::Long).allow_null();
343
        assert!(arg.allow_null);
344
    }
345

346
    #[test]
347
    fn test_default() {
348
        let arg = Arg::new("test", DataType::Long).default("default");
349
        assert_eq!(arg.default_value, Some("default".to_string()));
350

351
        // TODO: Validate type
352
    }
353

354
    #[test]
355
    fn test_consume_no_value() {
356
        let arg = Arg::new("test", DataType::Long);
357
        let result: Result<i32, _> = arg.consume();
358
        assert!(result.is_err());
359
        assert_eq!(result.unwrap_err().name, "test");
360
    }
361

362
    #[test]
363
    #[cfg(feature = "embed")]
364
    fn test_consume() {
365
        let mut arg = Arg::new("test", DataType::Long);
366
        let mut zval = Zval::from(42);
367
        arg.zval = Some(&mut zval);
368

369
        let result: Result<i32, _> = arg.consume();
370
        assert_eq!(result.unwrap(), 42);
371
    }
372

373
    #[test]
374
    fn test_val_no_value() {
375
        let mut arg = Arg::new("test", DataType::Long);
376
        let result: Option<i32> = arg.val();
377
        assert!(result.is_none());
378
    }
379

380
    #[test]
381
    #[cfg(feature = "embed")]
382
    fn test_val() {
383
        let mut arg = Arg::new("test", DataType::Long);
384
        let mut zval = Zval::from(42);
385
        arg.zval = Some(&mut zval);
386

387
        let result: Option<i32> = arg.val();
388
        assert_eq!(result.unwrap(), 42);
389
    }
390

391
    #[test]
392
    #[cfg(feature = "embed")]
393
    fn test_variadic_vals() {
394
        let mut arg = Arg::new("test", DataType::Long).is_variadic();
395
        let mut zval1 = Zval::from(42);
396
        let mut zval2 = Zval::from(43);
397
        arg.variadic_zvals.push(Some(&mut zval1));
398
        arg.variadic_zvals.push(Some(&mut zval2));
399

400
        let result: Vec<i32> = arg.variadic_vals();
401
        assert_eq!(result, vec![42, 43]);
402
    }
403

404
    #[test]
405
    fn test_zval_no_value() {
406
        let mut arg = Arg::new("test", DataType::Long);
407
        let result = arg.zval();
408
        assert!(result.is_none());
409
    }
410

411
    #[test]
412
    #[cfg(feature = "embed")]
413
    fn test_zval() {
414
        let mut arg = Arg::new("test", DataType::Long);
415
        let mut zval = Zval::from(42);
416
        arg.zval = Some(&mut zval);
417

418
        let result = arg.zval();
419
        assert!(result.is_some());
420
        assert_eq!(result.unwrap().dereference_mut().long(), Some(42));
421
    }
422

423
    #[cfg(feature = "embed")]
424
    #[test]
425
    fn test_try_call_no_value() {
426
        let arg = Arg::new("test", DataType::Long);
427
        let result = arg.try_call(vec![]);
428
        assert!(result.is_err());
429
    }
430

431
    #[test]
432
    #[cfg(feature = "embed")]
433
    fn test_try_call_not_callable() {
434
        Embed::run(|| {
435
            let mut arg = Arg::new("test", DataType::Long);
436
            let mut zval = Zval::from(42);
437
            arg.zval = Some(&mut zval);
438

439
            let result = arg.try_call(vec![]);
440
            assert!(result.is_err());
441
        });
442
    }
443

444
    // TODO: Test the callable case
445

446
    #[test]
447
    #[cfg(feature = "embed")]
448
    fn test_as_arg_info() {
449
        let arg = Arg::new("test", DataType::Long);
450
        let arg_info = arg.as_arg_info();
451
        assert!(arg_info.is_ok());
452

453
        let arg_info = arg_info.unwrap();
454
        assert!(arg_info.default_value.is_null());
455

456
        let r#type = arg_info.type_;
457
        assert_eq!(r#type.type_mask, 16);
458
    }
459

460
    #[test]
461
    #[cfg(feature = "embed")]
462
    fn test_as_arg_info_with_default() {
463
        let arg = Arg::new("test", DataType::Long).default("default");
464
        let arg_info = arg.as_arg_info();
465
        assert!(arg_info.is_ok());
466

467
        let arg_info = arg_info.unwrap();
468
        assert!(!arg_info.default_value.is_null());
469

470
        let r#type = arg_info.type_;
471
        assert_eq!(r#type.type_mask, 16);
472
    }
473

474
    #[test]
475
    fn test_type_from_arg() {
476
        let arg = Arg::new("test", DataType::Long);
477
        let actual: _zend_expected_type = arg.into();
478
        assert_eq!(actual, 0);
479

480
        let arg = Arg::new("test", DataType::Long).allow_null();
481
        let actual: _zend_expected_type = arg.into();
482
        assert_eq!(actual, 1);
483

484
        let arg = Arg::new("test", DataType::False);
485
        let actual: _zend_expected_type = arg.into();
486
        assert_eq!(actual, 2);
487

488
        let arg = Arg::new("test", DataType::False).allow_null();
489
        let actual: _zend_expected_type = arg.into();
490
        assert_eq!(actual, 3);
491

492
        let arg = Arg::new("test", DataType::True);
493
        let actual: _zend_expected_type = arg.into();
494
        assert_eq!(actual, 2);
495

496
        let arg = Arg::new("test", DataType::True).allow_null();
497
        let actual: _zend_expected_type = arg.into();
498
        assert_eq!(actual, 3);
499

500
        let arg = Arg::new("test", DataType::String);
501
        let actual: _zend_expected_type = arg.into();
502
        assert_eq!(actual, 4);
503

504
        let arg = Arg::new("test", DataType::String).allow_null();
505
        let actual: _zend_expected_type = arg.into();
506
        assert_eq!(actual, 5);
507

508
        let arg = Arg::new("test", DataType::Array);
509
        let actual: _zend_expected_type = arg.into();
510
        assert_eq!(actual, 6);
511

512
        let arg = Arg::new("test", DataType::Array).allow_null();
513
        let actual: _zend_expected_type = arg.into();
514
        assert_eq!(actual, 7);
515

516
        let arg = Arg::new("test", DataType::Resource);
517
        let actual: _zend_expected_type = arg.into();
518
        assert_eq!(actual, 14);
519

520
        let arg = Arg::new("test", DataType::Resource).allow_null();
521
        let actual: _zend_expected_type = arg.into();
522
        assert_eq!(actual, 15);
523

524
        let arg = Arg::new("test", DataType::Object(None));
525
        let actual: _zend_expected_type = arg.into();
526
        assert_eq!(actual, 18);
527

528
        let arg = Arg::new("test", DataType::Object(None)).allow_null();
529
        let actual: _zend_expected_type = arg.into();
530
        assert_eq!(actual, 19);
531

532
        let arg = Arg::new("test", DataType::Double);
533
        let actual: _zend_expected_type = arg.into();
534
        assert_eq!(actual, 20);
535

536
        let arg = Arg::new("test", DataType::Double).allow_null();
537
        let actual: _zend_expected_type = arg.into();
538
        assert_eq!(actual, 21);
539
    }
540

541
    #[test]
542
    fn test_param_from_arg() {
543
        let arg = Arg::new("test", DataType::Long)
544
            .default("default")
545
            .allow_null();
546
        let param: Parameter = arg.into();
547
        assert_eq!(param.name, "test".into());
548
        assert_eq!(param.ty, abi::Option::Some(DataType::Long));
549
        assert!(param.nullable);
550
        assert_eq!(param.default, abi::Option::Some("default".into()));
551
    }
552

553
    #[test]
554
    fn test_arg_parser_new() {
555
        let arg_zvals = vec![None, None];
556
        let parser = ArgParser::new(arg_zvals);
557
        assert_eq!(parser.arg_zvals.len(), 2);
558
        assert!(parser.args.is_empty());
559
        assert!(parser.min_num_args.is_none());
560
    }
561

562
    #[test]
563
    fn test_arg_parser_arg() {
564
        let arg_zvals = vec![None, None];
565
        let mut parser = ArgParser::new(arg_zvals);
566
        let mut arg = Arg::new("test", DataType::Long);
567
        parser = parser.arg(&mut arg);
568
        assert_eq!(parser.args.len(), 1);
569
        assert_eq!(parser.args[0].name, "test");
570
        assert_eq!(parser.args[0].r#type, DataType::Long);
571
    }
572

573
    // TODO: test parse
574
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc