1 module upromised.backtrace;
2 import core.runtime : Runtime, TraceHandler;
3 
4 struct BaseStack {
5 	Throwable.TraceInfo base;
6 	size_t delegate() skip;
7 }
8 
9 struct PromiseTraceHandler {
10 	TraceHandler original;
11 	BaseStack base;
12 }
13 
14 Throwable.TraceInfo dgTraceInfo(void delegate(void delegate(const(char[]) value, int* stop)) impl) nothrow {
15 	return new class Throwable.TraceInfo {
16 		override int opApply(scope int delegate(ref const(char[])) dg) const {
17 			return opApply((ref size_t, ref const(char[]) buf) {
18 				return dg(buf);
19 			});
20 		}
21 
22 		override int opApply(scope int delegate(ref size_t, ref const(char[])) dg) const {
23 			ulong count;
24 			impl((value, stop) {
25 				*stop = dg(count, value);
26 				++count;
27 			});
28 			return 0;
29 		}
30 
31 		override string toString() const {
32 			string bt;
33 			this.opApply((ref size_t a, ref const(char[]) b) {
34 				bt ~= b;
35 				bt ~= "\n";
36 				return 0;
37 			});
38 			return bt;
39 		}
40 	};
41 }
42 Throwable.TraceInfo traceinfo(string[] list) nothrow {
43 	return dgTraceInfo((cb) {
44 		int r;
45 		auto listIt = list;
46 		while (r == 0) {
47 			if (listIt.length == 0) return;
48 			cb(listIt[0], &r);
49 			listIt = listIt[1..$];
50 		}
51 	});
52 }
53 Throwable.TraceInfo concat(Throwable.TraceInfo a, Throwable.TraceInfo b) nothrow {
54 	import core.stdc.stdlib : abort;
55 	if (!(a !is null)) abort();
56 	if (!(b !is null)) abort();
57 	return dgTraceInfo((cb) {
58 		int r;
59 		a.opApply((ref const(char[]) v) {
60 			cb(v, &r);
61 			return r;
62 		});
63 		if (r == 0) b.opApply((ref const(char[]) v) {
64 			cb(v, &r);
65 			return r;
66 		});
67 	});
68 }
69 ulong length(Throwable.TraceInfo info) {
70 	import core.stdc.stdlib : abort;
71 
72 	if (info is null) abort();
73 
74 	size_t total;
75 	foreach(_; info) {
76 		total++;
77 	}
78 	return total;
79 }
80 
81 Throwable.TraceInfo trimTrail(Throwable.TraceInfo info, ulong amount) nothrow {
82 	return trimTrailDg(info, () => amount);
83 }
84 Throwable.TraceInfo trimTrailDg(Throwable.TraceInfo info, ulong delegate() amountDg) nothrow {
85 	import core.stdc.stdlib : abort;
86 	if (info is null) abort();
87 	if (amountDg.funcptr is null) abort();
88 
89 	return dgTraceInfo((cb) {
90 		size_t total = info.length;
91 		size_t amount = amountDg();
92 		size_t limit = total >= amount ? total - amount : 0;
93 		int r;
94 		info.opApply((ref const(char[]) v) {
95 			if (limit-- == 0) return 1;
96 			cb(v, &r);
97 			return r;
98 		});
99 	});
100 }
101 Throwable.TraceInfo skipMagic(Throwable.TraceInfo a, string magic) nothrow {
102 	import core.stdc.stdlib : abort;
103 	import std.algorithm : countUntil;
104 
105 	if (a is null) abort();
106 
107 	magic = magic[1..$];
108 	return dgTraceInfo((cb) {
109 		bool skipping = true;
110 		int r;
111 		a.opApply((ref const(char[]) v) {
112 			if (skipping) {
113 				if (v.countUntil(magic) >= 0) {
114 					skipping = false;
115 				}
116 				return 0;
117 			} else {
118 				cb(v, &r);
119 				return r;
120 			}
121 		});
122 		if (skipping) {
123 			a.opApply((ref const(char[]) v) {
124 				cb(v, &r);
125 				return r;
126 			});
127 		}
128 	});
129 }
130 
131 private __gshared PromiseTraceHandler handler;
132 enum handlerFuncMagic = "___98371_realBacktraceMagic_31810";
133 pragma(mangle, handlerFuncMagic) Throwable.TraceInfo handlerFunc(void* pos) {
134 	import core.runtime : defaultTraceHandler;
135 
136 	return realBacktrace(pos, handlerFuncMagic).trimTrailDg(handler.base.skip).concat(handler.base.base);
137 }
138 
139 static this() {
140 	import core.runtime : defaultTraceHandler;
141 
142 	handler.base.base = [].traceinfo;
143 	handler.base.skip = () => 0;
144 	handler.original = &defaultTraceHandler;
145 	Runtime.traceHandler(&handlerFunc);
146 }
147 
148 enum realBacktraceMagic = "___38912_realBacktrace_32191";
149 pragma(mangle, realBacktraceMagic) pragma(inline, false) Throwable.TraceInfo realBacktrace(void* pos, string skipMagicString = realBacktraceMagic) nothrow {
150 	return wa1(pos).skipMagic(skipMagicString);
151 }
152 // Dlang skip a few frames to remove internal function from stack traces. But sometimes this does not work.
153 // Using a magic name to remove the internal function always works.
154 pragma(inline, false) Throwable.TraceInfo wa1(void* pos) nothrow {
155 	return wa2(pos);
156 }
157 pragma(inline, false) Throwable.TraceInfo wa2(void* pos) nothrow {
158 	return wa3(pos);
159 }
160 pragma(inline, false) Throwable.TraceInfo wa3(void* pos) nothrow {
161 	return wa4(pos);
162 }
163 pragma(inline, false) Throwable.TraceInfo wa4(void* pos) nothrow {
164 	try {
165 		return handler.original(pos);
166 	} catch(Exception e) {
167 		assert(false);
168 	}
169 }
170 
171 BaseStack setBasestack(Throwable.TraceInfo backBt) nothrow {
172 	import std.array : array;
173 
174 	auto prev = handler.base;
175 	handler.base.base = backBt;
176 	auto skip = realBacktrace(null);
177 	try {
178 		handler.base.skip = () {
179 			auto l = skip.length;
180 			if (l == 0) return 0;
181 			return skip.length - 1;
182 		};
183 	} catch(Exception) {
184 		assert(false);
185 	}
186 	return prev;
187 }
188 
189 void recoverBasestack(BaseStack a) nothrow {
190 	handler.base = a;
191 }
192 
193 enum backtrace_magic = "892179_backtrace_381243";
194 pragma(mangle, backtrace_magic) Throwable.TraceInfo backtrace(void* pos = null, string skipMagicValue = backtrace_magic) nothrow {
195 	try {
196 		return Runtime.traceHandler()(pos).skipMagic(skipMagicValue);
197 	} catch(Exception) {
198 		assert(false);
199 	}
200 }