Rev 2 | Blame | Compare with Previous | Last modification | View Log | RSS feed
/*
* Copyright (c) 2002-2006 The TenDRA Project <http://www.tendra.org/>.
* 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,
* 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.
* 3. Neither the name of The TenDRA Project nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific, prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``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 COPYRIGHT HOLDERS OR
* CONTRIBUTORS 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.
*
* $Id$
*/
/*
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.
*/
#include "config.h"
#include <limits.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 "char.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(void)
{
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;
}