-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathStringViewDanglingRuntimeRule.cpp
More file actions
277 lines (234 loc) · 8.05 KB
/
Copy pathStringViewDanglingRuntimeRule.cpp
File metadata and controls
277 lines (234 loc) · 8.05 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
/**
*
* @file StringViewDanglingRuntimeRule.cpp
* @author Gaspard Kirira
*
* Copyright 2025, Gaspard Kirira. All rights reserved.
* https://github.com/vixcpp/vix
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Vix.cpp
*
*/
#include <vix/cli/errors/runtime/IRuntimeErrorRule.hpp>
#include <vix/cli/errors/runtime/RuntimeRuleUtils.hpp>
#include <filesystem>
#include <iostream>
#include <memory>
#include <string>
#include <vector>
#include <vix/cli/Style.hpp>
using namespace vix::cli::style;
namespace vix::cli::errors::runtime
{
namespace
{
enum class StringViewLifetimeKind
{
OutlivedLocalString,
UseAfterReturn,
PointsToFreedMemory,
PointsToTemporary,
InvalidatedByStringMutation,
InvalidatedByMove,
ReturnedDanglingView,
GenericDanglingView,
};
StringViewLifetimeKind classify_issue(const std::string &log)
{
if (icontains(log, "use-after-return"))
{
return StringViewLifetimeKind::UseAfterReturn;
}
if (icontains(log, "stack-use-after-scope") ||
icontains(log, "use-after-scope"))
{
return StringViewLifetimeKind::OutlivedLocalString;
}
if (icontains(log, "heap-use-after-free") ||
icontains(log, "use-after-free"))
{
return StringViewLifetimeKind::PointsToFreedMemory;
}
if (icontains(log, "temporary") ||
icontains(log, "temporary object") ||
icontains(log, "rvalue"))
{
return StringViewLifetimeKind::PointsToTemporary;
}
if (icontains(log, "reallocation") ||
icontains(log, "reallocated") ||
icontains(log, "reserve") ||
icontains(log, "resize") ||
icontains(log, "clear") ||
icontains(log, "append") ||
icontains(log, "operator+="))
{
return StringViewLifetimeKind::InvalidatedByStringMutation;
}
if (icontains(log, "moved-from") ||
icontains(log, "moved from") ||
icontains(log, "std::move"))
{
return StringViewLifetimeKind::InvalidatedByMove;
}
if (icontains(log, "return") &&
(icontains(log, "string_view") ||
icontains(log, "basic_string_view")))
{
return StringViewLifetimeKind::ReturnedDanglingView;
}
return StringViewLifetimeKind::GenericDanglingView;
}
std::string choose_message(const std::string &log)
{
switch (classify_issue(log))
{
case StringViewLifetimeKind::OutlivedLocalString:
return "std::string_view outlived local string data";
case StringViewLifetimeKind::UseAfterReturn:
return "std::string_view points to data from a returned function";
case StringViewLifetimeKind::PointsToFreedMemory:
return "std::string_view points to freed memory";
case StringViewLifetimeKind::PointsToTemporary:
return "std::string_view points to temporary string data";
case StringViewLifetimeKind::InvalidatedByStringMutation:
return "std::string_view invalidated by string mutation";
case StringViewLifetimeKind::InvalidatedByMove:
return "std::string_view points to moved-from string data";
case StringViewLifetimeKind::ReturnedDanglingView:
return "function returned a dangling std::string_view";
case StringViewLifetimeKind::GenericDanglingView:
default:
return "dangling std::string_view";
}
}
std::string choose_hint(const std::string &log)
{
switch (classify_issue(log))
{
case StringViewLifetimeKind::OutlivedLocalString:
return "return std::string when ownership is needed, or ensure the local string outlives the std::string_view";
case StringViewLifetimeKind::UseAfterReturn:
return "do not return std::string_view pointing to local std::string or temporary storage";
case StringViewLifetimeKind::PointsToFreedMemory:
return "avoid keeping std::string_view after the owning string is destroyed";
case StringViewLifetimeKind::PointsToTemporary:
return "store the string in a stable std::string before creating std::string_view";
case StringViewLifetimeKind::InvalidatedByStringMutation:
return "recreate the std::string_view after modifying the owning string";
case StringViewLifetimeKind::InvalidatedByMove:
return "do not keep std::string_view into a string after that string has been moved";
case StringViewLifetimeKind::ReturnedDanglingView:
return "return std::string instead, or return std::string_view only when the referenced storage is guaranteed to outlive the caller";
case StringViewLifetimeKind::GenericDanglingView:
default:
return "do not keep std::string_view to temporary, local, destroyed, moved, or reallocated string storage";
}
}
std::vector<std::string> source_patterns_for_string_view_lifetime()
{
return {
"std::string_view",
"string_view",
"basic_string_view",
".substr(",
"substr(",
".data()",
"data()",
".c_str()",
"c_str()",
"std::string",
"string ",
"std::move(",
".clear(",
"clear(",
".resize(",
"resize(",
".reserve(",
"reserve(",
".append(",
"append(",
".push_back(",
"push_back(",
"+=",
"return",
};
}
bool looks_like_string_view_lifetime_log(const std::string &log)
{
const bool hasStringViewSignal =
icontains(log, "string_view") ||
icontains(log, "basic_string_view") ||
icontains(log, "std::string_view");
const bool hasLifetimeSignal =
icontains(log, "dangling") ||
icontains(log, "lifetime") ||
icontains(log, "invalid") ||
icontains(log, "stack-use-after-scope") ||
icontains(log, "use-after-scope") ||
icontains(log, "use-after-return") ||
icontains(log, "heap-use-after-free") ||
icontains(log, "use-after-free") ||
icontains(log, "temporary") ||
icontains(log, "reallocation") ||
icontains(log, "reallocated") ||
icontains(log, "moved-from") ||
icontains(log, "moved from");
return hasStringViewSignal && hasLifetimeSignal;
}
} // namespace
class StringViewDanglingRuntimeRule final : public IRuntimeErrorRule
{
public:
bool match(
const std::string &log,
const std::filesystem::path &sourceFile) const override
{
(void)sourceFile;
return looks_like_string_view_lifetime_log(log);
}
bool handle(
const std::string &log,
const std::filesystem::path &sourceFile) const override
{
const std::string message = choose_message(log);
RuntimeLocation location =
find_best_runtime_location(log, sourceFile);
if (!location.valid())
{
location =
find_best_runtime_location_or_source_hint(
log,
sourceFile,
source_patterns_for_string_view_lifetime());
}
std::cerr << RED
<< "runtime error: "
<< message
<< RESET << "\n";
if (location.valid())
{
const auto err = make_runtime_location(
location.file,
location.line,
location.column,
message);
print_runtime_codeframe(err);
}
print_runtime_hints_and_at(
{
choose_hint(log),
"do not ignore the runtime log: string_view bugs often require comparing the view creation site and the invalid use site",
},
make_at_text(location, sourceFile));
print_runtime_log_excerpt(log, 22);
return true;
}
};
std::unique_ptr<IRuntimeErrorRule> makeStringViewDanglingRuntimeRule()
{
return std::make_unique<StringViewDanglingRuntimeRule>();
}
} // namespace vix::cli::errors::runtime