Abhinav Gauniyal
Abhinav Gauniyal

Reputation: 7574

terminfo parameterized string %d encoding behavior

I'm trying to understand the behavior of %d encoding in terminfo's parameterized string parser. The relevant man page is here and states that -

%[[:]flags][width[.precision]][doxXs]
        as  in  printf, flags are [-+#] and space.  Use a ":" to allow the
        next character to be a "-" flag, avoiding interpreting "%-" as  an
        operator.

but says nothing about from where the values are to be printed and what to do with edge cases. Are they from stack or from the arguments being passed to the parameterized string? Also what happens when extra arguments are passed(non equal %d in parameterized string) or extra %d are present(incorrect parameterized string?)? Is that undefined behavior or implementation-defined or defined by definition somewhere?

I tried to check some cases by manually writing some valid and invalid strings and verifying the output but everything till now is bit inconsistent so I cannot see a pattern here -

#include <iostream>
#include <curses.h>
#include <term.h>

using namespace std;

int main() {
    // single %d prints single argument
    // => 2
    auto res = tparm("[%d]", 2);
    cout << res << endl;

    // single %d prints single argument and ignores additional
    // => 2
    res = tparm("[%d]", 2, 3, 4);
    cout << res << endl;

    // multiple %d prints 0 for absent additional arguments
    // => 2-0-0-0
    res = tparm("[%d-%d-%d-%d]", 2);
    cout << res << endl;

    // multiple %d prints with equal number of arguments prints
    // first two correctly and rest 0
    // => 2-3-0-0-0
    res = tparm("[%d-%d-%d-%d-%d]", 2,3,4,5,6);
    cout << res << endl;

    // single value pushed to stack prints from stack
    // => 2
    res = tparm("[%p1%d]", 2);
    cout << res << endl;

    // single value pushed to stack prints from stack and ignores extra arguments
    // => 2
    res = tparm("[%p1%d]", 2,3,4);
    cout << res << endl;

    // single value pushed to stack prints from stack and additional prints are 0
    // if no arguments are provided
    // => 2-0-0
    res = tparm("[%p1%d-%d-%d]", 2);
    cout << res << endl;

    // single value pushed to stack prints from stack and additional prints 0
    // even if equal arguments are provided
    // => 2-0-0
    res = tparm("[%p1%d-%d-%d]", 2,3,4);
    cout << res << endl;

    // single value pushed to stack prints from stack after pop()?
    // => 100-<garbage>
    res = tparm("[%p1%d-%c]", 100);
    cout << res << endl;

    // pushed to stack via {} and equal arguments provided, prints all
    // => 2-1-100-200
    res = tparm("[%{1}%{2}%d-%d-%d-%d]", 100, 200);
    cout << res << endl;

    // pushed to stack via {} and %p1 equal arguments provided
    // prints only stack and rest 0
    // => 100-2-1-0
    res = tparm("[%{1}%{2}%p1%d-%d-%d-%d]", 100, 200);
    cout << res << endl;
}

Upvotes: 1

Views: 189

Answers (1)

Thomas Dickey
Thomas Dickey

Reputation: 54475

One problem noted in your examples is that they exercise undefined behavior. The defined behavior for terminfo uses explicit parameter tokens such as %p1 to pass push parameters onto the stack, where they can be popped by operators such as %d. Lacking that, you're relying on ncurses's workarounds for termcap (which has no parameter tokens), and offhand, that will make an expression like

    res = tparm("[%d-%d-%d-%d]", 2);

attempt to read more than one parameter from the parameter list. Your example gives one, so you're in the realm of C language undefined behavior (i.e., wrong number of parameters in a variable-length argument list). If your call passed extra parameters, that might be okay (see for example Visual C accepting wrong number of arguments?), but with fewer, the result could be accessing memory which is outside the parameter list.

Responding to the comment: ncurses allows termcap-style use of %d without %p1. But it counts the number of parameters, making a list of those before doing the actual substitution. Since it is processing those as a variable-length argument list, it cannot determine if your application passed the wrong number of parameters for a given string.

Further reading:

    /*
     * Analyze the string to see how many parameters we need from the varargs list,
     * and what their types are.  We will only accept string parameters if they
     * appear as a %l or %s format following an explicit parameter reference (e.g.,
     * %p2%s).  All other parameters are numbers.
     *
     * 'number' counts coarsely the number of pop's we see in the string, and
     * 'popcount' shows the highest parameter number in the string.  We would like
     * to simply use the latter count, but if we are reading termcap strings, there
     * may be cases that we cannot see the explicit parameter numbers.
     */

as well as features such as tc_BUMP which accommodate termcap's lack of parameter tokens.

Upvotes: 1

Related Questions