/*
* Copyright (c) 2002, The Tendra Project
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice unmodified, this list of conditions, and the following
* disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*
* Crown Copyright (c) 1997
*
* This TenDRA(r) Computer Program is subject to Copyright
* owned by the United Kingdom Secretary of State for Defence
* acting through the Defence Evaluation and Research Agency
* (DERA). It is made available to Recipients with a
* royalty-free licence for its use, reproduction, transfer
* to other parties and amendment for any purpose not excluding
* product development provided that any such use et cetera
* shall be deemed to be acceptance of the following conditions:-
*
* (1) Its Recipients shall ensure that this Notice is
* reproduced upon any copies or amended versions of it;
*
* (2) Any amended version of it shall be clearly marked to
* show both the nature of and the organisation responsible
* for the relevant amendment or amendments;
*
* (3) Its onward transfer from a recipient to another
* party shall be deemed to be that party's acceptance of
* these conditions;
*
* (4) DERA gives no warranty or assurance as to its
* quality or suitability for any purpose and DERA accepts
* no liability whatsoever in relation to any use to which
* it may be put.
*
* $TenDRA: tendra/src/producers/common/construct/printf.c,v 1.8 2004/09/05 03:54:06 bp Exp $
*/
#include "config.h"
#include "producer.h"
#include
#include "char.h"
#include "c_types.h"
#include "id_ops.h"
#include "str_ops.h"
#include "type_ops.h"
#include "error.h"
#include "catalog.h"
#include "option.h"
#include "basetype.h"
#include "chktype.h"
#include "convert.h"
#include "literal.h"
#include "printf.h"
#include "typeid.h"
#include "ustring.h"
/*
* PRINTF AND SCANF FLAGS
*
* These values are used to indicate the various flags, field widths and
* precision used to modify printf and scanf format strings.
*/
#define PRINTF_NONE ((unsigned) 0x0000)
#define PRINTF_THOUSAND ((unsigned) 0x0001)
#define PRINTF_LEFT ((unsigned) 0x0002)
#define PRINTF_SIGN ((unsigned) 0x0004)
#define PRINTF_SPACE ((unsigned) 0x0008)
#define PRINTF_ALT ((unsigned) 0x0010)
#define PRINTF_ZERO ((unsigned) 0x0020)
#define PRINTF_WIDTH ((unsigned) 0x0040)
#define PRINTF_PREC ((unsigned) 0x0080)
#define PRINTF_ERROR ((unsigned) 0x0100)
#define PRINTF_FLAGS ((unsigned) 0x003f)
#define PRINTF_ARITH ((unsigned) 0x003d)
/*
* SKIP A NUMBER OF DIGITS FROM A STRING
*
* This routine skips a number of decimal digits from the string str.
* The first character and its character type are given by c and pc.
* The routine returns the first non-digit character, assigning the
* value read (or UINT_MAX if an overflow occurs) into pn.
*/
static unsigned long
read_width(STRING str, unsigned long c, int *pc, unsigned *pn)
{
unsigned n = 0;
unsigned m = 0;
int overflow = 0;
while (*pc == CHAR_SIMPLE && (c >= char_zero && c <= char_nine)) {
n = 10 * n + (unsigned) (c - char_zero);
if (n < m) overflow = 1;
m = n;
c = get_string_char (str, pc);
}
if (overflow) n = UINT_MAX;
*pn = n;
return (c);
}
/*
* READ A PRINTF ARGUMENT NUMBER
*
* In several places in a printf or scanf format string, a sequence of
* characters of the form 'n$' for a sequence of decimal digits n is
* used to indicate the nth, as opposed to the next, argument. This
* routine checks for such sequences in the string str, returning n.
* margs gives the maximum value allowed for n, if n exceeds this value
* then it UINT_MAX is returned. A value of 0 is returned if str does
* not have this form (note that the arguments are numbered from 1).
*/
static unsigned
read_arg_no(STRING str, unsigned margs)
{
unsigned long tok = DEREF_ulong (str_simple_tok (str));
int ch = CHAR_SIMPLE;
unsigned long c = get_string_char (str, &ch);
if (ch == CHAR_SIMPLE && (c >= char_zero && c <= char_nine)) {
unsigned n = 0;
c = read_width (str, c, &ch, &n);
if (ch == CHAR_SIMPLE && c == char_dollar) {
if (n > margs) {
/* Argument number out of range */
report (crt_loc, ERR_printf_arg_large (n));
n = UINT_MAX;
}
if (n == 0) {
/* Can't have argument zero */
report (crt_loc, ERR_printf_arg_zero ());
n = 1;
}
return (n);
}
}
COPY_ulong (str_simple_tok (str), tok);
return (0);
}
/*
* CHECK A PRINTF OR SCANF FORMAT STRING
*
* This routine combines a printf or scanf format string corresponding to
* the type n, modified by the type modifier m. flags gives any invalid
* format flags.
*/
static BUILTIN_TYPE
check_format(string s, BUILTIN_TYPE n, BUILTIN_TYPE m, unsigned flags)
{
switch (m) {
case ntype_sshort : {
/* 'h' modifier */
switch (n) {
case ntype_sint : n = ntype_sshort ; break;
case ntype_uint : n = ntype_ushort ; break;
default : flags |= PRINTF_ERROR ; break;
}
break;
}
case ntype_slong : {
/* 'l' modifier */
switch (n) {
case ntype_char : n = ntype_wchar_t ; break;
case ntype_uchar : n = ntype_none ; break;
case ntype_sint : n = ntype_slong ; break;
case ntype_uint : n = ntype_ulong ; break;
case ntype_float : n = ntype_double ; break;
default : flags |= PRINTF_ERROR ; break;
}
break;
}
case ntype_ldouble : {
/* 'L' modifier */
switch (n) {
case ntype_float : n = ntype_ldouble ; break;
case ntype_double : n = ntype_ldouble ; break;
default : flags |= PRINTF_ERROR ; break;
}
break;
}
}
if (flags) {
if (flags & PRINTF_ERROR) {
report (crt_loc, ERR_printf_invalid (s));
s [1] = s [2];
s [2] = 0;
}
if (flags & PRINTF_FLAGS) {
character t [8];
string r = t;
if (flags & PRINTF_THOUSAND) *(r++) = char_single_quote;
if (flags & PRINTF_LEFT) *(r++) = char_minus;
if (flags & PRINTF_SIGN) *(r++) = char_plus;
if (flags & PRINTF_SPACE) *(r++) = char_space;
if (flags & PRINTF_ALT) *(r++) = char_hash;
if (flags & PRINTF_ZERO) *(r++) = char_zero;
*r = 0;
report (crt_loc, ERR_printf_flags (t, s));
}
if (flags & PRINTF_WIDTH) {
report (crt_loc, ERR_printf_width (s));
}
if (flags & PRINTF_PREC) {
report (crt_loc, ERR_printf_precision (s));
}
}
return (n);
}
/*
* SET AN ARGUMENT TYPE
*
* This routine sets the nth argument of the list p to be t. The elements
* of p are in reverse order and numbered from 1. A zero value for n is
* equivalent to 'LENGTH_list (p) + 1'. The state flag is used to keep
* track of whether numbered and unnumbered arguments are mixed.
*/
static LIST (TYPE)
set_printf_arg(LIST (TYPE) p, unsigned n, TYPE t, int *state)
{
if (n == 0) {
/* Simple case */
if (*state == 2) {
report (crt_loc, ERR_printf_arg_mix ());
*state = 3;
} else {
*state = 1;
}
CONS_type (t, p, p);
} else {
unsigned m = UINT_MAX;
if (*state == 1) {
report (crt_loc, ERR_printf_arg_mix ());
*state = 3;
} else {
*state = 2;
}
if (n != m) {
/* Valid argument number */
TYPE s;
LIST (TYPE) q;
m = LENGTH_list (p);
while (m < n) {
/* Add extra arguments if necessary */
CONS_type (NULL_type, p, p);
m++;
}
q = p;
while (m > n) {
/* Scan to nth argument */
q = TAIL_list (q);
m--;
}
s = DEREF_type (HEAD_list (q));
if (!IS_NULL_type (s) && !EQ_type (s, t)) {
/* Check for compatibility with previous type */
ERROR err = NULL_err;
t = check_compatible (s, t, 1, &err, 1);
if (!IS_NULL_err (err)) {
err = set_severity (err, OPT_whatever, 0);
err = concat_error (err, ERR_printf_arg_compat (n));
report (crt_loc, err);
}
}
COPY_type (HEAD_list (q), t);
}
}
return (p);
}
/*
* PRINTF AND SCANF ARGUMENT TYPES
*
* These variables give those printf and scanf argument types which are
* not built-in types or pointers to built-in types.
*/
static TYPE ptr_const_char = NULL_type;
static TYPE ptr_const_wchar_t = NULL_type;
static TYPE ptr_ptr_void = NULL_type;
static TYPE type_wint_t = NULL_type;
/*
* FIND A PRINTF ARGUMENT TYPE
*
* This routine reads a single printf argument type from the string
* str, adding it to the list p. It is entered immediately after the
* '%' in the format string has been read.
*/
static LIST (TYPE)
add_printf_arg(STRING str, LIST (TYPE) p, unsigned margs, int *state)
{
unsigned flag;
character s [8];
string r = s;
unsigned long c;
TYPE t = NULL_type;
int ch = CHAR_SIMPLE;
BUILTIN_TYPE n = ntype_none;
unsigned flags = PRINTF_NONE;
/* Read argument number */
unsigned arg = read_arg_no (str, margs);
/* Read flags */
do {
c = get_string_char (str, &ch);
if (ch == CHAR_SIMPLE) {
if (c == char_percent && flags == PRINTF_NONE && arg == 0) {
/* Have precisely '%%' */
return (p);
}
switch (c) {
case char_single_quote : flag = PRINTF_THOUSAND ; break;
case char_minus : flag = PRINTF_LEFT ; break;
case char_plus : flag = PRINTF_SIGN ; break;
case char_space : flag = PRINTF_SPACE ; break;
case char_hash : flag = PRINTF_ALT ; break;
case char_zero : flag = PRINTF_ZERO ; break;
default : flag = PRINTF_NONE ; break;
}
flags |= flag;
} else {
flag = PRINTF_NONE;
}
} while (flag != PRINTF_NONE);
/* Read field width */
if (ch == CHAR_SIMPLE) {
if (c == char_asterix) {
unsigned arg2 = read_arg_no (str, margs);
p = set_printf_arg (p, arg2, type_sint, state);
c = get_string_char (str, &ch);
flags |= PRINTF_WIDTH;
} else if (c >= char_zero && c <= char_nine) {
unsigned v = 0;
c = read_width (str, c, &ch, &v);
flags |= PRINTF_WIDTH;
}
}
/* Read precision */
if (ch == CHAR_SIMPLE && c == char_dot) {
c = get_string_char (str, &ch);
if (ch == CHAR_SIMPLE) {
if (c == char_asterix) {
unsigned arg2 = read_arg_no (str, margs);
p = set_printf_arg (p, arg2, type_sint, state);
c = get_string_char (str, &ch);
} else if (c >= char_zero && c <= char_nine) {
unsigned v = 0;
c = read_width (str, c, &ch, &v);
}
}
flags |= PRINTF_PREC;
}
/* Read type modifier */
*(r++) = char_percent;
if (ch == CHAR_SIMPLE) {
switch (c) {
case char_h : n = ntype_sshort ; break;
case char_l : n = ntype_slong ; break;
case char_L : n = ntype_ldouble ; break;
}
if (n != ntype_none) {
*(r++) = (character) c;
c = get_string_char (str, &ch);
}
}
/* Read type specifier */
r [0] = (character) c;
r [1] = 0;
if (ch == CHAR_SIMPLE) {
switch (c) {
case char_c : {
flags &= (PRINTF_ARITH | PRINTF_PREC);
n = check_format (s, ntype_uchar, n, flags);
if (n == ntype_none) goto wint_lab;
break;
}
case char_C : {
flags &= PRINTF_ARITH;
n = check_format (s, ntype_none, n, flags);
wint_lab : {
t = type_wint_t;
if (IS_NULL_type (t)) {
t = find_std_type ("wint_t", 0, 0);
if (!IS_NULL_type (t)) {
type_wint_t = t;
} else {
t = type_error;
}
}
}
break;
}
case char_d :
case char_i : {
flags &= PRINTF_ALT;
n = check_format (s, ntype_sint, n, flags);
break;
}
case char_o : {
flags &= (PRINTF_ALT | PRINTF_THOUSAND);
n = check_format (s, ntype_uint, n, flags);
break;
}
case char_u : {
flags &= PRINTF_ALT;
n = check_format (s, ntype_uint, n, flags);
break;
}
case char_x :
case char_X : {
flags &= PRINTF_THOUSAND;
n = check_format (s, ntype_uint, n, flags);
break;
}
case char_e :
case char_E : {
flags &= PRINTF_THOUSAND;
n = check_format (s, ntype_double, n, flags);
break;
}
case char_f :
case char_g :
case char_G : {
n = check_format (s, ntype_double, n, PRINTF_NONE);
break;
}
case char_s : {
flags &= PRINTF_ARITH;
n = check_format (s, ntype_char, n, flags);
if (n == ntype_char) {
t = ptr_const_char;
} else {
t = ptr_const_wchar_t;
}
n = ntype_none;
break;
}
case char_S : {
flags &= PRINTF_ARITH;
n = check_format (s, ntype_none, n, flags);
t = ptr_const_wchar_t;
break;
}
case char_p : {
flags &= (PRINTF_ARITH | PRINTF_PREC);
n = check_format (s, ntype_none, n, flags);
t = type_void_star;
break;
}
case char_n : {
n = check_format (s, ntype_sint, n, flags);
t = ptr_type_builtin [n];
n = ntype_none;
break;
}
default : {
report (crt_loc, ERR_printf_unknown (s));
t = type_error;
n = ntype_none;
break;
}
}
if (n != ntype_none) t = type_builtin [n];
} else {
report (crt_loc, ERR_printf_unknown (s));
t = type_error;
}
if (!IS_NULL_type (t)) {
t = arg_promote_type (t, KILL_err);
p = set_printf_arg (p, arg, t, state);
}
return (p);
}
/*
* FIND A SCANF ARGUMENT TYPE
*
* This routine reads a single scanf argument type from the string
* str, adding it to the list p. It is entered immediately after the
* '%' in the format string has been read.
*/
static LIST (TYPE)
add_scanf_arg(STRING str, LIST (TYPE) p, unsigned margs, int *state)
{
character s [8];
string r = s;
int ignore = 0;
unsigned long c;
TYPE t = NULL_type;
int ch = CHAR_SIMPLE;
BUILTIN_TYPE n = ntype_none;
unsigned flags = PRINTF_NONE;
/* Read argument number */
unsigned arg = read_arg_no (str, margs);
/* Check for initial '*' */
c = get_string_char (str, &ch);
if (ch == CHAR_SIMPLE) {
if (c == char_percent && arg == 0) {
/* Have precisely '%%' */
return (p);
}
if (c == char_asterix) {
c = get_string_char (str, &ch);
ignore = 1;
}
}
/* Read field width */
if (ch == CHAR_SIMPLE && (c >= char_zero && c <= char_nine)) {
unsigned v = 0;
c = read_width (str, c, &ch, &v);
flags |= PRINTF_WIDTH;
}
/* Read type modifier */
*(r++) = char_percent;
if (ch == CHAR_SIMPLE) {
switch (c) {
case char_h : n = ntype_sshort ; break;
case char_l : n = ntype_slong ; break;
case char_L : n = ntype_ldouble ; break;
}
if (n != ntype_none) {
*(r++) = (character) c;
c = get_string_char (str, &ch);
}
}
/* Read type specifier */
r [0] = (character) c;
r [1] = 0;
if (ch == CHAR_SIMPLE) {
switch (c) {
case char_c : {
n = check_format (s, ntype_char, n, PRINTF_NONE);
break;
}
case char_C : {
n = check_format (s, ntype_wchar_t, n, PRINTF_NONE);
break;
}
case char_d :
case char_i : {
n = check_format (s, ntype_sint, n, PRINTF_NONE);
break;
}
case char_o :
case char_u :
case char_x :
case char_X : {
n = check_format (s, ntype_uint, n, PRINTF_NONE);
break;
}
case char_e :
case char_E :
case char_f :
case char_g :
case char_G : {
n = check_format (s, ntype_float, n, PRINTF_NONE);
break;
}
case char_s : {
n = check_format (s, ntype_char, n, PRINTF_NONE);
break;
}
case char_open_square : {
c = get_string_char (str, &ch);
if (ch == CHAR_SIMPLE && c == char_circum) {
IGNORE get_string_char (str, &ch);
}
do {
c = get_string_char (str, &ch);
if (ch == CHAR_NONE) {
report (crt_loc, ERR_printf_unterm (s));
break;
}
} while (ch != CHAR_SIMPLE || c != char_close_square);
r = ustrlit ("%[...]");
n = check_format (r, ntype_char, n, PRINTF_NONE);
break;
}
case char_S : {
n = check_format (s, ntype_wchar_t, n, PRINTF_NONE);
break;
}
case char_p : {
n = check_format (s, ntype_none, n, PRINTF_NONE);
t = ptr_ptr_void;
break;
}
case char_n : {
if (ignore) {
r = ustrlit ("*");
report (crt_loc, ERR_printf_flags (r, s));
}
n = check_format (s, ntype_sint, n, flags);
break;
}
default : {
report (crt_loc, ERR_printf_unknown (s));
t = type_error;
n = ntype_none;
break;
}
}
if (n != ntype_none) t = ptr_type_builtin [n];
} else {
report (crt_loc, ERR_printf_unknown (s));
t = type_error;
}
if (!IS_NULL_type (t) && !ignore) {
p = set_printf_arg (p, arg, t, state);
}
return (p);
}
/*
* FIND PRINTF OR SCANF ARGUMENT TYPES
*
* This routine finds the list of arguments expected by a printf-like
* or scanf-like function with format string fmt. margs gives the
* number argument number which may be specified using '%n$', and pf is
* the value returned by is_printf_type.
*/
LIST (TYPE)
find_printf_args(STRING str, unsigned margs, int pf)
{
int state = 0;
unsigned long c;
int ch = CHAR_SIMPLE;
LIST (TYPE) p = NULL_list (TYPE);
ulong tok = DEREF_ulong (str_simple_tok (str));
COPY_ulong (str_simple_tok (str), 0);
while (c = get_string_char (str, &ch), ch != CHAR_NONE) {
if (c == char_percent && ch == CHAR_SIMPLE) {
if (pf & 1) {
p = add_printf_arg (str, p, margs, &state);
} else {
p = add_scanf_arg (str, p, margs, &state);
}
}
}
if (state >= 2) {
int reported = 0;
LIST (TYPE) q = p;
while (!IS_NULL_list (q)) {
TYPE t = DEREF_type (HEAD_list (q));
if (IS_NULL_type (t)) {
/* No format string for given argument */
if (!reported) {
unsigned n = LENGTH_list (q);
report (crt_loc, ERR_printf_arg_none (n));
reported = 1;
}
COPY_type (HEAD_list (q), type_error);
} else {
reported = 0;
}
q = TAIL_list (q);
}
}
COPY_ulong (str_simple_tok (str), tok);
p = REVERSE_list (p);
return (p);
}
/*
* PRINTF AND SCANF STRING TYPES
*
* Functions like printf and scanf are indicated by an argument with one
* of the following types, which equal 'const char *' or 'const wchar_t *'.
*/
TYPE type_printf = NULL_type;
TYPE type_scanf = NULL_type;
TYPE type_wprintf = NULL_type;
TYPE type_wscanf = NULL_type;
/*
* IS A TYPE A PRINTF OR SCANF STRING TYPE?
*
* This routine checks whether the type t is derived from one of the
* printf or scanf string types above. It returns 1 for type_printf,
* 2 for type_scanf, 3 for type_wprintf and 4 for type_wscanf.
*/
int
is_printf_type(TYPE t)
{
IDENTIFIER tid = DEREF_id (type_name (t));
if (!IS_NULL_id (tid)) {
TYPE s = DEREF_type (id_class_name_etc_defn (tid));
if (IS_type_ptr (s)) {
if (EQ_type (s, type_printf)) return (1);
if (EQ_type (s, type_scanf)) return (2);
if (EQ_type (s, type_wprintf)) return (3);
if (EQ_type (s, type_wscanf)) return (4);
}
}
return (0);
}
/*
* INITIALISE PRINTF AND SCANF TYPES
*
* This routine initialises the printf and scanf strings.
*/
void
init_printf()
{
TYPE c = qualify_type (type_char, cv_const, 0);
TYPE w = qualify_type (type_wchar_t, cv_const, 0);
MAKE_type_ptr (cv_none, c, type_printf);
MAKE_type_ptr (cv_none, c, type_scanf);
MAKE_type_ptr (cv_none, w, type_wprintf);
MAKE_type_ptr (cv_none, w, type_wscanf);
MAKE_type_ptr (cv_none, c, ptr_const_char);
MAKE_type_ptr (cv_none, w, ptr_const_wchar_t);
MAKE_type_ptr (cv_none, type_void_star, ptr_ptr_void);
return;
}