From 4dc9dc8551355cf081cd74a9842769d77a818f8a Mon Sep 17 00:00:00 2001 From: 0-v-0 Date: Wed, 1 Jul 2026 19:39:51 +0800 Subject: [PATCH] Fix #10712: std.format rejects valid format string with position before width --- std/format/spec.d | 24 ++++++++++++++++-------- std/format/write.d | 6 ++++++ 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/std/format/spec.d b/std/format/spec.d index 623fd06b7f2..c73091e091e 100644 --- a/std/format/spec.d +++ b/std/format/spec.d @@ -280,6 +280,7 @@ if (is(Unqual!Char == Char)) private void fillUp() scope { import std.format : enforceFmt, FormatException; + bool canBePosition = true; // Reset content if (__ctfe) @@ -368,19 +369,20 @@ if (is(Unqual!Char == Char)) // We practically found the format specifier trailing = trailing[j + 1 .. $]; return; - case '-': flDash = true; ++i; break; - case '+': flPlus = true; ++i; break; - case '=': flEqual = true; ++i; break; - case '#': flHash = true; ++i; break; - case '0': flZero = true; ++i; break; - case ' ': flSpace = true; ++i; break; + case '-': flDash = true; canBePosition = false; ++i; break; + case '+': flPlus = true; canBePosition = false; ++i; break; + case '=': flEqual = true; canBePosition = false; ++i; break; + case '#': flHash = true; canBePosition = false; ++i; break; + case '0': flZero = true; canBePosition = false; ++i; break; + case ' ': flSpace = true; canBePosition = false; ++i; break; case '*': + canBePosition = false; ++i; if (i < trailing.length && isDigit(trailing[i])) { // a '*' followed by digits and '$' is a // positional format - trailing = trailing[1 .. $]; + trailing = trailing[i .. $]; width = -parse!(typeof(width))(trailing); i = 0; enforceFmt(trailing[i++] == '$', @@ -400,12 +402,16 @@ if (is(Unqual!Char == Char)) i = trailing.length - tmp.length; if (tmp.startsWith('$')) { + enforceFmt(canBePosition, + text("Incorrect format specifier %", trailing[i .. $])); // index of the form %n$ indexEnd = indexStart = to!ubyte(widthOrArgIndex); ++i; } else if (tmp.startsWith(':')) { + enforceFmt(canBePosition, + text("Incorrect format specifier %", trailing[i .. $])); // two indexes of the form %m:n$, or one index of the form %m:$ indexStart = to!ubyte(widthOrArgIndex); tmp = tmp[1 .. $]; @@ -424,11 +430,13 @@ if (is(Unqual!Char == Char)) else { // width + canBePosition = false; width = to!int(widthOrArgIndex); } break; case ',': // Separator (default 3 digits) + canBePosition = false; ++i; flSeparator = true; @@ -459,6 +467,7 @@ if (is(Unqual!Char == Char)) break; case '.': // Precision + canBePosition = false; ++i; if (i < trailing.length && trailing[i] == '*') { @@ -985,4 +994,3 @@ void enforceValidFormatSpec(T, Char)(scope const ref FormatSpec!Char f) assert(collectExceptionMsg!FormatException(format("%.*,*?d", 5)) == "Missing separator digit width argument"); } - diff --git a/std/format/write.d b/std/format/write.d index 68a96d48625..3c7e85d8333 100644 --- a/std/format/write.d +++ b/std/format/write.d @@ -771,6 +771,12 @@ if (isSomeString!(typeof(fmt))) w.clear(); formattedWrite(w, "%s%s", 23, 45); assert(w.data == "2345", w.data); + + w.clear(); + formattedWrite(w, "%2$*1$d %3$s", 7, 23, "abc"); + assert(w.data == " 23 abc", w.data); + assert(collectExceptionMsg!FormatException(formattedWrite(w, "%*1$2$d %3$s", 7, 23, "abc")) + == "Incorrect format specifier %$d %3$s"); } // https://issues.dlang.org/show_bug.cgi?id=3479