Skip to content

Commit d805dcd

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 e03669a commit d805dcd

File tree

16 files changed

+218
-85
lines changed

16 files changed

+218
-85
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/benchmarks/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@ set(LLVM_LINK_COMPONENTS
55
add_benchmark(DummyYAML DummyYAML.cpp PARTIAL_SOURCES_INTENDED)
66
add_benchmark(xxhash xxhash.cpp PARTIAL_SOURCES_INTENDED)
77
add_benchmark(GetIntrinsicForClangBuiltin GetIntrinsicForClangBuiltin.cpp PARTIAL_SOURCES_INTENDED)
8+
add_benchmark(FormatVariadicBM FormatVariadicBM.cpp PARTIAL_SOURCES_INTENDED)

llvm/benchmarks/FormatVariadicBM.cpp

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
#include "benchmark/benchmark.h"
2+
#include "llvm/Support/FormatVariadic.h"
3+
#include <algorithm>
4+
#include <string>
5+
#include <vector>
6+
7+
using namespace llvm;
8+
using namespace std;
9+
10+
// Generate a list of format strings that have `NumReplacements` replacements.
11+
static vector<string> getFormatStrings(int NumReplacements) {
12+
vector<string> Components;
13+
for (int I = 0; I < NumReplacements; I++)
14+
Components.push_back("{" + to_string(I) + "}");
15+
// Intersperse these with some other literal text (_).
16+
const string_view Literal = "____";
17+
for (char C : Literal)
18+
Components.push_back(string(1, C));
19+
20+
vector<string> Formats;
21+
do {
22+
string Concat;
23+
for (const string &C : Components)
24+
Concat += C;
25+
Formats.emplace_back(Concat);
26+
} while (next_permutation(Components.begin(), Components.end()));
27+
return Formats;
28+
}
29+
30+
static const vector<vector<string>> Formats = {
31+
getFormatStrings(1), getFormatStrings(2), getFormatStrings(3),
32+
getFormatStrings(4), getFormatStrings(5),
33+
};
34+
35+
// Benchmark formatv() for a variety of format strings and 1-5 replacements.
36+
static void BM_FormatVariadic(benchmark::State &state) {
37+
for (auto _ : state) {
38+
for (const string &Fmt : Formats[0])
39+
formatv(Fmt.c_str(), 1).str();
40+
for (const string &Fmt : Formats[1])
41+
formatv(Fmt.c_str(), 1, 2).str();
42+
for (const string &Fmt : Formats[2])
43+
formatv(Fmt.c_str(), 1, 2, 3).str();
44+
for (const string &Fmt : Formats[3])
45+
formatv(Fmt.c_str(), 1, 2, 3, 4).str();
46+
for (const string &Fmt : Formats[4])
47+
formatv(Fmt.c_str(), 1, 2, 3, 4, 5).str();
48+
}
49+
}
50+
51+
BENCHMARK(BM_FormatVariadic);
52+
53+
BENCHMARK_MAIN();

llvm/include/llvm/Support/FormatVariadic.h

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -67,23 +67,20 @@ class formatv_object_base {
6767
protected:
6868
StringRef Fmt;
6969
ArrayRef<support::detail::format_adapter *> Adapters;
70-
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);
70+
bool Validate;
7671

7772
formatv_object_base(StringRef Fmt,
78-
ArrayRef<support::detail::format_adapter *> Adapters)
79-
: Fmt(Fmt), Adapters(Adapters) {}
73+
ArrayRef<support::detail::format_adapter *> Adapters,
74+
bool Validate)
75+
: Fmt(Fmt), Adapters(Adapters), Validate(Validate) {}
8076

8177
formatv_object_base(formatv_object_base const &rhs) = delete;
8278
formatv_object_base(formatv_object_base &&rhs) = default;
8379

