Skip to content

Commit 61c2681

Browse files
committed
Add support for * precision in printf()
1 parent 40ceafc commit 61c2681

File tree

2 files changed

+121
-19
lines changed

2 files changed

+121
-19
lines changed

ext/standard/formatted_print.c

Lines changed: 67 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,27 @@ php_sprintf_getnumber(char **buffer, size_t *len)
364364
}
365365
/* }}} */
366366

367+
#define ARG_NUM_NEXT -1
368+
#define ARG_NUM_INVALID -2
369+
370+
int php_sprintf_get_argnum(char **format, size_t *format_len) {
371+
char *temppos = *format;
372+
while (isdigit((int) *temppos)) temppos++;
373+
if (*temppos != '$') {
374+
return ARG_NUM_NEXT;
375+
}
376+
377+
int argnum = php_sprintf_getnumber(format, format_len);
378+
if (argnum <= 0) {
379+
zend_value_error("Argument number must be greater than zero");
380+
return ARG_NUM_INVALID;
381+
}
382+
383+
(*format)++; /* skip the '$' */
384+
(*format_len)--;
385+
return argnum - 1;
386+
}
387+
367388
/* php_formatted_print() {{{
368389
* New sprintf implementation for PHP.
369390
*
@@ -438,24 +459,12 @@ php_formatted_print(char *format, size_t format_len, zval *args, int argc, int n
438459
*format, format - Z_STRVAL_P(z_format)));
439460
if (isalpha((int)*format)) {
440461
width = precision = 0;
441-
argnum = currarg++;
462+
argnum = ARG_NUM_NEXT;
442463
} else {
443464
/* first look for argnum */
444-
temppos = format;
445-
while (isdigit((int)*temppos)) temppos++;
446-
if (*temppos == '$') {
447-
argnum = php_sprintf_getnumber(&format, &format_len);
448-
449-
if (argnum <= 0) {
450-
zend_string_efree(result);
451-
zend_value_error("Argument number must be greater than zero");
452-
return NULL;
453-
}
454-
argnum--;
455-
format++; /* skip the '$' */
456-
format_len--;
457-
} else {
458-
argnum = currarg++;
465+
argnum = php_sprintf_get_argnum(&format, &format_len);
466+
if (argnum == ARG_NUM_INVALID) {
467+
goto fail;
459468
}
460469

461470
/* after argnum comes modifiers */
@@ -503,11 +512,38 @@ php_formatted_print(char *format, size_t format_len, zval *args, int argc, int n
503512
format++;
504513
format_len--;
505514
PRINTF_DEBUG(("sprintf: getting precision\n"));
506-
if (isdigit((int)*format)) {
515+
if (*format == '*') {
516+
format++;
517+
format_len--;
518+
519+
int prec_argnum = php_sprintf_get_argnum(&format, &format_len);
520+
if (prec_argnum == ARG_NUM_INVALID) {
521+
goto fail;
522+
}
523+
if (prec_argnum == ARG_NUM_NEXT) {
524+
prec_argnum = currarg++;
525+
}
526+
if (prec_argnum >= argc) {
527+
max_missing_argnum = MAX(max_missing_argnum, prec_argnum);
528+
continue;
529+
}
530+
tmp = &args[prec_argnum];
531+
ZVAL_DEREF(tmp);
532+
if (Z_TYPE_P(tmp) != IS_LONG) {
533+
zend_value_error("Precision must be an integer");
534+
goto fail;
535+
}
536+
if (Z_LVAL_P(tmp) < -1 || Z_LVAL_P(tmp) > INT_MAX) {
537+
zend_value_error("Precision must be between -1 and %d", INT_MAX);
538+
goto fail;
539+
}
540+
precision = Z_LVAL_P(tmp);
541+
adjusting |= ADJ_PRECISION;
542+
expprec = 1;
543+
} else if (isdigit((int)*format)) {
507544
if ((precision = php_sprintf_getnumber(&format, &format_len)) < 0) {
508-
efree(result);
509545
zend_value_error("Precision must be greater than zero and less than %d", INT_MAX);
510-
return NULL;
546+
goto fail;
511547
}
512548
adjusting |= ADJ_PRECISION;
513549
expprec = 1;
@@ -526,11 +562,19 @@ php_formatted_print(char *format, size_t format_len, zval *args, int argc, int n
526562
}
527563
PRINTF_DEBUG(("sprintf: format character='%c'\n", *format));
528564

565+
if (argnum == ARG_NUM_NEXT) {
566+
argnum = currarg++;
567+
}
529568
if (argnum >= argc) {
530569
max_missing_argnum = MAX(max_missing_argnum, argnum);
531570
continue;
532571
}
533572

573+
if (expprec && precision == -1 && *format != 'g' && *format != 'G') {
574+
zend_value_error("Precision -1 is only supported for %%g and %%G");
575+
goto fail;
576+
}
577+
534578
/* now we expect to find a type specifier */
535579
tmp = &args[argnum];
536580
switch (*format) {
@@ -641,6 +685,10 @@ php_formatted_print(char *format, size_t format_len, zval *args, int argc, int n
641685
ZSTR_VAL(result)[outpos]=0;
642686
ZSTR_LEN(result) = outpos;
643687
return result;
688+
689+
fail:
690+
zend_string_efree(result);
691+
return NULL;
644692
}
645693
/* }}} */
646694

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
--TEST--
2+
Star width and precision in sprintf()
3+
--FILE--
4+
<?php
5+
6+
7+
$f = 1.23456789012345678;
8+
var_dump($f);
9+
printf("%.*f\n", 10, $f);
10+
printf("%.*G\n", 10, $f);
11+
printf("%.*G\n", -1, $f);
12+
printf("%.*s\n", 3, "foobar");
13+
14+
echo "\n";
15+
16+
printf("%1$.*2\$f\n", $f, 10);
17+
printf("%.*2\$f\n", $f, 10);
18+
printf("%2$.*f\n", 10, $f);
19+
20+
echo "\n";
21+
22+
try {
23+
printf("%.*G\n", "foo", 1.5);
24+
} catch (ValueError $e) {
25+
echo $e->getMessage(), "\n";
26+
}
27+
28+
try {
29+
printf("%.*G\n", -100, 1.5);
30+
} catch (ValueError $e) {
31+
echo $e->getMessage(), "\n";
32+
}
33+
34+
try {
35+
printf("%.*s\n", -1, "Foo");
36+
} catch (ValueError $e) {
37+
echo $e->getMessage(), "\n";
38+
}
39+
40+
?>
41+
--EXPECTF--
42+
float(1.2345678901234567)
43+
1.2345678901
44+
1.23456789
45+
1.2345678901234567
46+
foo
47+
48+
1.2345678901
49+
1.2345678901
50+
1.2345678901
51+
52+
Precision must be an integer
53+
Precision must be between -1 and 2147483647
54+
Precision -1 is only supported for %g and %G

0 commit comments

Comments
 (0)