Skip to content

Commit 9ae8a03

Browse files
committed
[Support] Detect invalid formatv() calls
- Detect formatv() calls where the number of replacement parameters expected after parsing the format string does not match the number provides in the formatv() call. - assert() in debug builds, and fail the formatv() call in release builds by just emitting an error message in the stream.
1 parent 34dee0a commit 9ae8a03

File tree

15 files changed

+253
-149
lines changed

15 files changed

+253
-149
lines changed

clang/lib/StaticAnalyzer/Checkers/CheckPlacementNew.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ bool PlacementNewChecker::checkPlaceCapacityIsSufficient(
131131
"Storage provided to placement new is only {0} bytes, "
132132
"whereas the allocated array type requires more space for "
133133
"internal needs",
134-
SizeOfPlaceCI->getValue(), SizeOfTargetCI->getValue()));
134+
SizeOfPlaceCI->getValue()));
135135
else
136136
Msg = std::string(llvm::formatv(
137137
"Storage provided to placement new is only {0} bytes, "

clang/lib/StaticAnalyzer/Checkers/StdLibraryFunctionsChecker.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1401,7 +1401,7 @@ void StdLibraryFunctionsChecker::checkPostCall(const CallEvent &Call,
14011401
ErrnoNote =
14021402
llvm::formatv("After calling '{0}' {1}", FunctionName, ErrnoNote);
14031403
} else {
1404-
CaseNote = llvm::formatv(Case.getNote().str().c_str(), FunctionName);
1404+
CaseNote = llvm::formatvv(Case.getNote().str().c_str(), FunctionName);
14051405
}
14061406
const SVal RV = Call.getReturnValue();
14071407

lldb/source/Plugins/SymbolFile/DWARF/DWARFUnit.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -655,7 +655,7 @@ DWARFUnit::GetDIE(dw_offset_t die_offset) {
655655

656656
if (!ContainsDIEOffset(die_offset)) {
657657
GetSymbolFileDWARF().GetObjectFile()->GetModule()->ReportError(
658-
"GetDIE for DIE {0:x16} is outside of its CU {0:x16}", die_offset,
658+
"GetDIE for DIE {0:x16} is outside of its CU {1:x16}", die_offset,
659659
GetOffset());
660660
return DWARFDIE(); // Not found
661661
}

llvm/include/llvm/Support/FormatVariadic.h

Lines changed: 37 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -66,24 +66,24 @@ struct ReplacementItem {
6666
class formatv_object_base {
6767
protected:
6868
StringRef Fmt;
69+
bool Validate;
6970
ArrayRef<support::detail::format_adapter *> Adapters;
7071

71-
static bool consumeFieldLayout(StringRef &Spec, AlignStyle &Where,
72-
size_t &Align, char &Pad);
73-
74-
static std::pair<ReplacementItem, StringRef>
75-
splitLiteralAndReplacement(StringRef Fmt);
76-
77-
formatv_object_base(StringRef Fmt,
72+
formatv_object_base(StringRef Fmt, bool Validate,
7873
ArrayRef<support::detail::format_adapter *> Adapters)
79-
: Fmt(Fmt), Adapters(Adapters) {}
74+
: Fmt(Fmt), Validate(Validate), Adapters(Adapters) {}
8075

8176
formatv_object_base(formatv_object_base const &rhs) = delete;
8277
formatv_object_base(formatv_object_base &&rhs) = default;
8378

8479
public:
8580
void format(raw_ostream &S) const {
86-
for (auto &R : parseFormatString(Fmt)) {
81+
const auto [Replacements, IsValid] =
82+
parseFormatString(S, Fmt, Adapters.size(), Validate);
83+
if (!IsValid)
84+
return;
85+
86+
for (const auto &R : Replacements) {
8787
if (R.Type == ReplacementType::Empty)
8888
continue;
8989
if (R.Type == ReplacementType::Literal) {
@@ -101,9 +101,13 @@ class formatv_object_base {
101101
Align.format(S, R.Options);
102102
}
103103
}
104-
static SmallVector<ReplacementItem, 2> parseFormatString(StringRef Fmt);
105104

106-
static std::optional<ReplacementItem> parseReplacementItem(StringRef Spec);
105+
// Parse format string and return the array of replacement items. If there is
106+
// an error in the format string, return false for the second member of the
107+
// pair, and print the error message to `S`.
108+
static std::pair<SmallVector<ReplacementItem, 2>, bool>
109+
parseFormatString(raw_ostream &S, StringRef Fmt, size_t NumArgs,
110+
bool Validate);
107111

108112
std::string str() const {
109113
std::string Result;
@@ -149,8 +153,8 @@ template <typename Tuple> class formatv_object : public formatv_object_base {
149153
};
150154

151155
public:
152-
formatv_object(StringRef Fmt, Tuple &&Params)
153-
: formatv_object_base(Fmt, ParameterPointers),
156+
formatv_object(StringRef Fmt, bool ValidateNumArgs, Tuple &&Params)
157+
: formatv_object_base(Fmt, ValidateNumArgs, ParameterPointers),
154158
Parameters(std::move(Params)) {
155159
ParameterPointers = std::apply(create_adapters(), Parameters);
156160
}
@@ -174,7 +178,7 @@ template <typename Tuple> class formatv_object : public formatv_object_base {
174178
//
175179
// rep_field ::= "{" index ["," layout] [":" format] "}"
176180
// index ::= <non-negative integer>
177-
// layout ::= [[[char]loc]width]
181+
// layout ::= [[[pad]loc]width]
178182
// format ::= <any string not containing "{" or "}">
179183
// char ::= <any character except "{" or "}">
180184
// loc ::= "-" | "=" | "+"
@@ -187,7 +191,7 @@ template <typename Tuple> class formatv_object : public formatv_object_base {
187191
// format - A type-dependent string used to provide additional options to
188192
// the formatting operation. Refer to the documentation of the
189193
// various individual format providers for per-type options.
190-
// char - The padding character. Defaults to ' ' (space). Only valid if
194+
// pad - The padding character. Defaults to ' ' (space). Only valid if
191195
// `loc` is also specified.
192196
// loc - Where to print the formatted text within the field. Only valid if
193197
// `width` is also specified.
@@ -247,15 +251,31 @@ template <typename Tuple> class formatv_object : public formatv_object_base {
247251
// assertion. Otherwise, it will try to do something reasonable, but in general
248252
// the details of what that is are undefined.
249253
//
254+
255+
// formatv() with validation enabled.
250256
template <typename... Ts>
251257
inline auto formatv(const char *Fmt, Ts &&...Vals)
252258
-> formatv_object<decltype(std::make_tuple(
253259
support::detail::build_format_adapter(std::forward<Ts>(Vals))...))> {
254260
using ParamTuple = decltype(std::make_tuple(
255261
support::detail::build_format_adapter(std::forward<Ts>(Vals))...));
256262
return formatv_object<ParamTuple>(
257-
Fmt, std::make_tuple(support::detail::build_format_adapter(
258-
std::forward<Ts>(Vals))...));
263+
Fmt, /*Validate=*/true,
264+
std::make_tuple(
265+
support::detail::build_format_adapter(std::forward<Ts>(Vals))...));
266+
}
267+
268+
// formatvv() perform no argument/format string validation.
269+
template <typename... Ts>
270+
inline auto formatvv(const char *Fmt, Ts &&...Vals)
271+
-> formatv_object<decltype(std::make_tuple(
272+
support::detail::build_format_adapter(std::forward<Ts>(Vals))...))> {
273+
using ParamTuple = decltype(std::make_tuple(
274+
support::detail::build_format_adapter(std::forward<Ts>(Vals))...));
275+
return formatv_object<ParamTuple>(
276+
Fmt, /*Validate=*/false,
277+
std::make_tuple(
278+
support::detail::build_format_adapter(std::forward<Ts>(Vals))...));
259279
}
260280

261281
} // end namespace llvm

llvm/lib/DebugInfo/DWARF/DWARFVerifier.cpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1518,8 +1518,7 @@ DWARFVerifier::verifyNameIndexAbbrevs(const DWARFDebugNames::NameIndex &NI) {
15181518
error() << formatv("NameIndex @ {0:x}: Indexing multiple compile units "
15191519
"and abbreviation {1:x} has no DW_IDX_compile_unit "
15201520
"or DW_IDX_type_unit attribute.\n",
1521-
NI.getUnitOffset(), Abbrev.Code,
1522-
dwarf::DW_IDX_compile_unit);
1521+
NI.getUnitOffset(), Abbrev.Code);
15231522
});
15241523
++NumErrors;
15251524
}

llvm/lib/ExecutionEngine/Orc/CompileOnDemandLayer.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -348,10 +348,10 @@ void CompileOnDemandLayer::emitPartition(
348348
HC = hash_combine(HC, hash_combine_range(GVName.begin(), GVName.end()));
349349
}
350350
raw_string_ostream(SubModuleName)
351-
<< ".submodule."
352-
<< formatv(sizeof(size_t) == 8 ? "{0:x16}" : "{0:x8}",
353-
static_cast<size_t>(HC))
354-
<< ".ll";
351+
<< ".submodule."
352+
<< formatv(sizeof(size_t) == 8 ? "{0:x16}" : "{0:x8}",
353+
static_cast<size_t>(HC))
354+
<< ".ll";
355355
}
356356

357357
// Extract the requested partiton (plus any necessary aliases) and

llvm/lib/Support/FormatVariadic.cpp

Lines changed: 102 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@
66
//===----------------------------------------------------------------------===//
77

88
#include "llvm/Support/FormatVariadic.h"
9+
#include "llvm/ADT/SmallSet.h"
910
#include <cassert>
1011
#include <optional>
12+
#include <variant>
1113

1214
using namespace llvm;
1315

@@ -25,8 +27,8 @@ static std::optional<AlignStyle> translateLocChar(char C) {
2527
LLVM_BUILTIN_UNREACHABLE;
2628
}
2729

28-
bool formatv_object_base::consumeFieldLayout(StringRef &Spec, AlignStyle &Where,
29-
size_t &Align, char &Pad) {
30+
static bool consumeFieldLayout(StringRef &Spec, AlignStyle &Where,
31+
size_t &Align, char &Pad) {
3032
Where = AlignStyle::Right;
3133
Align = 0;
3234
Pad = ' ';
@@ -35,8 +37,7 @@ bool formatv_object_base::consumeFieldLayout(StringRef &Spec, AlignStyle &Where,
3537

3638
if (Spec.size() > 1) {
3739
// A maximum of 2 characters at the beginning can be used for something
38-
// other
39-
// than the width.
40+
// other than the width.
4041
// If Spec[1] is a loc char, then Spec[0] is a pad char and Spec[2:...]
4142
// contains the width.
4243
// Otherwise, if Spec[0] is a loc char, then Spec[1:...] contains the width.
@@ -55,8 +56,8 @@ bool formatv_object_base::consumeFieldLayout(StringRef &Spec, AlignStyle &Where,
5556
return !Failed;
5657
}
5758

58-
std::optional<ReplacementItem>
59-
formatv_object_base::parseReplacementItem(StringRef Spec) {
59+
static std::variant<ReplacementItem, StringRef>
60+
parseReplacementItem(StringRef Spec) {
6061
StringRef RepString = Spec.trim("{}");
6162

6263
// If the replacement sequence does not start with a non-negative integer,
@@ -67,92 +68,123 @@ formatv_object_base::parseReplacementItem(StringRef Spec) {
6768
StringRef Options;
6869
size_t Index = 0;
6970
RepString = RepString.trim();
70-
if (RepString.consumeInteger(0, Index)) {
71-
assert(false && "Invalid replacement sequence index!");
72-
return ReplacementItem{};
73-
}
71+
if (RepString.consumeInteger(0, Index))
72+
return "Invalid replacement sequence index!";
7473
RepString = RepString.trim();
7574
if (RepString.consume_front(",")) {
7675
if (!consumeFieldLayout(RepString, Where, Align, Pad))
77-
assert(false && "Invalid replacement field layout specification!");
76+
return "Invalid replacement field layout specification!";
7877
}
7978
RepString = RepString.trim();
8079
if (RepString.consume_front(":")) {
8180
Options = RepString.trim();
8281
RepString = StringRef();
8382
}
8483
RepString = RepString.trim();
85-
if (!RepString.empty()) {
86-
assert(false && "Unexpected characters found in replacement string!");
87-
}
88-
84+
if (!RepString.empty())
85+
return "Unexpected character found in replacement string!";
8986
return ReplacementItem{Spec, Index, Align, Where, Pad, Options};
9087
}
9188

92-
std::pair<ReplacementItem, StringRef>
93-
formatv_object_base::splitLiteralAndReplacement(StringRef Fmt) {
94-
while (!Fmt.empty()) {
95-
// Everything up until the first brace is a literal.
96-
if (Fmt.front() != '{') {
97-
std::size_t BO = Fmt.find_first_of('{');
98-
return std::make_pair(ReplacementItem{Fmt.substr(0, BO)}, Fmt.substr(BO));
99-
}
100-
101-
StringRef Braces = Fmt.take_while([](char C) { return C == '{'; });
102-
// If there is more than one brace, then some of them are escaped. Treat
103-
// these as replacements.
104-
if (Braces.size() > 1) {
105-
size_t NumEscapedBraces = Braces.size() / 2;
106-
StringRef Middle = Fmt.take_front(NumEscapedBraces);
107-
StringRef Right = Fmt.drop_front(NumEscapedBraces * 2);
108-
return std::make_pair(ReplacementItem{Middle}, Right);
109-
}
110-
// An unterminated open brace is undefined. Assert to indicate that this is
111-
// undefined and that we consider it an error. When asserts are disabled,
112-
// build a replacement item with an error message.
113-
std::size_t BC = Fmt.find_first_of('}');
114-
if (BC == StringRef::npos) {
115-
assert(
116-
false &&
117-
"Unterminated brace sequence. Escape with {{ for a literal brace.");
118-
return std::make_pair(
119-
ReplacementItem{"Unterminated brace sequence. Escape with {{ for a "
120-
"literal brace."},
121-
StringRef());
122-
}
123-
124-
// Even if there is a closing brace, if there is another open brace before
125-
// this closing brace, treat this portion as literal, and try again with the
126-
// next one.
127-
std::size_t BO2 = Fmt.find_first_of('{', 1);
128-
if (BO2 < BC)
129-
return std::make_pair(ReplacementItem{Fmt.substr(0, BO2)},
130-
Fmt.substr(BO2));
131-
132-
StringRef Spec = Fmt.slice(1, BC);
133-
StringRef Right = Fmt.substr(BC + 1);
134-
135-
auto RI = parseReplacementItem(Spec);
136-
if (RI)
137-
return std::make_pair(*RI, Right);
89+
static std::variant<std::pair<ReplacementItem, StringRef>, StringRef>
90+
splitLiteralAndReplacement(StringRef Fmt) {
91+
// Everything up until the first brace is a literal.
92+
if (Fmt.front() != '{') {
93+
std::size_t BO = Fmt.find_first_of('{');
94+
return std::make_pair(ReplacementItem(Fmt.substr(0, BO)), Fmt.substr(BO));
95+
}
13896

139-
// If there was an error parsing the replacement item, treat it as an
140-
// invalid replacement spec, and just continue.
141-
Fmt = Fmt.drop_front(BC + 1);
97+
StringRef Braces = Fmt.take_while([](char C) { return C == '{'; });
98+
// If there is more than one brace, then some of them are escaped. Treat
99+
// these as replacements.
100+
if (Braces.size() > 1) {
101+
size_t NumEscapedBraces = Braces.size() / 2;
102+
StringRef Middle = Fmt.take_front(NumEscapedBraces);
103+
StringRef Right = Fmt.drop_front(NumEscapedBraces * 2);
104+
return std::make_pair(ReplacementItem(Middle), Right);
142105
}
143-
return std::make_pair(ReplacementItem{Fmt}, StringRef());
106+
// An unterminated open brace is undefined. Assert to indicate that this is
107+
// undefined and that we consider it an error. When asserts are disabled,
108+
// build a replacement item with an error message.
109+
std::size_t BC = Fmt.find_first_of('}');
110+
if (BC == StringRef::npos)
111+
return "Unterminated brace sequence. Escape with {{ for a literal brace.";
112+
113+
// Even if there is a closing brace, if there is another open brace before
114+
// this closing brace, treat this portion as literal, and try again with the
115+
// next one.
116+
std::size_t BO2 = Fmt.find_first_of('{', 1);
117+
if (BO2 < BC)
118+
return std::make_pair(ReplacementItem{Fmt.substr(0, BO2)}, Fmt.substr(BO2));
119+
120+
StringRef Spec = Fmt.slice(1, BC);
121+
StringRef Right = Fmt.substr(BC + 1);
122+
123+
auto RI = parseReplacementItem(Spec);
124+
if (const StringRef *ErrMsg = std::get_if<1>(&RI))
125+
return *ErrMsg;
126+
127+
return std::make_pair(std::get<0>(RI), Right);
144128
}
145129

146-
SmallVector<ReplacementItem, 2>
147-
formatv_object_base::parseFormatString(StringRef Fmt) {
130+
std::pair<SmallVector<ReplacementItem, 2>, bool>
131+
formatv_object_base::parseFormatString(raw_ostream &S, const StringRef Fmt,
132+
size_t NumArgs, bool Validate) {
148133
SmallVector<ReplacementItem, 2> Replacements;
149134
ReplacementItem I;
150-
while (!Fmt.empty()) {
151-
std::tie(I, Fmt) = splitLiteralAndReplacement(Fmt);
135+
size_t NumExpectedArgs = 0;
136+
137+
// Make a copy for pasring as it updates it.
138+
StringRef ParseFmt = Fmt;
139+
while (!ParseFmt.empty()) {
140+
auto RI = splitLiteralAndReplacement(ParseFmt);
141+
if (const StringRef *ErrMsg = std::get_if<1>(&RI)) {
142+
// If there was an error parsing the format string, write the error to the
143+
// stream, and return false as second member of the pair.
144+
errs() << "Invalid format string: " << Fmt << "\n";
145+
assert(0 && "Invalid format string");
146+
S << *ErrMsg;
147+
return {{}, false};
148+
}
149+
std::tie(I, ParseFmt) = std::get<0>(RI);
152150
if (I.Type != ReplacementType::Empty)
153151
Replacements.push_back(I);
152+
if (I.Type == ReplacementType::Format)
153+
NumExpectedArgs = std::max(NumExpectedArgs, I.Index + 1);
154154
}
155-
return Replacements;
155+
if (!Validate)
156+
return {Replacements, true};
157+
158+
// Perform additional validation. Verify that the number of arguments matches
159+
// the number of replacement indices and that there are no holes in the
160+
// replacement indexes.
161+
if (NumExpectedArgs != NumArgs) {
162+
errs() << "Expected " << NumExpectedArgs << " Args, but got " << NumArgs
163+
<< " for format string '" << Fmt << "'\n";
164+
assert(0 && "Invalid formatv() call");
165+
S << "Expected " << NumExpectedArgs << " Args, but got " << NumArgs
166+
<< " for format string '" << Fmt << "'\n";
167+
return {{}, false};
168+
}
169+
170+
SmallSet<size_t, 2> Indices;
171+
for (const ReplacementItem &R : Replacements) {
172+
if (R.Type != ReplacementType::Format)
173+
continue;
174+
Indices.insert(R.Index);
175+
}
176+
177+
if (Indices.size() != NumExpectedArgs) {
178+
errs() << "Invalid format string: Replacement field indices "
179+
"cannot have holes for format string '"
180+
<< Fmt << "'\n";
181+
assert(0 && "Invalid format string");
182+
S << "Replacement field indices cannot have holes for format string '"
183+
<< Fmt << "'\n";
184+
return {{}, false};
185+
}
186+
187+
return {Replacements, true};
156188
}
157189

158190
void support::detail::format_adapter::anchor() {}

0 commit comments

Comments
 (0)