8480
public:
8581
void format(raw_ostream &S) const {
86-
for (auto &R : parseFormatString(Fmt)) {
82+
const auto Replacements = parseFormatString(Fmt, Adapters.size(), Validate);
83+
for (const auto &R : Replacements) {
8784
if (R.Type == ReplacementType::Empty)
8885
continue;
8986
if (R.Type == ReplacementType::Literal) {
@@ -101,9 +98,10 @@ class formatv_object_base {
10198
Align.format(S, R.Options);
10299
}
103100
}
104-
static SmallVector<ReplacementItem, 2> parseFormatString(StringRef Fmt);
105101

106-
static std::optional<ReplacementItem> parseReplacementItem(StringRef Spec);
102+
// Parse and optionally validate format string (in debug builds).
103+
static SmallVector<ReplacementItem, 2>
104+
parseFormatString(StringRef Fmt, size_t NumArgs, bool Validate);
107105

108106
std::string str() const {
109107
std::string Result;
@@ -149,8 +147,8 @@ template <typename Tuple> class formatv_object : public formatv_object_base {
149147
};
150148

151149
public:
152-
formatv_object(StringRef Fmt, Tuple &&Params)
153-
: formatv_object_base(Fmt, ParameterPointers),
150+
formatv_object(StringRef Fmt, Tuple &&Params, bool Validate)
151+
: formatv_object_base(Fmt, ParameterPointers, Validate),
154152
Parameters(std::move(Params)) {
155153
ParameterPointers = std::apply(create_adapters(), Parameters);
156154
}
@@ -247,15 +245,27 @@ template <typename Tuple> class formatv_object : public formatv_object_base {
247245
// assertion. Otherwise, it will try to do something reasonable, but in general
248246
// the details of what that is are undefined.
249247
//
248+
249+
// formatv() with validation enable/disable controlled by the first argument.
250250
template <typename... Ts>
251-
inline auto formatv(const char *Fmt, Ts &&...Vals)
251+
inline auto formatv(bool Validate, const char *Fmt, Ts &&...Vals)
252252
-> formatv_object<decltype(std::make_tuple(
253253
support::detail::build_format_adapter(std::forward<Ts>(Vals))...))> {
254254
using ParamTuple = decltype(std::make_tuple(
255255
support::detail::build_format_adapter(std::forward<Ts>(Vals))...));
256-
return formatv_object<ParamTuple>(
257-
Fmt, std::make_tuple(support::detail::build_format_adapter(
258-
std::forward<Ts>(Vals))...));
256+
auto Params = std::make_tuple(
257+
support::detail::build_format_adapter(std::forward<Ts>(Vals))...);
258+
return formatv_object<ParamTuple>(Fmt, std::move(Params), Validate);
259+
}
260+
261+
// formatv() with validation enabled.
262+
template <typename... Ts> inline auto formatv(const char *Fmt, Ts &&...Vals) {
263+
return formatv<Ts...>(true, Fmt, std::forward<Ts>(Vals)...);
264+
}
265+
266+
// formatvv() has validation disabled.
267+
template <typename... Ts> inline auto formatvv(const char *Fmt, Ts &&...Vals) {
268+
return formatv<Ts...>(false, Fmt, std::forward<Ts>(Vals)...);
259269
}
260270

261271
} // 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/Support/FormatVariadic.cpp

Lines changed: 71 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@ static std::optional<AlignStyle> translateLocChar(char C) {
2525
LLVM_BUILTIN_UNREACHABLE;
2626
}
2727

28-
bool formatv_object_base::consumeFieldLayout(StringRef &Spec, AlignStyle &Where,
29-
size_t &Align, char &Pad) {
28+
static bool consumeFieldLayout(StringRef &Spec, AlignStyle &Where,
29+
size_t &Align, char &Pad) {
3030
Where = AlignStyle::Right;
3131
Align = 0;
3232
Pad = ' ';
@@ -35,8 +35,7 @@ bool formatv_object_base::consumeFieldLayout(StringRef &Spec, AlignStyle &Where,
3535

3636
if (Spec.size() > 1) {
3737
// A maximum of 2 characters at the beginning can be used for something
38-
// other
39-
// than the width.
38+
// other than the width.
4039
// If Spec[1] is a loc char, then Spec[0] is a pad char and Spec[2:...]
4140
// contains the width.
4241
// Otherwise, if Spec[0] is a loc char, then Spec[1:...] contains the width.
@@ -55,8 +54,7 @@ bool formatv_object_base::consumeFieldLayout(StringRef &Spec, AlignStyle &Where,
5554
return !Failed;
5655
}
5756

58-
std::optional<ReplacementItem>
59-
formatv_object_base::parseReplacementItem(StringRef Spec) {
57+
static std::optional<ReplacementItem> parseReplacementItem(StringRef Spec) {
6058
StringRef RepString = Spec.trim("{}");
6159

6260
// If the replacement sequence does not start with a non-negative integer,
@@ -82,15 +80,14 @@ formatv_object_base::parseReplacementItem(StringRef Spec) {
8280
RepString = StringRef();
8381
}
8482
RepString = RepString.trim();
85-
if (!RepString.empty()) {
86-
assert(false && "Unexpected characters found in replacement string!");
87-
}
83+
assert(RepString.empty() &&
84+
"Unexpected characters found in replacement string!");
8885

8986
return ReplacementItem{Spec, Index, Align, Where, Pad, Options};
9087
}
9188

92-
std::pair<ReplacementItem, StringRef>
93-
formatv_object_base::splitLiteralAndReplacement(StringRef Fmt) {
89+
static std::pair<ReplacementItem, StringRef>
90+
splitLiteralAndReplacement(StringRef Fmt) {
9491
while (!Fmt.empty()) {
9592
// Everything up until the first brace is a literal.
9693
if (Fmt.front() != '{') {
@@ -143,15 +140,76 @@ formatv_object_base::splitLiteralAndReplacement(StringRef Fmt) {
143140
return std::make_pair(ReplacementItem{Fmt}, StringRef());
144141
}
145142

143+
#ifndef NDEBUG
144+
#define ENABLE_VALIDATION 1
145+
#else
146+
#define ENABLE_VALIDATION 1 // Convienently enable validation in release mode.
147+
#endif
148+
146149
SmallVector<ReplacementItem, 2>
147-
formatv_object_base::parseFormatString(StringRef Fmt) {
150+
formatv_object_base::parseFormatString(StringRef Fmt, size_t NumArgs,
151+
bool Validate) {
148152
SmallVector<ReplacementItem, 2> Replacements;
149-
ReplacementItem I;
153+
154+
#if ENABLE_VALIDATION
155+
const StringRef SavedFmtStr = Fmt;
156+
size_t NumExpectedArgs = 0;
157+
#endif
158+
150159
while (!Fmt.empty()) {
160+
ReplacementItem I;
151161
std::tie(I, Fmt) = splitLiteralAndReplacement(Fmt);
152162
if (I.Type != ReplacementType::Empty)
153163
Replacements.push_back(I);
164+
#if ENABLE_VALIDATION
165+
if (I.Type == ReplacementType::Format)
166+
NumExpectedArgs = std::max(NumExpectedArgs, I.Index + 1);
167+
#endif
168+
}
169+
170+
#if ENABLE_VALIDATION
171+
if (!Validate)
172+
return Replacements;
173+
174+
// Perform additional validation. Verify that the number of arguments matches
175+
// the number of replacement indices and that there are no holes in the
176+
// replacement indexes.
177+
178+
// When validation fails, return an array of replacement items that
179+
// will print an error message as the outout of this formatv().
180+
auto getErrorReplacements = [SavedFmtStr](StringLiteral ErrorMsg) {
181+
return SmallVector<ReplacementItem, 2>{
182+
ReplacementItem("Invalid formatv() call: "), ReplacementItem(ErrorMsg),
183+
ReplacementItem(" for format string: "), ReplacementItem(SavedFmtStr)};
184+
};
185+
186+
if (NumExpectedArgs != NumArgs) {
187+
errs() << formatv(
188+
"Expected {0} Args, but got {1} for format string '{2}'\n",
189+
NumExpectedArgs, NumArgs, SavedFmtStr);
190+
assert(0 && "Invalid formatv() call");
191+
return getErrorReplacements("Unexpected number of arguments");
192+
}
193+
194+
// Find the number of unique indices seen. All replacement indices
195+
// are < NumExpectedArgs.
196+
SmallVector<bool> Indices(NumExpectedArgs);
197+
size_t Count = 0;
198+
for (const ReplacementItem &I : Replacements) {
199+
if (I.Type != ReplacementType::Format || Indices[I.Index])
200+
continue;
201+
Indices[I.Index] = true;
202+
++Count;
203+
}
204+
205+
if (Count != NumExpectedArgs) {
206+
errs() << formatv(
207+
"Replacement field indices cannot have holes for format string '{0}'\n",
208+
SavedFmtStr);
209+
assert(0 && "Invalid format string");
210+
return getErrorReplacements("Replacement indices have holes");
154211
}
212+
#endif // ENABLE_VALIDATION
155213
return Replacements;
156214
}
157215

llvm/tools/llvm-pdbutil/ExplainOutputStyle.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -139,8 +139,8 @@ bool ExplainOutputStyle::explainPdbBlockStatus() {
139139
FileOffset, File.pdb().getFileSize());
140140
return false;
141141
}
142-
P.formatLine("Block:Offset = {2:X-}:{1:X-4}.", FileOffset, pdbBlockOffset(),
143-
pdbBlockIndex());
142+
P.formatLine("Block:Offset = {0:X-}:{1:X-4}.", pdbBlockIndex(),
143+
pdbBlockOffset());
144144

145145
bool IsFree = File.pdb().getMsfLayout().FreePageMap[pdbBlockIndex()];
146146
P.formatLine("Address is in block {0} ({1}allocated).", pdbBlockIndex(),

0 commit comments

Comments
 (0)