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

davidcole1340 / ext-php-rs / 15920812749

27 Jun 2025 07:32AM UTC coverage: 20.629% (-1.3%) from 21.978%
15920812749

push

github

web-flow
Merge pull request #472 from Qard/enable-zts-on-nix

761 of 3689 relevant lines covered (20.63%)

3.57 hits per line

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

81.4
/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 {
5✔
171
                Some(val) => CString::new(val.as_str())?.into_raw(),
5✔
172
                None => ptr::null(),
4✔
173
            },
174
        })
175
    }
176
}
177

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

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

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

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

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

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

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

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

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

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

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

302
        Ok(())
×
303
    }
304
}
305

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

312
    use super::*;
313

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

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

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

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

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

350
        // TODO: Validate type
351
    }
352

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

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

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

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

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

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

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

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

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

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

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

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

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

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

442
    // TODO: Test the callable case
443

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

451
        let arg_info = arg_info.unwrap();
452
        assert!(arg_info.default_value.is_null());
453

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

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

465
        let arg_info = arg_info.unwrap();
466
        assert!(!arg_info.default_value.is_null());
467

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

571
    // TODO: test parse
572
}
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