载入中...
搜索中...
未找到
CallGraph.cpp
浏览该文件的文档.
2
3#include <algorithm>
4#include <queue>
5#include <sstream>
6
7namespace eve::dev {
8
9bool SourceLoc::matches(const SourceLoc& o) const {
10 if (line > 0 && o.line > 0 && line != o.line) return false;
11 if (!source.empty() && !o.source.empty() && source != o.source) return false;
12 if (!function.empty() && !o.function.empty() && function != o.function) return false;
13 return true;
14}
15
16std::string SourceLoc::toString() const {
17 std::ostringstream os;
18 if (!source.empty())
19 os << source;
20 else
21 os << "<unknown>";
22 if (line > 0) os << ':' << line;
23 if (!function.empty()) os << " in " << function;
24 return os.str();
25}
26
27CallGraph::CallGraph() = default;
28
29CallGraph::~CallGraph() = default;
30
32 head_ = 0;
33 count_ = 0;
34 frameStack_.clear();
35 nextFrameId_ = 1;
36 nextEventId_ = 1;
37 lastEventId_ = 0;
38 lastDef_.clear();
39 dataDeps_.clear();
40 frameToCallEvent_.clear();
41}
42
43void CallGraph::ensureRing() {
44 if (slots_.size() != maxEvents_) slots_.resize(maxEvents_);
45}
46
48 n = n < 16 ? 16 : n;
49 if (n == maxEvents_ && slots_.size() == n) return;
50
51 const size_t keep = count_ < n ? count_ : n;
52 const size_t drop = count_ - keep;
53 std::vector<TraceEvent> neu(n);
54 for (size_t i = 0; i < drop; ++i) retireSlot(physicalIndex(i));
55 for (size_t i = 0; i < keep; ++i) neu[i] = (*this)[drop + i];
56
57 slots_ = std::move(neu);
58 head_ = 0;
59 count_ = keep;
60 maxEvents_ = n;
61}
62
63void CallGraph::retireSlot(size_t physical) {
64 TraceEvent& old = slots_[physical];
65 if (old.id == 0) return;
66
67 dataDeps_.erase(old.id);
68
69 if (old.kind == TraceKind::Def && !old.name.empty()) {
70 auto fit = lastDef_.find(old.frameId);
71 if (fit != lastDef_.end()) {
72 auto vit = fit->second.find(old.name);
73 if (vit != fit->second.end() && vit->second == old.id) fit->second.erase(vit);
74 if (fit->second.empty()) lastDef_.erase(fit);
75 }
76 }
77
78 if (old.kind == TraceKind::Call) {
79 auto it = frameToCallEvent_.find(old.frameId);
80 if (it != frameToCallEvent_.end() && it->second == old.id) frameToCallEvent_.erase(it);
81 }
82
83 old = TraceEvent{};
84}
85
86uint32_t CallGraph::append(TraceKind kind, const SourceLoc& loc, const std::string& name) {
87 ensureRing();
88
89 size_t phys;
90 if (count_ < maxEvents_) {
91 phys = physicalIndex(count_);
92 ++count_;
93 } else {
94 // Overwrite oldest slot in place; advance head.
95 phys = head_;
96 retireSlot(phys);
97 head_ = (head_ + 1) % maxEvents_;
98 }
99
100 TraceEvent& e = slots_[phys];
101 e.id = nextEventId_++;
102 e.kind = kind;
103 e.loc = loc;
104 e.name = name;
105 e.frameId = frameStack_.empty() ? 0 : frameStack_.back();
106 e.parentEventId = lastEventId_;
107 if (e.loc.function.empty() && !name.empty() &&
109 e.loc.function = name;
110 }
111 lastEventId_ = e.id;
112 return e.id;
113}
114
115uint32_t CallGraph::onCall(const SourceLoc& loc, const std::string& funcName) {
116 const std::string fn = !funcName.empty() ? funcName : loc.function;
117 const uint32_t id = append(TraceKind::Call, loc, fn);
118 const uint32_t frame = nextFrameId_++;
119 newest().frameId = frame;
120 frameStack_.push_back(frame);
121 frameToCallEvent_[frame] = id;
122 return id;
123}
124
125uint32_t CallGraph::onReturn(const SourceLoc& loc, const std::string& funcName) {
126 const std::string fn = !funcName.empty() ? funcName : loc.function;
127 const uint32_t id = append(TraceKind::Return, loc, fn);
128 if (!frameStack_.empty()) {
129 newest().frameId = frameStack_.back();
130 frameStack_.pop_back();
131 }
132 return id;
133}
134
135uint32_t CallGraph::onLine(const SourceLoc& loc) {
136 return append(TraceKind::Line, loc, {});
137}
138
139uint32_t CallGraph::onDef(const SourceLoc& loc, const std::string& var) {
140 if (var.empty()) return 0;
141 const uint32_t id = append(TraceKind::Def, loc, var);
142 const uint32_t frameId = newest().frameId;
143 lastDef_[frameId][var] = id;
144 // Free / outer locals: if an enclosing activation already tracks this name,
145 // update its reaching definition (Squirrel closures mutate outer bindings).
146 for (uint32_t fid : frameStack_) {
147 if (fid == frameId) continue;
148 auto fit = lastDef_.find(fid);
149 if (fit != lastDef_.end() && fit->second.count(var)) fit->second[var] = id;
150 }
151 return id;
152}
153
154uint32_t CallGraph::onUse(const SourceLoc& loc, const std::string& var) {
155 if (var.empty()) return 0;
156 const uint32_t id = append(TraceKind::Use, loc, var);
157 linkData(id, var);
158 return id;
159}
160
161void CallGraph::linkData(uint32_t useEventId, const std::string& var) {
162 const TraceEvent* use = event(useEventId);
163 if (!use || var.empty()) return;
164
165 // 1) Same-frame reaching definition
166 auto fit = lastDef_.find(use->frameId);
167 if (fit != lastDef_.end()) {
168 auto vit = fit->second.find(var);
169 if (vit != fit->second.end() && vit->second < useEventId && event(vit->second)) {
170 dataDeps_[useEventId].push_back(vit->second);
171 return;
172 }
173 }
174
175 // 2) Walk caller frames for outer-scope / captured defs (best-effort)
176 for (auto it = frameStack_.rbegin(); it != frameStack_.rend(); ++it) {
177 if (*it == use->frameId) continue;
178 auto fit2 = lastDef_.find(*it);
179 if (fit2 == lastDef_.end()) continue;
180 auto vit2 = fit2->second.find(var);
181 if (vit2 != fit2->second.end() && vit2->second < useEventId && event(vit2->second)) {
182 dataDeps_[useEventId].push_back(vit2->second);
183 return;
184 }
185 }
186
187 // 3) Historical scan inside the retained window (newest → oldest)
188 for (size_t i = count_; i-- > 0;) {
189 const TraceEvent& e = (*this)[i];
190 if (e.id >= useEventId) continue;
191 if (e.kind == TraceKind::Def && e.name == var) {
192 dataDeps_[useEventId].push_back(e.id);
193 return;
194 }
195 }
196}
197
198uint32_t CallGraph::enter(const SourceLoc& loc, const std::string& funcName) {
199 onCall(loc, funcName);
200 return onLine(loc);
201}
202
203const TraceEvent* CallGraph::event(uint32_t id) const {
204 if (id == 0 || count_ == 0) return nullptr;
205 const uint32_t first = (*this)[0].id;
206 const uint32_t last = (*this)[count_ - 1].id;
207 if (id < first || id > last) return nullptr;
208 return &(*this)[static_cast<size_t>(id - first)];
209}
210
211std::vector<CallFrame> CallGraph::currentStack() const {
212 std::vector<CallFrame> out;
213 out.reserve(frameStack_.size());
214 for (uint32_t fid : frameStack_) {
215 CallFrame f;
216 f.frameId = fid;
217 auto it = frameToCallEvent_.find(fid);
218 if (it != frameToCallEvent_.end()) {
219 f.callEventId = it->second;
220 if (const TraceEvent* e = event(it->second)) f.loc = e->loc;
221 }
222 out.push_back(f);
223 }
224 return out;
225}
226
227std::vector<CallFrame> CallGraph::stackAt(uint32_t eventId) const {
228 std::vector<CallFrame> stack;
229 if (!event(eventId)) return stack;
230 for (size_t i = 0; i < count_; ++i) {
231 const TraceEvent& e = (*this)[i];
232 if (e.id > eventId) break;
233 if (e.kind == TraceKind::Call) {
234 CallFrame f;
235 f.frameId = e.frameId;
236 f.loc = e.loc;
237 f.callEventId = e.id;
238 stack.push_back(f);
239 } else if (e.kind == TraceKind::Return) {
240 if (!stack.empty()) stack.pop_back();
241 }
242 }
243 return stack;
244}
245
246std::vector<std::pair<SourceLoc, SourceLoc>> CallGraph::callEdges() const {
247 std::vector<std::pair<SourceLoc, SourceLoc>> edges;
248 std::vector<SourceLoc> stack;
249 for (size_t i = 0; i < count_; ++i) {
250 const TraceEvent& e = (*this)[i];
251 if (e.kind == TraceKind::Call) {
252 if (!stack.empty()) edges.emplace_back(stack.back(), e.loc);
253 stack.push_back(e.loc);
254 } else if (e.kind == TraceKind::Return) {
255 if (!stack.empty()) stack.pop_back();
256 }
257 }
258 return edges;
259}
260
261uint32_t CallGraph::findSeedEvent(const SliceCriterion& c) const {
262 if (c.eventId != 0 && event(c.eventId)) return c.eventId;
263 if (c.loc.empty()) return count_ == 0 ? 0 : (*this)[count_ - 1].id;
264
265 for (size_t i = count_; i-- > 0;) {
266 if (c.loc.matches((*this)[i].loc)) return (*this)[i].id;
267 }
268 return count_ == 0 ? 0 : (*this)[count_ - 1].id;
269}
270
271void CallGraph::collectSeeds(const SliceCriterion& c, std::vector<uint32_t>& out) const {
272 const uint32_t seed = findSeedEvent(c);
273 if (seed == 0) return;
274 out.push_back(seed);
275
276 const TraceEvent* se = event(seed);
277 if (!se) return;
278
279 for (size_t i = 0; i < count_; ++i) {
280 const TraceEvent& e = (*this)[i];
281 if (e.id > seed) break;
282 if (!c.loc.empty() && !c.loc.matches(e.loc)) continue;
283 if (e.kind == TraceKind::Def || e.kind == TraceKind::Use || e.kind == TraceKind::Line) {
284 if (e.id != seed) out.push_back(e.id);
285 }
286 }
287
288 if (!c.variables.empty()) {
289 for (const auto& var : c.variables) {
290 for (size_t i = count_; i-- > 0;) {
291 const TraceEvent& e = (*this)[i];
292 if (e.id >= seed) continue;
293 if (e.kind == TraceKind::Def && e.name == var) {
294 out.push_back(e.id);
295 break;
296 }
297 if (e.kind == TraceKind::Use && e.name == var) {
298 out.push_back(e.id);
299 break;
300 }
301 }
302 }
303 } else {
304 for (size_t i = 0; i < count_; ++i) {
305 const TraceEvent& e = (*this)[i];
306 if (e.id > seed) break;
307 if (e.frameId == se->frameId && e.kind == TraceKind::Use &&
308 (c.loc.empty() || c.loc.matches(e.loc))) {
309 out.push_back(e.id);
310 }
311 }
312 }
313}
314
316 SliceResult result;
317 std::vector<uint32_t> seeds;
318 collectSeeds(criterion, seeds);
319 if (seeds.empty()) {
320 result.summary = "empty slice (no matching events)";
321 return result;
322 }
323
324 const uint32_t primary = findSeedEvent(criterion);
325 result.callStack = stackAt(primary);
326
327 std::unordered_set<uint32_t> visited;
328 std::queue<uint32_t> q;
329 for (uint32_t s : seeds) {
330 if (visited.insert(s).second) q.push(s);
331 }
332
333 auto enqueue = [&](uint32_t id) {
334 if (!event(id)) return;
335 if (visited.insert(id).second) q.push(id);
336 };
337
338 while (!q.empty()) {
339 const uint32_t id = q.front();
340 q.pop();
341 const TraceEvent* e = event(id);
342 if (!e) continue;
343
344 auto dit = dataDeps_.find(id);
345 if (dit != dataDeps_.end()) {
346 for (uint32_t dep : dit->second) {
347 if (!event(dep)) continue;
348 DataFlowEdge edge;
349 edge.fromEventId = dep;
350 edge.toEventId = id;
351 edge.var = e->name;
352 result.dataFlow.push_back(edge);
353 enqueue(dep);
354 }
355 }
356
357 enqueue(e->parentEventId);
358
359 if (e->frameId != 0) {
360 auto cit = frameToCallEvent_.find(e->frameId);
361 if (cit != frameToCallEvent_.end()) {
362 enqueue(cit->second);
363 } else {
364 for (size_t i = 0; i < count_; ++i) {
365 const TraceEvent& ev = (*this)[i];
366 if (ev.kind == TraceKind::Call && ev.frameId == e->frameId) {
367 enqueue(ev.id);
368 break;
369 }
370 }
371 }
372 }
373
374 if (e->kind == TraceKind::Return && e->parentEventId != 0) enqueue(e->parentEventId);
375 }
376
377 result.eventIds.assign(visited.begin(), visited.end());
378 std::sort(result.eventIds.begin(), result.eventIds.end());
379
380 std::unordered_set<std::string> seenLoc;
381 for (uint32_t id : result.eventIds) {
382 const TraceEvent* e = event(id);
383 if (!e || e->loc.empty()) continue;
384 const std::string key = e->loc.toString();
385 if (seenLoc.insert(key).second) result.locations.push_back(e->loc);
386 }
387
388 std::ostringstream os;
389 os << "backward slice: " << result.eventIds.size() << " events, "
390 << result.locations.size() << " locations, " << result.dataFlow.size()
391 << " data-flow edges, stack depth " << result.callStack.size();
392 result.summary = os.str();
393 return result;
394}
395
396std::string CallGraph::formatErrorReport(const std::string& errorMessage,
397 const SliceCriterion& criterion) const {
398 const SliceResult slice = sliceBackward(criterion);
399 std::ostringstream os;
400 os << "=== Script Error Trace (dynamic slice) ===\n";
401 os << "Error: " << errorMessage << "\n";
402 if (!criterion.loc.empty()) os << "Site: " << criterion.loc.toString() << "\n";
403 if (!criterion.variables.empty()) {
404 os << "Vars: ";
405 for (size_t i = 0; i < criterion.variables.size(); ++i) {
406 if (i) os << ", ";
407 os << criterion.variables[i];
408 }
409 os << "\n";
410 }
411 os << "\n-- Call stack --\n";
412 if (slice.callStack.empty()) {
413 os << " (empty)\n";
414 } else {
415 for (size_t i = 0; i < slice.callStack.size(); ++i) {
416 const auto& f = slice.callStack[slice.callStack.size() - 1 - i];
417 os << " #" << i << ' ' << f.loc.toString() << "\n";
418 }
419 }
420 os << "\n-- Data flow (def → use) --\n";
421 if (slice.dataFlow.empty()) {
422 os << " (none recorded; feed onDef/onUse or enable local sampling)\n";
423 } else {
424 std::vector<DataFlowEdge> edges = slice.dataFlow;
425 std::sort(edges.begin(), edges.end(),
426 [](const DataFlowEdge& a, const DataFlowEdge& b) {
427 return a.toEventId > b.toEventId;
428 });
429 std::unordered_set<std::string> seen;
430 size_t shown = 0;
431 for (const auto& edge : edges) {
432 const TraceEvent* from = event(edge.fromEventId);
433 const TraceEvent* to = event(edge.toEventId);
434 if (!from || !to) continue;
435 std::ostringstream line;
436 line << edge.var << ": " << from->loc.toString() << " → " << to->loc.toString();
437 const std::string s = line.str();
438 if (!seen.insert(s).second) continue;
439 os << " " << s << "\n";
440 if (++shown >= 32) {
441 os << " ...\n";
442 break;
443 }
444 }
445 }
446 os << "\n-- Relevant code (slice) --\n";
447 if (slice.locations.empty()) {
448 os << " (no locations)\n";
449 } else {
450 for (const auto& loc : slice.locations) os << " " << loc.toString() << "\n";
451 }
452 os << "\n" << slice.summary << "\n";
453 return os.str();
454}
455
456} // namespace eve::dev
uint32_t seed
int line
Tok kind
std::string id
glm::vec3 n
Definition Grass.cpp:64
std::ostringstream & os
uint32_t a
uint32_t b
uint32_t c
float f
const char * name
Definition RockMesh.cpp:21
SettlementPipeline::Stage fn
uint32_t s
Definition Weather.cpp:28
const TraceEvent * event(uint32_t id) const
SliceResult sliceBackward(const SliceCriterion &criterion) const
Dynamic backward slice from an error criterion. Follows data dependencies (Use←Def) and control prede...
uint32_t enter(const SourceLoc &loc, const std::string &funcName)
Convenience: Call then Line at the same site.
uint32_t onLine(const SourceLoc &loc)
uint32_t onUse(const SourceLoc &loc, const std::string &var)
std::string formatErrorReport(const std::string &errorMessage, const SliceCriterion &criterion) const
Human-readable report: message + call stack + data-flow + slice locs.
void setMaxEvents(size_t n)
Definition CallGraph.cpp:47
uint32_t onDef(const SourceLoc &loc, const std::string &var)
std::vector< CallFrame > currentStack() const
std::vector< CallFrame > stackAt(uint32_t eventId) const
Stack reconstructed at (or just before) a given event.
std::vector< std::pair< SourceLoc, SourceLoc > > callEdges() const
uint32_t onCall(const SourceLoc &loc, const std::string &funcName={})
uint32_t onReturn(const SourceLoc &loc, const std::string &funcName={})
Criterion for a Weiser-style dynamic backward slice.
Definition CallGraph.hpp:57
std::vector< std::string > variables
Definition CallGraph.hpp:59
std::vector< CallFrame > callStack
Definition CallGraph.hpp:66
std::vector< SourceLoc > locations
Definition CallGraph.hpp:65
std::vector< DataFlowEdge > dataFlow
Definition CallGraph.hpp:67
std::vector< uint32_t > eventIds
Definition CallGraph.hpp:64
Source location in a Squirrel (or synthetic) script.
Definition CallGraph.hpp:16
bool matches(const SourceLoc &o) const
Definition CallGraph.cpp:9
std::string toString() const
Definition CallGraph.cpp:16
std::string function
Definition CallGraph.hpp:19
bool empty() const
Definition CallGraph.hpp:21
std::string source
Definition CallGraph.hpp:17
One recorded runtime event used by the dynamic slicer.
Definition CallGraph.hpp:35