| /** | |
| * Check the arguments to `printf` and `scanf` against the `format` string. | |
| * | |
| * Copyright: Copyright (C) 1999-2022 by The D Language Foundation, All Rights Reserved | |
| * Authors: $(LINK2 https://www.digitalmars.com, Walter Bright) | |
| * License: $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) | |
| * Source: $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/chkformat.d, _chkformat.d) | |
| * Documentation: https://dlang.org/phobos/dmd_chkformat.html | |
| * Coverage: https://codecov.io/gh/dlang/dmd/src/master/src/dmd/chkformat.d | |
| */ | |
| module dmd.chkformat; | |
| //import core.stdc.stdio : printf, scanf; | |
| import core.stdc.ctype : isdigit; | |
| import dmd.astenums; | |
| import dmd.cond; | |
| import dmd.errors; | |
| import dmd.expression; | |
| import dmd.globals; | |
| import dmd.identifier; | |
| import dmd.mtype; | |
| import dmd.target; | |
| /****************************************** | |
| * 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 | |
| * https://www.cplusplus.com/reference/cstdio/printf/ | |
| */ | |
| bool checkPrintfFormat(ref const Loc loc, scope const char[] format, scope Expression[] args, bool isVa_list) | |
| { | |
| //printf("checkPrintFormat('%.*s')\n", cast(int)format.length, format.ptr); | |
| size_t n, gnu_m_count; // index in args / number of Format.GNU_m | |
| for (size_t i = 0; i < format.length;) | |
| { | |
| if (format[i] != '%') | |
| { | |
| ++i; | |
| continue; | |
| } | |
| bool widthStar; | |
| bool precisionStar; | |
| size_t j = i; | |
| const fmt = parsePrintfFormatSpecifier(format, j, widthStar, precisionStar); | |
| const slice = format[i .. j]; | |
| 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", cast(int)slice.length, slice.ptr); | |
| continue; | |
| } | |
| if (fmt == Format.GNU_m) | |
| ++gnu_m_count; | |
| Expression getNextArg(ref bool skip) | |
| { | |
| if (n == args.length) | |
| { | |
| if (args.length < (n + 1) - gnu_m_count) | |
| deprecation(loc, "more format specifiers than %d arguments", cast(int)n); | |
| else | |
| skip = true; | |
| return null; | |
| } | |
| return args[n++]; | |
| } | |
| void errorMsg(const char* prefix, 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(), cast(int)slice.length, slice.ptr, texpect, tactual.toChars()); | |
| } | |
| if (widthStar) | |
| { | |
| bool skip; | |
| auto e = getNextArg(skip); | |
| if (skip) | |
| continue; | |
| if (!e) | |
| return true; | |
| auto t = e.type.toBasetype(); | |
| if (t.ty != Tint32 && t.ty != Tuns32) | |
| errorMsg("width ", e, "int", t); | |
| } | |
| if (precisionStar) | |
| { | |
| bool skip; | |
| auto e = getNextArg(skip); | |
| if (skip) | |
| continue; | |
| if (!e) | |
| return true; | |
| auto t = e.type.toBasetype(); | |
| if (t.ty != Tint32 && t.ty != Tuns32) | |
| errorMsg("precision ", e, "int", t); | |
| } | |
| bool skip; | |
| auto e = getNextArg(skip); | |
| if (skip) | |
| continue; | |
| if (!e) | |
| return true; | |
| auto t = e.type.toBasetype(); | |
| auto tnext = t.nextOf(); | |
| const c_longsize = target.c.longsize; | |
| const ptrsize = target.ptrsize; | |
| // Types which are promoted to int are allowed. | |
| // Spec: C99 6.5.2.2.7 | |
| final switch (fmt) | |
| { | |
| case Format.u: // unsigned int | |
| case Format.d: // int | |
| if (t.ty != Tint32 && t.ty != Tuns32) | |
| errorMsg(null, 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) | |
| errorMsg(null, 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) | |
| errorMsg(null, 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) | |
| errorMsg(null, e, (c_longsize == 4 ? "uint" : "ulong"), t); | |
| else | |
| errorMsg(null, 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) | |
| errorMsg(null, 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) | |
| errorMsg(null, e, "core.stdc.stdint.uintmax_t", t); | |
| else | |
| errorMsg(null, e, "core.stdc.stdint.intmax_t", t); | |
| } | |
| break; | |
| case Format.zd: // size_t | |
| if (!(t.isintegral() && t.size() == ptrsize)) | |
| errorMsg(null, e, "size_t", t); | |
| break; | |
| case Format.td: // ptrdiff_t | |
| if (!(t.isintegral() && t.size() == ptrsize)) | |
| errorMsg(null, 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) | |
| errorMsg(null, e, "double", t); | |
| break; | |
| case Format.Lg: // long double | |
| if (t.ty != Tfloat80 && t.ty != Timaginary80) | |
| errorMsg(null, e, "real", t); | |
| break; | |
| case Format.p: // pointer | |
| if (t.ty != Tpointer && t.ty != Tnull && t.ty != Tclass && t.ty != Tdelegate && t.ty != Taarray) | |
| errorMsg(null, e, "void*", t); | |
| break; | |
| case Format.n: // pointer to int | |
| if (!(t.ty == Tpointer && tnext.ty == Tint32)) | |
| errorMsg(null, e, "int*", t); | |
| break; | |
| case Format.ln: // pointer to long int | |
| if (!(t.ty == Tpointer && tnext.isintegral() && tnext.size() == c_longsize)) | |
| errorMsg(null, e, (c_longsize == 4 ? "int*" : "long*"), t); | |
| break; | |
| case Format.lln: // pointer to long long int | |
| if (!(t.ty == Tpointer && tnext.ty == Tint64)) | |
| errorMsg(null, e, "long*", t); | |
| break; | |
| case Format.hn: // pointer to short | |
| if (!(t.ty == Tpointer && tnext.ty == Tint16)) | |
| errorMsg(null, e, "short*", t); | |
| break; | |
| case Format.hhn: // pointer to signed char | |
| if (!(t.ty == Tpointer && tnext.ty == Tint16)) | |
| errorMsg(null, e, "byte*", t); | |
| break; | |
| case Format.jn: // pointer to intmax_t | |
| if (!(t.ty == Tpointer && tnext.ty == Tint64)) | |
| errorMsg(null, 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)) | |
| errorMsg(null, e, "size_t*", t); | |
| break; | |
| case Format.tn: // pointer to ptrdiff_t | |
| if (!(t.ty == Tpointer && tnext.isintegral() && !tnext.isunsigned() && tnext.size() == ptrsize)) | |
| errorMsg(null, e, "ptrdiff_t*", t); | |
| break; | |
| case Format.c: // char | |
| if (t.ty != Tint32 && t.ty != Tuns32) | |
| errorMsg(null, e, "char", t); | |
| break; | |
| case Format.lc: // wint_t | |
| if (t.ty != Tint32 && t.ty != Tuns32) | |
| errorMsg(null, 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))) | |
| errorMsg(null, e, "char*", t); | |
| break; | |
| case Format.ls: // pointer to wchar_t string | |
| if (!(t.ty == Tpointer && tnext.ty.isSomeChar && tnext.size() == target.c.wchar_tsize)) | |
| errorMsg(null, e, "wchar_t*", t); | |
| break; | |
| case Format.error: | |
| deprecation(loc, "format specifier `\"%.*s\"` is invalid", cast(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: | |
| assert(0); | |
| } | |
| } | |
| return false; | |
| } | |
| /****************************************** | |
| * 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 | |
| * https://www.cplusplus.com/reference/cstdio/scanf/ | |
| */ | |
| bool checkScanfFormat(ref const Loc loc, scope const char[] format, scope Expression[] args, bool isVa_list) | |
| { | |
| size_t n = 0; | |
| for (size_t i = 0; i < format.length;) | |
| { | |
| if (format[i] != '%') | |
| { | |
| ++i; | |
| continue; | |
| } | |
| bool asterisk; | |
| size_t j = i; | |
| const fmt = parseScanfFormatSpecifier(format, j, asterisk); | |
| const slice = format[i .. j]; | |
| 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", cast(int)slice.length, slice.ptr); | |
| continue; | |
| } | |
| Expression getNextArg() | |
| { | |
| if (n == args.length) | |
| { | |
| if (!asterisk) | |
| deprecation(loc, "more format specifiers than %d arguments", cast(int)n); | |
| return null; | |
| } | |
| return args[n++]; | |
| } | |
| void errorMsg(const char* prefix, 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(), cast(int)slice.length, slice.ptr, texpect, tactual.toChars()); | |
| } | |
| auto e = getNextArg(); | |
| if (!e) | |
| return true; | |
| auto t = e.type.toBasetype(); | |
| auto tnext = t.nextOf(); | |
| const c_longsize = target.c.longsize; | |
| const ptrsize = target.ptrsize; | |
| final switch (fmt) | |
| { | |
| case Format.n: | |
| case Format.d: // pointer to int | |
| if (!(t.ty == Tpointer && tnext.ty == Tint32)) | |
| errorMsg(null, e, "int*", t); | |
| break; | |
| case Format.hhn: | |
| case Format.hhd: // pointer to signed char | |
| if (!(t.ty == Tpointer && tnext.ty == Tint16)) | |
| errorMsg(null, e, "byte*", t); | |
| break; | |
| case Format.hn: | |
| case Format.hd: // pointer to short | |
| if (!(t.ty == Tpointer && tnext.ty == Tint16)) | |
| errorMsg(null, e, "short*", t); | |
| break; | |
| case Format.ln: | |
| case Format.ld: // pointer to long int | |
| if (!(t.ty == Tpointer && tnext.isintegral() && !tnext.isunsigned() && tnext.size() == c_longsize)) | |
| errorMsg(null, 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)) | |
| errorMsg(null, e, "long*", t); | |
| break; | |
| case Format.jn: | |
| case Format.jd: // pointer to intmax_t | |
| if (!(t.ty == Tpointer && tnext.ty == Tint64)) | |
| errorMsg(null, 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)) | |
| errorMsg(null, 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)) | |
| errorMsg(null, e, "ptrdiff_t*", t); | |
| break; | |
| case Format.u: // pointer to unsigned int | |
| if (!(t.ty == Tpointer && tnext.ty == Tuns32)) | |
| errorMsg(null, e, "uint*", t); | |
| break; | |
| case Format.hhu: // pointer to unsigned char | |
| if (!(t.ty == Tpointer && tnext.ty == Tuns8)) | |
| errorMsg(null, e, "ubyte*", t); | |
| break; | |
| case Format.hu: // pointer to unsigned short int | |
| if (!(t.ty == Tpointer && tnext.ty == Tuns16)) | |
| errorMsg(null, e, "ushort*", t); | |
| break; | |
| case Format.lu: // pointer to unsigned long int | |
| if (!(t.ty == Tpointer && tnext.isintegral() && tnext.isunsigned() && tnext.size() == c_longsize)) | |
| errorMsg(null, e, (c_longsize == 4 ? "uint*" : "ulong*"), t); | |
| break; | |
| case Format.llu: // pointer to unsigned long long int | |
| if (!(t.ty == Tpointer && tnext.ty == Tuns64)) | |
| errorMsg(null, e, "ulong*", t); | |
| break; | |
| case Format.ju: // pointer to uintmax_t | |
| if (!(t.ty == Tpointer && tnext.ty == Tuns64)) | |
| errorMsg(null, e, "core.stdc.stdint.uintmax_t*", t); | |
| break; | |
| case Format.g: // pointer to float | |
| if (!(t.ty == Tpointer && tnext.ty == Tfloat32)) | |
| errorMsg(null, e, "float*", t); | |
| break; | |
| case Format.lg: // pointer to double | |
| if (!(t.ty == Tpointer && tnext.ty == Tfloat64)) | |
| errorMsg(null, e, "double*", t); | |
| break; | |
| case Format.Lg: // pointer to long double | |
| if (!(t.ty == Tpointer && tnext.ty == Tfloat80)) | |
| errorMsg(null, 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))) | |
| errorMsg(null, e, "char*", t); | |
| break; | |
| case Format.lc: | |
| case Format.ls: // pointer to wchar_t string | |
| if (!(t.ty == Tpointer && tnext.ty.isSomeChar && tnext.size() == target.c.wchar_tsize)) | |
| errorMsg(null, e, "wchar_t*", t); | |
| break; | |
| case Format.p: // double pointer | |
| if (!(t.ty == Tpointer && tnext.ty == Tpointer)) | |
| errorMsg(null, e, "void**", t); | |
| break; | |
| case Format.error: | |
| deprecation(loc, "format specifier `\"%.*s\"` is invalid", cast(int)slice.length, slice.ptr); | |
| break; | |
| case Format.percent: | |
| assert(0); | |
| } | |
| } | |
| return false; | |
| } | |
| private: | |
| /************************************** | |
| * 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 | |
| */ | |
| Format parseScanfFormatSpecifier(scope const char[] format, ref size_t idx, | |
| out bool asterisk) nothrow pure @safe | |
| { | |
| auto i = idx; | |
| assert(format[i] == '%'); | |
| const length = format.length; | |
| Format error() | |
| { | |
| idx = i; | |
| return Format.error; | |
| } | |
| ++i; | |
| if (i == length) | |
| return error(); | |
| if (format[i] == '%') | |
| { | |
| idx = i + 1; | |
| return Format.percent; | |
| } | |
| // * sub-specifier | |
| if (format[i] == '*') | |
| { | |
| ++i; | |
| if (i == length) | |
| return error(); | |
| asterisk = true; | |
| } | |
| // fieldWidth | |
| while (isdigit(format[i])) | |
| { | |
| i++; | |
| if (i == length) | |
| return error(); | |
| } | |
| /* 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 error(); | |
| ++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 error(); | |
| } | |
| /* Read the specifier | |
| */ | |
| char genSpec; | |
| Format specifier = parseGenericFormatSpecifier(format, i, genSpec); | |
| if (specifier == Format.error) | |
| return error(); | |
| 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 | |
| */ | |
| Format parsePrintfFormatSpecifier(scope const char[] format, ref size_t idx, | |
| out bool widthStar, out bool precisionStar) nothrow pure @safe | |
| { | |
| auto i = idx; | |
| assert(format[i] == '%'); | |
| const length = format.length; | |
| bool hash; | |
| bool zero; | |
| bool flags; | |
| bool width; | |
| bool precision; | |
| Format error() | |
| { | |
| idx = i; | |
| return Format.error; | |
| } | |
| ++i; | |
| if (i == length) | |
| return error(); | |
| if (format[i] == '%') | |
| { | |
| idx = i + 1; | |
| return Format.percent; | |
| } | |
| /* Read the `flags` | |
| */ | |
| while (1) | |
| { | |
| const 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 error(); | |
| } | |
| /* Read the `field width` | |
| */ | |
| { | |
| const c = format[i]; | |
| if (c == '*') | |
| { | |
| width = true; | |
| widthStar = true; | |
| ++i; | |
| if (i == length) | |
| return error(); | |
| } | |
| else if ('1' <= c && c <= '9') | |
| { | |
| width = true; | |
| ++i; | |
| if (i == length) | |
| return error(); | |
| while ('0' <= format[i] && format[i] <= '9') | |
| { | |
| ++i; | |
| if (i == length) | |
| return error(); | |
| } | |
| } | |
| } | |
| /* Read the `precision` | |
| */ | |
| if (format[i] == '.') | |
| { | |
| precision = true; | |
| ++i; | |
| if (i == length) | |
| return error(); | |
| const c = format[i]; | |
| if (c == '*') | |
| { | |
| precisionStar = true; | |
| ++i; | |
| if (i == length) | |
| return error(); | |
| } | |
| else if ('0' <= c && c <= '9') | |
| { | |
| ++i; | |
| if (i == length) | |
| return error(); | |
| while ('0' <= format[i] && format[i] <= '9') | |
| { | |
| ++i; | |
| if (i == length) | |
| return error(); | |
| } | |
| } | |
| } | |
| /* Read the specifier | |
| */ | |
| char genSpec; | |
| Format specifier = parseGenericFormatSpecifier(format, i, genSpec); | |
| if (specifier == Format.error) | |
| return error(); | |
| switch (genSpec) | |
| { | |
| case 'c': | |
| case 's': | |
| if (hash || zero) | |
| return error(); | |
| break; | |
| case 'd': | |
| case 'i': | |
| if (hash) | |
| return error(); | |
| break; | |
| case 'n': | |
| if (hash || zero || precision || width || flags) | |
| return error(); | |
| break; | |
| default: | |
| break; | |
| } | |
| idx = i; | |
| return specifier; // success | |
| } | |
| /* 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 | |
| { | |
| d, // int | |
| hhd, // signed char | |
| hd, // short int | |
| ld, // long int | |
| lld, // long long int | |
| jd, // intmax_t | |
| zd, // size_t | |
| td, // ptrdiff_t | |
| u, // unsigned int | |
| hhu, // unsigned char | |
| hu, // unsigned short int | |
| lu, // unsigned long int | |
| llu, // unsigned long long int | |
| ju, // uintmax_t | |
| g, // float (scanf) / double (printf) | |
| lg, // double (scanf) | |
| Lg, // long double (both) | |
| s, // char string (both) | |
| ls, // wchar_t string (both) | |
| c, // char (printf) | |
| lc, // wint_t (printf) | |
| p, // pointer | |
| n, // pointer to int | |
| hhn, // pointer to signed char | |
| hn, // pointer to short | |
| ln, // pointer to long int | |
| lln, // pointer to long long int | |
| jn, // pointer to intmax_t | |
| zn, // pointer to size_t | |
| tn, // pointer to ptrdiff_t | |
| GNU_a, // GNU ext. : address to a string with no maximum size (scanf) | |
| GNU_m, // GNU ext. : string corresponding to the error code in errno (printf) / length modifier (scanf) | |
| percent, // %% (i.e. no argument) | |
| 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 | |
| */ | |
| Format parseGenericFormatSpecifier(scope const char[] format, | |
| ref size_t idx, out char genSpecifier, bool useGNUExts = | |
| findCondition(global.versionids, Identifier.idPool("CRuntime_Glibc"))) nothrow pure @trusted | |
| { | |
| const length = format.length; | |
| /* Read the `length modifier` | |
| */ | |
| const lm = format[idx]; | |
| bool lm1; // if jztL | |
| bool lm2; // 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 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; | |
| } | |
| goto case; | |
| 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) | |
| { | |
| // https://www.gnu.org/software/libc/manual/html_node/Other-Output-Conversions.html | |
| specifier = Format.GNU_m; | |
| break; | |
| } | |
| goto default; | |
| default: | |
| specifier = Format.error; | |
| break; | |
| } | |
| ++idx; | |
| return specifier; // success | |
| } | |
| unittest | |
| { | |
| /* parseGenericFormatSpecifier | |
| */ | |
| char genSpecifier; | |
| size_t idx; | |
| assert(parseGenericFormatSpecifier("hhd", idx, genSpecifier) == Format.hhd); | |
| assert(genSpecifier == 'd'); | |
| idx = 0; | |
| assert(parseGenericFormatSpecifier("hn", idx, genSpecifier) == Format.hn); | |
| assert(genSpecifier == 'n'); | |
| idx = 0; | |
| assert(parseGenericFormatSpecifier("ji", idx, genSpecifier) == Format.jd); | |
| assert(genSpecifier == 'i'); | |
| idx = 0; | |
| assert(parseGenericFormatSpecifier("lu", idx, genSpecifier) == Format.lu); | |
| assert(genSpecifier == 'u'); | |
| idx = 0; | |
| assert(parseGenericFormatSpecifier("k", idx, genSpecifier) == Format.error); | |
| /* parsePrintfFormatSpecifier | |
| */ | |
| bool widthStar; | |
| bool precisionStar; | |
| // one for each Format | |
| idx = 0; | |
| assert(parsePrintfFormatSpecifier("%d", idx, widthStar, precisionStar) == Format.d); | |
| assert(idx == 2); | |
| assert(!widthStar && !precisionStar); | |
| idx = 0; | |
| assert(parsePrintfFormatSpecifier("%ld", idx, widthStar, precisionStar) == Format.ld); | |
| assert(idx == 3); | |
| idx = 0; | |
| assert(parsePrintfFormatSpecifier("%lld", idx, widthStar, precisionStar) == Format.lld); | |
| assert(idx == 4); | |
| idx = 0; | |
| assert(parsePrintfFormatSpecifier("%jd", idx, widthStar, precisionStar) == Format.jd); | |
| assert(idx == 3); | |
| idx = 0; | |
| assert(parsePrintfFormatSpecifier("%zd", idx, widthStar, precisionStar) == Format.zd); | |
| assert(idx == 3); | |
| idx = 0; | |
| assert(parsePrintfFormatSpecifier("%td", idx, widthStar, precisionStar) == Format.td); | |
| assert(idx == 3); | |
| idx = 0; | |
| assert(parsePrintfFormatSpecifier("%g", idx, widthStar, precisionStar) == Format.g); | |
| assert(idx == 2); | |
| idx = 0; | |
| assert(parsePrintfFormatSpecifier("%Lg", idx, widthStar, precisionStar) == Format.Lg); | |
| assert(idx == 3); | |
| idx = 0; | |
| assert(parsePrintfFormatSpecifier("%p", idx, widthStar, precisionStar) == Format.p); | |
| assert(idx == 2); | |
| idx = 0; | |
| assert(parsePrintfFormatSpecifier("%n", idx, widthStar, precisionStar) == Format.n); | |
| assert(idx == 2); | |
| idx = 0; | |
| assert(parsePrintfFormatSpecifier("%ln", idx, widthStar, precisionStar) == Format.ln); | |
| assert(idx == 3); | |
| idx = 0; | |
| assert(parsePrintfFormatSpecifier("%lln", idx, widthStar, precisionStar) == Format.lln); | |
| assert(idx == 4); | |
| idx = 0; | |
| assert(parsePrintfFormatSpecifier("%hn", idx, widthStar, precisionStar) == Format.hn); | |
| assert(idx == 3); | |
| idx = 0; | |
| assert(parsePrintfFormatSpecifier("%hhn", idx, widthStar, precisionStar) == Format.hhn); | |
| assert(idx == 4); | |
| idx = 0; | |
| assert(parsePrintfFormatSpecifier("%jn", idx, widthStar, precisionStar) == Format.jn); | |
| assert(idx == 3); | |
| idx = 0; | |
| assert(parsePrintfFormatSpecifier("%zn", idx, widthStar, precisionStar) == Format.zn); | |
| assert(idx == 3); | |
| idx = 0; | |
| assert(parsePrintfFormatSpecifier("%tn", idx, widthStar, precisionStar) == Format.tn); | |
| assert(idx == 3); | |
| idx = 0; | |
| assert(parsePrintfFormatSpecifier("%c", idx, widthStar, precisionStar) == Format.c); | |
| assert(idx == 2); | |
| idx = 0; | |
| assert(parsePrintfFormatSpecifier("%lc", idx, widthStar, precisionStar) == Format.lc); | |
| assert(idx == 3); | |
| idx = 0; | |
| assert(parsePrintfFormatSpecifier("%s", idx, widthStar, precisionStar) == Format.s); | |
| assert(idx == 2); | |
| idx = 0; | |
| assert(parsePrintfFormatSpecifier("%ls", idx, widthStar, precisionStar) == Format.ls); | |
| assert(idx == 3); | |
| idx = 0; | |
| assert(parsePrintfFormatSpecifier("%%", idx, widthStar, precisionStar) == Format.percent); | |
| assert(idx == 2); | |
| // Synonyms | |
| idx = 0; | |
| assert(parsePrintfFormatSpecifier("%i", idx, widthStar, precisionStar) == Format.d); | |
| assert(idx == 2); | |
| idx = 0; | |
| assert(parsePrintfFormatSpecifier("%u", idx, widthStar, precisionStar) == Format.u); | |
| assert(idx == 2); | |
| idx = 0; | |
| assert(parsePrintfFormatSpecifier("%o", idx, widthStar, precisionStar) == Format.u); | |
| assert(idx == 2); | |
| idx = 0; | |
| assert(parsePrintfFormatSpecifier("%x", idx, widthStar, precisionStar) == Format.u); | |
| assert(idx == 2); | |
| idx = 0; | |
| assert(parsePrintfFormatSpecifier("%X", idx, widthStar, precisionStar) == Format.u); | |
| assert(idx == 2); | |
| idx = 0; | |
| assert(parsePrintfFormatSpecifier("%f", idx, widthStar, precisionStar) == Format.g); | |
| assert(idx == 2); | |
| idx = 0; | |
| assert(parsePrintfFormatSpecifier("%F", idx, widthStar, precisionStar) == Format.g); | |
| assert(idx == 2); | |
| idx = 0; | |
| assert(parsePrintfFormatSpecifier("%G", idx, widthStar, precisionStar) == Format.g); | |
| assert(idx == 2); | |
| idx = 0; | |
| Format g = parsePrintfFormatSpecifier("%a", idx, widthStar, precisionStar); | |
| assert(g == Format.g || g == Format.GNU_a); | |
| assert(idx == 2); | |
| idx = 0; | |
| assert(parsePrintfFormatSpecifier("%A", idx, widthStar, precisionStar) == Format.g); | |
| assert(idx == 2); | |
| idx = 0; | |
| assert(parsePrintfFormatSpecifier("%lg", idx, widthStar, precisionStar) == Format.lg); | |
| assert(idx == 3); | |
| // width, precision | |
| idx = 0; | |
| assert(parsePrintfFormatSpecifier("%*d", idx, widthStar, precisionStar) == Format.d); | |
| assert(idx == 3); | |
| assert(widthStar && !precisionStar); | |
| idx = 0; | |
| assert(parsePrintfFormatSpecifier("%.*d", idx, widthStar, precisionStar) == Format.d); | |
| assert(idx == 4); | |
| assert(!widthStar && precisionStar); | |
| idx = 0; | |
| assert(parsePrintfFormatSpecifier("%*.*d", idx, widthStar, precisionStar) == Format.d); | |
| assert(idx == 5); | |
| assert(widthStar && precisionStar); | |
| // Too short formats | |
| { | |
| foreach (s; ["%", "%-", "%+", "% ", "%#", "%0", "%*", "%1", "%19", "%.", "%.*", "%.1", "%.12", | |
| "%j", "%z", "%t", "%l", "%h", "%ll", "%hh"]) | |
| { | |
| idx = 0; | |
| assert(parsePrintfFormatSpecifier(s, idx, widthStar, precisionStar) == Format.error); | |
| assert(idx == s.length); | |
| } | |
| } | |
| // Undefined format combinations | |
| { | |
| foreach (s; ["%#d", "%llg", "%jg", "%zg", "%tg", "%hg", "%hhg", | |
| "%#c", "%0c", "%jc", "%zc", "%tc", "%Lc", "%hc", "%hhc", "%llc", | |
| "%#s", "%0s", "%js", "%zs", "%ts", "%Ls", "%hs", "%hhs", "%lls", | |
| "%jp", "%zp", "%tp", "%Lp", "%hp", "%lp", "%hhp", "%llp", | |
| "%-n", "%+n", "% n", "%#n", "%0n", "%*n", "%1n", "%19n", "%.n", "%.*n", "%.1n", "%.12n", "%Ln", "%K"]) | |
| { | |
| idx = 0; | |
| assert(parsePrintfFormatSpecifier(s, idx, widthStar, precisionStar) == Format.error); | |
| assert(idx == s.length); | |
| } | |
| } | |
| /* parseScanfFormatSpecifier | |
| */ | |
| bool asterisk; | |
| // one for each Format | |
| idx = 0; | |
| assert(parseScanfFormatSpecifier("%d", idx, asterisk) == Format.d); | |
| assert(idx == 2); | |
| assert(!asterisk); | |
| idx = 0; | |
| assert(parseScanfFormatSpecifier("%hhd", idx, asterisk) == Format.hhd); | |
| assert(idx == 4); | |
| idx = 0; | |
| assert(parseScanfFormatSpecifier("%hd", idx, asterisk) == Format.hd); | |
| assert(idx == 3); | |
| idx = 0; | |
| assert(parseScanfFormatSpecifier("%ld", idx, asterisk) == Format.ld); | |
| assert(idx == 3); | |
| idx = 0; | |
| assert(parseScanfFormatSpecifier("%lld", idx, asterisk) == Format.lld); | |
| assert(idx == 4); | |
| idx = 0; | |
| assert(parseScanfFormatSpecifier("%jd", idx, asterisk) == Format.jd); | |
| assert(idx == 3); | |
| idx = 0; | |
| assert(parseScanfFormatSpecifier("%zd", idx, asterisk) == Format.zd); | |
| assert(idx == 3); | |
| idx = 0; | |
| assert(parseScanfFormatSpecifier("%td", idx, asterisk,) == Format.td); | |
| assert(idx == 3); | |
| idx = 0; | |
| assert(parseScanfFormatSpecifier("%u", idx, asterisk) == Format.u); | |
| assert(idx == 2); | |
| idx = 0; | |
| assert(parseScanfFormatSpecifier("%hhu", idx, asterisk,) == Format.hhu); | |
| assert(idx == 4); | |
| idx = 0; | |
| assert(parseScanfFormatSpecifier("%hu", idx, asterisk) == Format.hu); | |
| assert(idx == 3); | |
| idx = 0; | |
| assert(parseScanfFormatSpecifier("%lu", idx, asterisk) == Format.lu); | |
| assert(idx == 3); | |
| idx = 0; | |
| assert(parseScanfFormatSpecifier("%llu", idx, asterisk) == Format.llu); | |
| assert(idx == 4); | |
| idx = 0; | |
| assert(parseScanfFormatSpecifier("%ju", idx, asterisk) == Format.ju); | |
| assert(idx == 3); | |
| idx = 0; | |
| assert(parseScanfFormatSpecifier("%g", idx, asterisk) == Format.g); | |
| assert(idx == 2); | |
| idx = 0; | |
| assert(parseScanfFormatSpecifier("%lg", idx, asterisk) == Format.lg); | |
| assert(idx == 3); | |
| idx = 0; | |
| assert(parseScanfFormatSpecifier("%Lg", idx, asterisk) == Format.Lg); | |
| assert(idx == 3); | |
| idx = 0; | |
| assert(parseScanfFormatSpecifier("%p", idx, asterisk) == Format.p); | |
| assert(idx == 2); | |
| idx = 0; | |
| assert(parseScanfFormatSpecifier("%s", idx, asterisk) == Format.s); | |
| assert(idx == 2); | |
| idx = 0; | |
| assert(parseScanfFormatSpecifier("%ls", idx, asterisk,) == Format.ls); | |
| assert(idx == 3); | |
| idx = 0; | |
| assert(parseScanfFormatSpecifier("%%", idx, asterisk) == Format.percent); | |
| assert(idx == 2); | |
| // Synonyms | |
| idx = 0; | |
| assert(parseScanfFormatSpecifier("%i", idx, asterisk) == Format.d); | |
| assert(idx == 2); | |
| idx = 0; | |
| assert(parseScanfFormatSpecifier("%n", idx, asterisk) == Format.n); | |
| assert(idx == 2); | |
| idx = 0; | |
| assert(parseScanfFormatSpecifier("%o", idx, asterisk) == Format.u); | |
| assert(idx == 2); | |
| idx = 0; | |
| assert(parseScanfFormatSpecifier("%x", idx, asterisk) == Format.u); | |
| assert(idx == 2); | |
| idx = 0; | |
| assert(parseScanfFormatSpecifier("%f", idx, asterisk) == Format.g); | |
| assert(idx == 2); | |
| idx = 0; | |
| assert(parseScanfFormatSpecifier("%e", idx, asterisk) == Format.g); | |
| assert(idx == 2); | |
| idx = 0; | |
| g = parseScanfFormatSpecifier("%a", idx, asterisk); | |
| assert(g == Format.g || g == Format.GNU_a); | |
| assert(idx == 2); | |
| idx = 0; | |
| assert(parseScanfFormatSpecifier("%c", idx, asterisk) == Format.c); | |
| assert(idx == 2); | |
| // asterisk | |
| idx = 0; | |
| assert(parseScanfFormatSpecifier("%*d", idx, asterisk) == Format.d); | |
| assert(idx == 3); | |
| assert(asterisk); | |
| idx = 0; | |
| assert(parseScanfFormatSpecifier("%9ld", idx, asterisk) == Format.ld); | |
| assert(idx == 4); | |
| assert(!asterisk); | |
| idx = 0; | |
| assert(parseScanfFormatSpecifier("%*25984hhd", idx, asterisk) == Format.hhd); | |
| assert(idx == 10); | |
| assert(asterisk); | |
| // scansets | |
| idx = 0; | |
| assert(parseScanfFormatSpecifier("%[a-zA-Z]s", idx, asterisk) == Format.s); | |
| assert(idx == 10); | |
| assert(!asterisk); | |
| idx = 0; | |
| assert(parseScanfFormatSpecifier("%*25[a-z]hhd", idx, asterisk) == Format.hhd); | |
| assert(idx == 12); | |
| assert(asterisk); | |
| // Too short formats | |
| foreach (s; ["%", "% ", "%#", "%0", "%*", "%1", "%19", | |
| "%j", "%z", "%t", "%l", "%h", "%ll", "%hh", "%K"]) | |
| { | |
| idx = 0; | |
| assert(parseScanfFormatSpecifier(s, idx, asterisk) == Format.error); | |
| assert(idx == s.length); | |
| } | |
| // Undefined format combinations | |
| foreach (s; ["%Ld", "%llg", "%jg", "%zg", "%tg", "%hg", "%hhg", | |
| "%jc", "%zc", "%tc", "%Lc", "%hc", "%hhc", "%llc", | |
| "%jp", "%zp", "%tp", "%Lp", "%hp", "%lp", "%hhp", "%llp", | |
| "%-", "%+", "%#", "%0", "%.", "%Ln"]) | |
| { | |
| idx = 0; | |
| assert(parseScanfFormatSpecifier(s, idx, asterisk) == Format.error); | |
| assert(idx == s.length); | |
| } | |
| // Invalid scansets | |
| foreach (s; ["%[]", "%[s", "%[0-9lld", "%[", "%[a-z]"]) | |
| { | |
| idx = 0; | |
| assert(parseScanfFormatSpecifier(s, idx, asterisk) == Format.error); | |
| assert(idx == s.length); | |
| } | |
| } |