views:

105

answers:

3

Given some unknown input, how do you tell which variables are being substituted into a (s)printf statement?

printf("%s %s", "a", "b");     // both used
printf("%s",    "a", "b");     // only the first one used
printf('%1$s %1$s', "a", "b"); //      "        "
printf('%s %1$s', "a", "b");    //     "        "
printf('%1$s %s %1$s', "a", "b");   // "        "
printf('%2$s', "a", "b");      // only the second one used.

Checking the resultant string for the presence of the first or second variables won't help, because they could have the same value.

In my own situation, there is only ever 2 variables that could be substituted, and I need to know whether the second one is used or not.

+1  A: 

Parse the format string yourself and see how many args it expects.

EDIT: A psuedocode example (no error checking):

bool arg1used = false;
bool arg2used = false;
int unspecifiedscount = 0;
for (int i = 0; i < s.length; i++) {
    if s[i] != '%' continue;
    switch s[i+1] {
        case '%':
            i++;
            break;
        case 's':
            if unspecifiedscount == 0 arg1used = true;
            if unspecifiedscount == 1 arg2used = true;
            break;
        case '1':
            if s[i+2] == '$' && s[i+3] == 's' {
                arg1used = true;
                i+=3;
                true;
            }
            break;
        case '2':
            if s[i+2] == '$' && s[i+3] == 's' {
                arg2used = true;
                i+=3;
                true;
            }
            break;
    }
}
Anon.
yeah, that was pretty much exactly what I was asking for help with.
nickf
One way would be to walk the string looking for % characters.If you see %1$s or your first %s, set a flag indicating the first variable is used. Likewise for %2$s or your second %s.
Anon.
@nickf, in other words, how do you suppose the printf() implementation itself figures it out? Then do that. :P Which means looking for %. (Note %% becomes %.)
asveikau
There's a lot of things to consider however. For example: `sprintf("%% %s", "a", "b") == "% a"`, whereas `sprintf("% % %s", "a", "b") == "% b"`.
nickf
Also: `sprintf("% % % %s", "a", "b") == "% %s"` and sprintf("% % % % %s", "a", "b") ==> error, too few args`.
nickf
Like I said, walk the string. I'll edit something in.You could also regex something up, but unless you're comfortable with regular expressions I wouldn't recommend it.
Anon.
+1  A: 

I've come up with a kludgey way which works in this situation, but would be interested to hear better solutions which are more generic.

if (($result = @sprintf($input, "a")) === false) {
    // need two arguments
    $result = sprintf($input, "a", "b");
} else {
    // only needed one argument
}

Basically, it just tries it with one argument, and if that didn't work, then you know it needs two.

nickf
+1  A: 

Here is a bit hackish function to do it:

function printf_and_return_min_args_required(/* var args */) {
    $old_track = ini_set('track_errors', '1');
    $args = func_get_args();
    $args2 = array();
    $required = 100; 
    foreach ($args as $arg) {
     $args2[] = $arg;
     if (@call_user_func_array("printf", $args2)) {
      $required = count($args2) - 1;
      break;
     }
    }
    ini_set('track_errors', $old_track);
    return $required;
}

For sprintf() you might have to return array($result, $required) instead if you don't want the $result printed by the function.

Lukman