Skip to content

Commit f704b8f

Browse files
committed
Add support for * width and precision in printf()
1 parent 427cc4f commit f704b8f

File tree

2 files changed

+180
-17
lines changed

2 files changed

+180
-17
lines changed

ext/standard/formatted_print.c

Lines changed: 90 additions & 17 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,23 +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_value_error("Argument number must be greater than zero");
451-
goto fail;
452-
}
453-
argnum--;
454-
format++; /* skip the '$' */
455-
format_len--;
456-
} else {
457-
argnum = currarg++;
465+
argnum = php_sprintf_get_argnum(&format, &format_len);
466+
if (argnum == ARG_NUM_INVALID) {
467+
goto fail;
458468
}
459469

460470
/* after argnum comes modifiers */
@@ -489,7 +499,34 @@ php_formatted_print(char *format, size_t format_len, zval *args, int argc, int n
489499

490500

491501
/* after modifiers comes width */
492-
if (isdigit((int)*format)) {
502+
if (*format == '*') {
503+
format++;
504+
format_len--;
505+
506+
int width_argnum = php_sprintf_get_argnum(&format, &format_len);
507+
if (width_argnum == ARG_NUM_INVALID) {
508+
goto fail;
509+
}
510+
if (width_argnum == ARG_NUM_NEXT) {
511+
width_argnum = currarg++;
512+
}
513+
if (width_argnum >= argc) {
514+
max_missing_argnum = MAX(max_missing_argnum, width_argnum);
515+
continue;
516+
}
517+
tmp = &args[width_argnum];
518+
ZVAL_DEREF(tmp);
519+
if (Z_TYPE_P(tmp) != IS_LONG) {
520+
zend_value_error("Width must be an integer");
521+
goto fail;
522+
}
523+
if (Z_LVAL_P(tmp) < 0 || Z_LVAL_P(tmp) > INT_MAX) {
524+
zend_value_error("Width must be greater than zero and less than %d", INT_MAX);
525+
goto fail;
526+
}
527+
width = Z_LVAL_P(tmp);
528+
adjusting |= ADJ_WIDTH;
529+
} else if (isdigit((int)*format)) {
493530
PRINTF_DEBUG(("sprintf: getting width\n"));
494531
if ((width = php_sprintf_getnumber(&format, &format_len)) < 0) {
495532
zend_value_error("Width must be greater than zero and less than %d", INT_MAX);
@@ -506,7 +543,35 @@ php_formatted_print(char *format, size_t format_len, zval *args, int argc, int n
506543
format++;
507544
format_len--;
508545
PRINTF_DEBUG(("sprintf: getting precision\n"));
509-
if (isdigit((int)*format)) {
546+
if (*format == '*') {
547+
format++;
548+
format_len--;
549+
550+
int prec_argnum = php_sprintf_get_argnum(&format, &format_len);
551+
if (prec_argnum == ARG_NUM_INVALID) {
552+
goto fail;
553+
}
554+
if (prec_argnum == ARG_NUM_NEXT) {
555+
prec_argnum = currarg++;
556+
}
557+
if (prec_argnum >= argc) {
558+
max_missing_argnum = MAX(max_missing_argnum, prec_argnum);
559+
continue;
560+
}
561+
tmp = &args[prec_argnum];
562+
ZVAL_DEREF(tmp);
563+
if (Z_TYPE_P(tmp) != IS_LONG) {
564+
zend_value_error("Precision must be an integer");
565+
goto fail;
566+
}
567+
if (Z_LVAL_P(tmp) < -1 || Z_LVAL_P(tmp) > INT_MAX) {
568+
zend_value_error("Precision must be between -1 and %d", INT_MAX);
569+
goto fail;
570+
}
571+
precision = Z_LVAL_P(tmp);
572+
adjusting |= ADJ_PRECISION;
573+
expprec = 1;
574+
} else if (isdigit((int)*format)) {
510575
if ((precision = php_sprintf_getnumber(&format, &format_len)) < 0) {
511576
zend_value_error("Precision must be greater than zero and less than %d", INT_MAX);
512577
goto fail;
@@ -528,11 +593,19 @@ php_formatted_print(char *format, size_t format_len, zval *args, int argc, int n
528593
}
529594
PRINTF_DEBUG(("sprintf: format character='%c'\n", *format));
530595

596+
if (argnum == ARG_NUM_NEXT) {
597+
argnum = currarg++;
598+
}
531599
if (argnum >= argc) {
532600
max_missing_argnum = MAX(max_missing_argnum, argnum);
533601
continue;
534602
}
535603

604+
if (expprec && precision == -1 && *format != 'g' && *format != 'G') {
605+
zend_value_error("Precision -1 is only supported for %%g and %%G");
606+
goto fail;
607+
}
608+
536609
/* now we expect to find a type specifier */
537610
tmp = &args[argnum];
538611
switch (*format) {
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
--TEST--
2+
Star width and precision in sprintf()
3+
--FILE--
4+
<?php
5+
6+
7+
$f = 1.23456789012345678;
8+
var_dump($f);
9+
10+
printf("%.*f\n", 10, $f);
11+
printf("%.*G\n", 10, $f);
12+
printf("%.*G\n", -1, $f);
13+
printf("%.*s\n", 3, "foobar");
14+
echo "\n";
15+
16+
printf("%*f\n", 10, $f);
17+
printf("%*G\n", 10, $f);
18+
printf("%*s\n", 10, "foobar");
19+
echo "\n";
20+
21+
printf("%*.*f\n", 10, 3, $f);
22+
printf("%*.*G\n", 10, 3, $f);
23+
printf("%*.*s\n", 10, 3, "foobar");
24+
echo "\n";
25+
26+
printf("%1$.*2\$f\n", $f, 10);
27+
printf("%.*2\$f\n", $f, 10);
28+
printf("%2$.*f\n", 10, $f);
29+
printf("%1$*2\$f\n", $f, 10);
30+
printf("%*2\$f\n", $f, 10);
31+
printf("%2$*f\n", 10, $f);
32+
printf("%1$*2$.*3\$f\n", $f, 10, 3);
33+
printf("%*2$.*3\$f\n", $f, 10, 3);
34+
printf("%3$*.*f\n", 10, 3, $f);
35+
echo "\n";
36+
37+
try {
38+
printf("%.*G\n", "foo", 1.5);
39+
} catch (ValueError $e) {
40+
echo $e->getMessage(), "\n";
41+
}
42+
43+
try {
44+
printf("%.*G\n", -100, 1.5);
45+
} catch (ValueError $e) {
46+
echo $e->getMessage(), "\n";
47+
}
48+
49+
try {
50+
printf("%.*s\n", -1, "Foo");
51+
} catch (ValueError $e) {
52+
echo $e->getMessage(), "\n";
53+
}
54+
55+
try {
56+
printf("%*G\n", -1, $f);
57+
} catch (ValueError $e) {
58+
echo $e->getMessage(), "\n";
59+
}
60+
61+
?>
62+
--EXPECTF--
63+
float(1.2345678901234567)
64+
1.2345678901
65+
1.23456789
66+
1.2345678901234567
67+
foo
68+
69+
1.234568
70+
1.23457
71+
foobar
72+
73+
1.235
74+
1.23
75+
foo
76+
77+
1.2345678901
78+
1.2345678901
79+
1.2345678901
80+
1.234568
81+
1.234568
82+
1.234568
83+
1.235
84+
1.235
85+
1.235
86+
87+
Precision must be an integer
88+
Precision must be between -1 and 2147483647
89+
Precision -1 is only supported for %g and %G
90+
Width must be greater than zero and less than 2147483647

0 commit comments

Comments
 (0)