Skip to content

Commit d1f0080

Browse files
wsmosesmaleadt
andauthored
Avoid recursion in backtrace lookup to avoid stack overflows (#253)
Co-authored-by: Tim Besard <[email protected]>
1 parent 5762f7c commit d1f0080

File tree

1 file changed

+42
-32
lines changed

1 file changed

+42
-32
lines changed

src/debug.jl

Lines changed: 42 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -6,45 +6,55 @@
66
# sites of the containing function. if there's only one, repeat the process from that call.
77
# finally, the debug information is converted to a Julia stack trace.
88
function backtrace(inst::LLVM.Instruction, bt = StackTraces.StackFrame[])
9-
# look up the debug information from the current instruction
10-
if haskey(metadata(inst), LLVM.MD_dbg)
11-
loc = metadata(inst)[LLVM.MD_dbg]
12-
while loc !== nothing
13-
scope = LLVM.scope(loc)
14-
if scope !== nothing
15-
name = replace(LLVM.name(scope), r";$"=>"")
16-
file = LLVM.file(scope)
17-
path = joinpath(LLVM.directory(file), LLVM.filename(file))
18-
line = LLVM.line(loc)
19-
push!(bt, StackTraces.StackFrame(name, path, line))
9+
done = Set{LLVM.Instruction}()
10+
while true
11+
if in(inst, done)
12+
break
13+
end
14+
push!(done, inst)
15+
16+
# look up the debug information from the current instruction
17+
if haskey(metadata(inst), LLVM.MD_dbg)
18+
loc = metadata(inst)[LLVM.MD_dbg]
19+
while loc !== nothing
20+
scope = LLVM.scope(loc)
21+
if scope !== nothing
22+
name = replace(LLVM.name(scope), r";$"=>"")
23+
file = LLVM.file(scope)
24+
path = joinpath(LLVM.directory(file), LLVM.filename(file))
25+
line = LLVM.line(loc)
26+
push!(bt, StackTraces.StackFrame(name, path, line))
27+
end
28+
loc = LLVM.inlined_at(loc)
2029
end
21-
loc = LLVM.inlined_at(loc)
2230
end
23-
end
2431

25-
# move up the call chain
26-
f = LLVM.parent(LLVM.parent(inst))
27-
## functions can be used as a *value* in eg. constant expressions, so filter those out
28-
callers = filter(val -> isa(user(val), LLVM.CallInst), collect(uses(f)))
29-
## get rid of calls without debug info
30-
filter!(callers) do call
31-
md = metadata(user(call))
32-
haskey(md, LLVM.MD_dbg)
33-
end
34-
if !isempty(callers)
35-
# figure out the call sites of this instruction
36-
call_sites = unique(callers) do call
37-
# there could be multiple calls, originating from the same source location
32+
# move up the call chain
33+
f = LLVM.parent(LLVM.parent(inst))
34+
## functions can be used as a *value* in eg. constant expressions, so filter those out
35+
callers = filter(val -> isa(user(val), LLVM.CallInst), collect(uses(f)))
36+
## get rid of calls without debug info
37+
filter!(callers) do call
3838
md = metadata(user(call))
39-
md[LLVM.MD_dbg]
39+
haskey(md, LLVM.MD_dbg)
4040
end
41+
if !isempty(callers)
42+
# figure out the call sites of this instruction
43+
call_sites = unique(callers) do call
44+
# there could be multiple calls, originating from the same source location
45+
md = metadata(user(call))
46+
md[LLVM.MD_dbg]
47+
end
4148

42-
if length(call_sites) > 1
43-
frame = StackTraces.StackFrame("multiple call sites", "unknown", 0)
44-
push!(bt, frame)
45-
elseif length(call_sites) == 1
46-
backtrace(user(first(call_sites)), bt)
49+
if length(call_sites) > 1
50+
frame = StackTraces.StackFrame("multiple call sites", "unknown", 0)
51+
push!(bt, frame)
52+
elseif length(call_sites) == 1
53+
inst = user(first(call_sites))
54+
continue
55+
end
4756
end
57+
break
4858
end
4959

5060
return bt

0 commit comments

Comments
 (0)