blob: a4a97c9bf50d54162960bbdf58def9238ca15fe4 [file] [log] [blame]
/* Compiler implementation of the D programming language
* Copyright (C) 1999-2021 by The D Language Foundation, All Rights Reserved
* written by Walter Bright
* http://www.digitalmars.com
* Distributed under the Boost Software License, Version 1.0.
* http://www.boost.org/LICENSE_1_0.txt
*/
// Check the arguments to `printf` and `scanf` against the `format` string.
#include "root/dsystem.h"
#include "root/dcompat.h"
#include "arraytypes.h"
#include "cond.h"
#include "errors.h"
#include "expression.h"
#include "globals.h"
#include "identifier.h"
#include "mtype.h"
#include "target.h"
/* Different kinds of formatting specifications, variations we don't
care about are merged. (Like we don't care about the difference between
f, e, g, a, etc.)
For `scanf`, every format is a pointer.
*/
enum Format
{
Format_d, // int
Format_hhd, // signed char
Format_hd, // short int
Format_ld, // long int
Format_lld, // long long int
Format_jd, // intmax_t
Format_zd, // size_t
Format_td, // ptrdiff_t
Format_u, // unsigned int
Format_hhu, // unsigned char
Format_hu, // unsigned short int
Format_lu, // unsigned long int
Format_llu, // unsigned long long int
Format_ju, // uintmax_t
Format_g, // float (scanf) / double (printf)
Format_lg, // double (scanf)
Format_Lg, // long double (both)
Format_s, // char string (both)
Format_ls, // wchar_t string (both)
Format_c, // char (printf)
Format_lc, // wint_t (printf)
Format_p, // pointer
Format_n, // pointer to int
Format_hhn, // pointer to signed char
Format_hn, // pointer to short
Format_ln, // pointer to long int
Format_lln, // pointer to long long int
Format_jn, // pointer to intmax_t
Format_zn, // pointer to size_t
Format_tn, // pointer to ptrdiff_t
Format_GNU_a, // GNU ext. : address to a string with no maximum size (scanf)
Format_GNU_m, // GNU ext. : string corresponding to the error code in errno (printf) / length modifier (scanf)
Format_percent, // %% (i.e. no argument)
Format_error, // invalid format specification
};
/**************************************
* Parse the *length specifier* and the *specifier* of the following form:
* `[length]specifier`
*
* Params:
* format = format string
* idx = index of of start of format specifier,
* which gets updated to index past the end of it,
* even if `Format_error` is returned
* genSpecifier = Generic specifier. For instance, it will be set to `d` if the
* format is `hdd`.
* Returns:
* Format
*/
static Format parseGenericFormatSpecifier(const char *format,
size_t &idx, char &genSpecifier, bool useGNUExts =
findCondition(global.versionids, Identifier::idPool("CRuntime_Glibc")))
{
genSpecifier = 0;
const size_t length = strlen(format);
/* Read the `length modifier`
*/
const char lm = format[idx];
bool lm1= false; // if jztL
bool lm2= false; // if `hh` or `ll`
if (lm == 'j' ||
lm == 'z' ||
lm == 't' ||
lm == 'L')
{
++idx;
if (idx == length)
return Format_error;
lm1 = true;
}
else if (lm == 'h' || lm == 'l')
{
++idx;
if (idx == length)
return Format_error;
lm2 = lm == format[idx];
if (lm2)
{
++idx;
if (idx == length)
return Format_error;
}
}
/* Read the `specifier`
*/
Format specifier;
const char sc = format[idx];
genSpecifier = sc;
switch (sc)
{
case 'd':
case 'i':
if (lm == 'L')
specifier = Format_error;
else
specifier = lm == 'h' && lm2 ? Format_hhd :
lm == 'h' ? Format_hd :
lm == 'l' && lm2 ? Format_lld :
lm == 'l' ? Format_ld :
lm == 'j' ? Format_jd :
lm == 'z' ? Format_zd :
lm == 't' ? Format_td :
Format_d;
break;
case 'u':
case 'o':
case 'x':
case 'X':
if (lm == 'L')
specifier = Format_error;
else
specifier = lm == 'h' && lm2 ? Format_hhu :
lm == 'h' ? Format_hu :
lm == 'l' && lm2 ? Format_llu :
lm == 'l' ? Format_lu :
lm == 'j' ? Format_ju :
lm == 'z' ? Format_zd :
lm == 't' ? Format_td :
Format_u;
break;
case 'a':
if (useGNUExts)
{
// https://www.gnu.org/software/libc/manual/html_node/Dynamic-String-Input.html
specifier = Format_GNU_a;
break;
}
/* fall through */
case 'f':
case 'F':
case 'e':
case 'E':
case 'g':
case 'G':
case 'A':
if (lm == 'L')
specifier = Format_Lg;
else if (lm1 || lm2 || lm == 'h')
specifier = Format_error;
else
specifier = lm == 'l' ? Format_lg : Format_g;
break;
case 'c':
if (lm1 || lm2 || lm == 'h')
specifier = Format_error;
else
specifier = lm == 'l' ? Format_lc : Format_c;
break;
case 's':
if (lm1 || lm2 || lm == 'h')
specifier = Format_error;
else
specifier = lm == 'l' ? Format_ls : Format_s;
break;
case 'p':
if (lm1 || lm2 || lm == 'h' || lm == 'l')
specifier = Format_error;
else
specifier = Format_p;
break;
case 'n':
if (lm == 'L')
specifier = Format_error;
else
specifier = lm == 'l' && lm2 ? Format_lln :
lm == 'l' ? Format_ln :
lm == 'h' && lm2 ? Format_hhn :
lm == 'h' ? Format_hn :
lm == 'j' ? Format_jn :
lm == 'z' ? Format_zn :
lm == 't' ? Format_tn :
Format_n;
break;
case 'm':
if (useGNUExts)
{
// http://www.gnu.org/software/libc/manual/html_node/Other-Output-Conversions.html
specifier = Format_GNU_m;
break;
}
goto Ldefault;
default:
Ldefault:
specifier = Format_error;
break;
}
++idx;
return specifier; // success
}
Format formatError(size_t &idx, size_t i)
{
idx = i;
return Format_error;
}
/**************************************
* Parse the *format specifier* which is of the form:
*
* `%[*][width][length]specifier`
*
* Params:
* format = format string
* idx = index of `%` of start of format specifier,
* which gets updated to index past the end of it,
* even if `Format_error` is returned
* asterisk = set if there is a `*` sub-specifier
* Returns:
* Format
*/
static Format parseScanfFormatSpecifier(const char *format, size_t &idx,
bool &asterisk)
{
asterisk = false;
size_t i = idx;
assert(format[i] == '%');
const size_t length = strlen(format);
++i;
if (i == length)
return formatError(idx, i);
if (format[i] == '%')
{
idx = i + 1;
return Format_percent;
}
// * sub-specifier
if (format[i] == '*')
{
++i;
if (i == length)
return formatError(idx, i);
asterisk = true;
}
// fieldWidth
while (isdigit(format[i]))
{
i++;
if (i == length)
return formatError(idx, i);
}
/* Read the scanset
* A scanset can be anything, so we just check that it is paired
*/
if (format[i] == '[')
{
while (i < length)
{
if (format[i] == ']')
break;
++i;
}
// no `]` found
if (i == length)
return formatError(idx, i);
++i;
// no specifier after `]`
// it could be mixed with the one above, but then idx won't have the right index
if (i == length)
return formatError(idx, i);
}
/* Read the specifier
*/
char genSpec;
Format specifier = parseGenericFormatSpecifier(format, i, genSpec);
if (specifier == Format_error)
return formatError(idx, i);
idx = i;
return specifier; // success
}
/**************************************
* Parse the *format specifier* which is of the form:
*
* `%[flags][field width][.precision][length modifier]specifier`
*
* Params:
* format = format string
* idx = index of `%` of start of format specifier,
* which gets updated to index past the end of it,
* even if `Format_error` is returned
* widthStar = set if * for width
* precisionStar = set if * for precision
* Returns:
* Format
*/
static Format parsePrintfFormatSpecifier(const char *format, size_t &idx,
bool &widthStar, bool &precisionStar)
{
widthStar = false;
precisionStar = false;
size_t i = idx;
assert(format[i] == '%');
const size_t format_length = strlen(format);
const size_t length = format_length;
bool hash = false;
bool zero = false;
bool flags = false;
bool width = false;
bool precision = false;
++i;
if (i == length)
return formatError(idx, i);
if (format[i] == '%')
{
idx = i + 1;
return Format_percent;
}
/* Read the `flags`
*/
while (1)
{
const char c = format[i];
if (c == '-' ||
c == '+' ||
c == ' ')
{
flags = true;
}
else if (c == '#')
{
hash = true;
}
else if (c == '0')
{
zero = true;
}
else
break;
++i;
if (i == length)
return formatError(idx, i);
}
/* Read the `field width`
*/
{
const char c = format[i];
if (c == '*')
{
width = true;
widthStar = true;
++i;
if (i == length)
return formatError(idx, i);
}
else if ('1' <= c && c <= '9')
{
width = true;
++i;
if (i == length)
return formatError(idx, i);
while ('0' <= format[i] && format[i] <= '9')
{
++i;
if (i == length)
return formatError(idx, i);
}
}
}
/* Read the `precision`
*/
if (format[i] == '.')
{
precision = true;
++i;
if (i == length)
return formatError(idx, i);
const char c = format[i];
if (c == '*')
{
precisionStar = true;
++i;
if (i == length)
return formatError(idx, i);
}
else if ('0' <= c && c <= '9')
{
++i;
if (i == length)
return formatError(idx, i);
while ('0' <= format[i] && format[i] <= '9')
{
++i;
if (i == length)
return formatError(idx, i);
}
}
}
/* Read the specifier
*/
char genSpec;
Format specifier = parseGenericFormatSpecifier(format, i, genSpec);
if (specifier == Format_error)
return formatError(idx, i);
switch (genSpec)
{
case 'c':
case 's':
if (hash || zero)
return formatError(idx, i);
break;
case 'd':
case 'i':
if (hash)
return formatError(idx, i);
break;
case 'n':
if (hash || zero || precision || width || flags)
return formatError(idx, i);
break;
default:
break;
}
idx = i;
return specifier; // success
}
/*******************************************/
static Expression *getNextPrintfArg(const Loc &loc, Expressions &args, size_t &n,
size_t gnu_m_count, bool &skip)
{
if (n == args.length)
{
if (args.length < (n + 1) - gnu_m_count)
deprecation(loc, "more format specifiers than %d arguments", (int)n);
else
skip = true;
return NULL;
}
return args[n++];
}
static void errorPrintfFormat(const char *prefix, DString &slice, Expression *arg,
const char *texpect, Type *tactual)
{
deprecation(arg->loc, "%sargument `%s` for format specification `\"%.*s\"` must be `%s`, not `%s`",
prefix ? prefix : "", arg->toChars(), (int)slice.length, slice.ptr, texpect, tactual->toChars());
}
/******************************************
* Check that arguments to a printf format string are compatible
* with that string. Issue errors for incompatibilities.
*
* Follows the C99 specification for printf.
*
* Takes a generous, rather than strict, view of compatiblity.
* For example, an unsigned value can be formatted with a signed specifier.
*
* Diagnosed incompatibilities are:
*
* 1. incompatible sizes which will cause argument misalignment
* 2. deferencing arguments that are not pointers
* 3. insufficient number of arguments
* 4. struct arguments
* 5. array and slice arguments
* 6. non-pointer arguments to `s` specifier
* 7. non-standard formats
* 8. undefined behavior per C99
*
* Per the C Standard, extra arguments are ignored.
*
* No attempt is made to fix the arguments or the format string.
*
* Params:
* loc = location for error messages
* format = format string
* args = arguments to match with format string
* isVa_list = if a "v" function (format check only)
*
* Returns:
* `true` if errors occurred
* References:
* C99 7.19.6.1
* http://www.cplusplus.com/reference/cstdio/printf/
*/
bool checkPrintfFormat(const Loc &loc, const char *format, Expressions &args, bool isVa_list)
{
//printf("checkPrintFormat('%s')\n", format);
size_t n = 0; // index in args
size_t gnu_m_count = 0; // number of Format_GNU_m
const size_t format_length = strlen(format);
for (size_t i = 0; i < format_length;)
{
if (format[i] != '%')
{
++i;
continue;
}
bool widthStar = false;
bool precisionStar = false;
size_t j = i;
const Format fmt = parsePrintfFormatSpecifier(format, j, widthStar, precisionStar);
DString slice = DString(j - i, format + i);
i = j;
if (fmt == Format_percent)
continue; // "%%", no arguments
if (isVa_list)
{
// format check only
if (fmt == Format_error)
deprecation(loc, "format specifier `\"%.*s\"` is invalid", (int)slice.length, slice.ptr);
continue;
}
if (fmt == Format_GNU_m)
++gnu_m_count;
if (widthStar)
{
bool skip = false;
Expression *e = getNextPrintfArg(loc, args, n, gnu_m_count, skip);
if (skip)
continue;
if (!e)
return true;
Type *t = e->type->toBasetype();
if (t->ty != Tint32 && t->ty != Tuns32)
errorPrintfFormat("width ", slice, e, "int", t);
}
if (precisionStar)
{
bool skip = false;
Expression *e = getNextPrintfArg(loc, args, n, gnu_m_count, skip);
if (skip)
continue;
if (!e)
return true;
Type *t = e->type->toBasetype();
if (t->ty != Tint32 && t->ty != Tuns32)
errorPrintfFormat("precision ", slice, e, "int", t);
}
bool skip = false;
Expression *e = getNextPrintfArg(loc, args, n, gnu_m_count, skip);
if (skip)
continue;
if (!e)
return true;
Type *t = e->type->toBasetype();
Type *tnext = t->nextOf();
const unsigned c_longsize = target.c.longsize;
const unsigned ptrsize = target.ptrsize;
// Types which are promoted to int are allowed.
// Spec: C99 6.5.2.2.7
switch (fmt)
{
case Format_u: // unsigned int
case Format_d: // int
if (t->ty != Tint32 && t->ty != Tuns32)
errorPrintfFormat(NULL, slice, e, fmt == Format_u ? "uint" : "int", t);
break;
case Format_hhu: // unsigned char
case Format_hhd: // signed char
if (t->ty != Tint32 && t->ty != Tuns32 && t->ty != Tint8 && t->ty != Tuns8)
errorPrintfFormat(NULL, slice, e, fmt == Format_hhu ? "ubyte" : "byte", t);
break;
case Format_hu: // unsigned short int
case Format_hd: // short int
if (t->ty != Tint32 && t->ty != Tuns32 && t->ty != Tint16 && t->ty != Tuns16)
errorPrintfFormat(NULL, slice, e, fmt == Format_hu ? "ushort" : "short", t);
break;
case Format_lu: // unsigned long int
case Format_ld: // long int
if (!(t->isintegral() && t->size() == c_longsize))
{
if (fmt == Format_lu)
errorPrintfFormat(NULL, slice, e, (c_longsize == 4 ? "uint" : "ulong"), t);
else
errorPrintfFormat(NULL, slice, e, (c_longsize == 4 ? "int" : "long"), t);
}
break;
case Format_llu: // unsigned long long int
case Format_lld: // long long int
if (t->ty != Tint64 && t->ty != Tuns64)
errorPrintfFormat(NULL, slice, e, fmt == Format_llu ? "ulong" : "long", t);
break;
case Format_ju: // uintmax_t
case Format_jd: // intmax_t
if (t->ty != Tint64 && t->ty != Tuns64)
{
if (fmt == Format_ju)
errorPrintfFormat(NULL, slice, e, "core.stdc.stdint.uintmax_t", t);
else
errorPrintfFormat(NULL, slice, e, "core.stdc.stdint.intmax_t", t);
}
break;
case Format_zd: // size_t
if (!(t->isintegral() && t->size() == ptrsize))
errorPrintfFormat(NULL, slice, e, "size_t", t);
break;
case Format_td: // ptrdiff_t
if (!(t->isintegral() && t->size() == ptrsize))
errorPrintfFormat(NULL, slice, e, "ptrdiff_t", t);
break;
case Format_GNU_a: // Format_GNU_a is only for scanf
case Format_lg:
case Format_g: // double
if (t->ty != Tfloat64 && t->ty != Timaginary64)
errorPrintfFormat(NULL, slice, e, "double", t);
break;
case Format_Lg: // long double
if (t->ty != Tfloat80 && t->ty != Timaginary80)
errorPrintfFormat(NULL, slice, e, "real", t);
break;
case Format_p: // pointer
if (t->ty != Tpointer && t->ty != Tnull && t->ty != Tclass && t->ty != Tdelegate && t->ty != Taarray)
errorPrintfFormat(NULL, slice, e, "void*", t);
break;
case Format_n: // pointer to int
if (!(t->ty == Tpointer && tnext->ty == Tint32))
errorPrintfFormat(NULL, slice, e, "int*", t);
break;
case Format_ln: // pointer to long int
if (!(t->ty == Tpointer && tnext->isintegral() && !tnext->isunsigned() && tnext->size() == c_longsize))
errorPrintfFormat(NULL, slice, e, (c_longsize == 4 ? "int*" : "long*"), t);
break;
case Format_lln: // pointer to long long int
if (!(t->ty == Tpointer && tnext->ty == Tint64))
errorPrintfFormat(NULL, slice, e, "long*", t);
break;
case Format_hn: // pointer to short
if (!(t->ty == Tpointer && tnext->ty == Tint16))
errorPrintfFormat(NULL, slice, e, "short*", t);
break;
case Format_hhn: // pointer to signed char
if (!(t->ty == Tpointer && tnext->ty == Tint16))
errorPrintfFormat(NULL, slice, e, "byte*", t);
break;
case Format_jn: // pointer to intmax_t
if (!(t->ty == Tpointer && tnext->ty == Tint64))
errorPrintfFormat(NULL, slice, e, "core.stdc.stdint.intmax_t*", t);
break;
case Format_zn: // pointer to size_t
if (!(t->ty == Tpointer && tnext->isintegral() && tnext->isunsigned() && tnext->size() == ptrsize))
errorPrintfFormat(NULL, slice, e, "size_t*", t);
break;
case Format_tn: // pointer to ptrdiff_t
if (!(t->ty == Tpointer && tnext->isintegral() && !tnext->isunsigned() && tnext->size() == ptrsize))
errorPrintfFormat(NULL, slice, e, "ptrdiff_t*", t);
break;
case Format_c: // char
if (t->ty != Tint32 && t->ty != Tuns32)
errorPrintfFormat(NULL, slice, e, "char", t);
break;
case Format_lc: // wint_t
if (t->ty != Tint32 && t->ty != Tuns32)
errorPrintfFormat(NULL, slice, e, "wchar_t", t);
break;
case Format_s: // pointer to char string
if (!(t->ty == Tpointer && (tnext->ty == Tchar || tnext->ty == Tint8 || tnext->ty == Tuns8)))
errorPrintfFormat(NULL, slice, e, "char*", t);
break;
case Format_ls: // pointer to wchar_t string
{
if (!(t->ty == Tpointer && tnext == target.c.twchar_t))
errorPrintfFormat(NULL, slice, e, "wchar_t*", t);
break;
}
case Format_error:
deprecation(loc, "format specifier `\"%.*s\"` is invalid", (int)slice.length, slice.ptr);
break;
case Format_GNU_m:
break; // not assert(0) because it may go through it if there are extra arguments
case Format_percent:
default:
assert(0);
}
}
return false;
}
/*******************************************/
static Expression *getNextScanfArg(const Loc &loc, Expressions &args, size_t &n, bool asterisk)
{
if (n == args.length)
{
if (!asterisk)
deprecation(loc, "more format specifiers than %d arguments", (int)n);
return NULL;
}
return args[n++];
}
static void errorScanfFormat(const char *prefix, DString &slice,
Expression *arg, const char *texpect, Type *tactual)
{
deprecation(arg->loc, "%sargument `%s` for format specification `\"%.*s\"` must be `%s`, not `%s`",
prefix ? prefix : "", arg->toChars(), (int)slice.length, slice.ptr, texpect, tactual->toChars());
}
/******************************************
* Check that arguments to a scanf format string are compatible
* with that string. Issue errors for incompatibilities.
*
* Follows the C99 specification for scanf.
*
* Takes a generous, rather than strict, view of compatiblity.
* For example, an unsigned value can be formatted with a signed specifier.
*
* Diagnosed incompatibilities are:
*
* 1. incompatible sizes which will cause argument misalignment
* 2. deferencing arguments that are not pointers
* 3. insufficient number of arguments
* 4. struct arguments
* 5. array and slice arguments
* 6. non-standard formats
* 7. undefined behavior per C99
*
* Per the C Standard, extra arguments are ignored.
*
* No attempt is made to fix the arguments or the format string.
*
* Params:
* loc = location for error messages
* format = format string
* args = arguments to match with format string
* isVa_list = if a "v" function (format check only)
*
* Returns:
* `true` if errors occurred
* References:
* C99 7.19.6.2
* http://www.cplusplus.com/reference/cstdio/scanf/
*/
bool checkScanfFormat(const Loc &loc, const char *format, Expressions &args, bool isVa_list)
{
size_t n = 0;
const size_t format_length = strlen(format);
for (size_t i = 0; i < format_length;)
{
if (format[i] != '%')
{
++i;
continue;
}
bool asterisk = false;
size_t j = i;
const Format fmt = parseScanfFormatSpecifier(format, j, asterisk);
DString slice = DString(j - i, format + i);
i = j;
if (fmt == Format_percent || asterisk)
continue; // "%%", "%*": no arguments
if (isVa_list)
{
// format check only
if (fmt == Format_error)
deprecation(loc, "format specifier `\"%.*s\"` is invalid", (int)slice.length, slice.ptr);
continue;
}
Expression *e = getNextScanfArg(loc, args, n, asterisk);
if (!e)
return true;
Type *t = e->type->toBasetype();
Type *tnext = t->nextOf();
const unsigned c_longsize = target.c.longsize;
const unsigned ptrsize = target.ptrsize;
switch (fmt)
{
case Format_n:
case Format_d: // pointer to int
if (!(t->ty == Tpointer && tnext->ty == Tint32))
errorScanfFormat(NULL, slice, e, "int*", t);
break;
case Format_hhn:
case Format_hhd: // pointer to signed char
if (!(t->ty == Tpointer && tnext->ty == Tint16))
errorScanfFormat(NULL, slice, e, "byte*", t);
break;
case Format_hn:
case Format_hd: // pointer to short
if (!(t->ty == Tpointer && tnext->ty == Tint16))
errorScanfFormat(NULL, slice, e, "short*", t);
break;
case Format_ln:
case Format_ld: // pointer to long int
if (!(t->ty == Tpointer && tnext->isintegral() && tnext->size() == c_longsize))
errorScanfFormat(NULL, slice, e, (c_longsize == 4 ? "int*" : "long*"), t);
break;
case Format_lln:
case Format_lld: // pointer to long long int
if (!(t->ty == Tpointer && tnext->ty == Tint64))
errorScanfFormat(NULL, slice, e, "long*", t);
break;
case Format_jn:
case Format_jd: // pointer to intmax_t
if (!(t->ty == Tpointer && tnext->ty == Tint64))
errorScanfFormat(NULL, slice, e, "core.stdc.stdint.intmax_t*", t);
break;
case Format_zn:
case Format_zd: // pointer to size_t
if (!(t->ty == Tpointer && tnext->isintegral() && tnext->isunsigned() && tnext->size() == ptrsize))
errorScanfFormat(NULL, slice, e, "size_t*", t);
break;
case Format_tn:
case Format_td: // pointer to ptrdiff_t
if (!(t->ty == Tpointer && tnext->isintegral() && !tnext->isunsigned() && tnext->size() == ptrsize))
errorScanfFormat(NULL, slice, e, "ptrdiff_t*", t);
break;
case Format_u: // pointer to unsigned int
if (!(t->ty == Tpointer && tnext->ty == Tuns32))
errorScanfFormat(NULL, slice, e, "uint*", t);
break;
case Format_hhu: // pointer to unsigned char
if (!(t->ty == Tpointer && tnext->ty == Tuns8))
errorScanfFormat(NULL, slice, e, "ubyte*", t);
break;
case Format_hu: // pointer to unsigned short int
if (!(t->ty == Tpointer && tnext->ty == Tuns16))
errorScanfFormat(NULL, slice, e, "ushort*", t);
break;
case Format_lu: // pointer to unsigned long int
if (!(t->ty == Tpointer && tnext->isintegral() && tnext->isunsigned() && tnext->size() == c_longsize))
errorScanfFormat(NULL, slice, e, (c_longsize == 4 ? "uint*" : "ulong*"), t);
break;
case Format_llu: // pointer to unsigned long long int
if (!(t->ty == Tpointer && tnext->ty == Tuns64))
errorScanfFormat(NULL, slice, e, "ulong*", t);
break;
case Format_ju: // pointer to uintmax_t
if (!(t->ty == Tpointer && tnext->ty == Tuns64))
errorScanfFormat(NULL, slice, e, "ulong*", t);
break;
case Format_g: // pointer to float
if (!(t->ty == Tpointer && tnext->ty == Tfloat32))
errorScanfFormat(NULL, slice, e, "float*", t);
break;
case Format_lg: // pointer to double
if (!(t->ty == Tpointer && tnext->ty == Tfloat64))
errorScanfFormat(NULL, slice, e, "double*", t);
break;
case Format_Lg: // pointer to long double
if (!(t->ty == Tpointer && tnext->ty == Tfloat80))
errorScanfFormat(NULL, slice, e, "real*", t);
break;
case Format_GNU_a:
case Format_GNU_m:
case Format_c:
case Format_s: // pointer to char string
if (!(t->ty == Tpointer && (tnext->ty == Tchar || tnext->ty == Tint8 || tnext->ty == Tuns8)))
errorScanfFormat(NULL, slice, e, "char*", t);
break;
case Format_lc:
case Format_ls: // pointer to wchar_t string
{
if (!(t->ty == Tpointer && tnext == target.c.twchar_t))
errorScanfFormat(NULL, slice, e, "wchar_t*", t);
break;
}
case Format_p: // double pointer
if (!(t->ty == Tpointer && tnext->ty == Tpointer))
errorScanfFormat(NULL, slice, e, "void**", t);
break;
case Format_error:
deprecation(loc, "format specifier `\"%.*s\"` is invalid", (int)slice.length, slice.ptr);
break;
case Format_percent:
default:
assert(0);
}
}
return false;
}