Skip to content

Commit 8c88a11

Browse files
First commit
1 parent f2f704b commit 8c88a11

File tree

1 file changed

+358
-0
lines changed
  • tests/fixtures/zsh-app/target/debug/legacy

1 file changed

+358
-0
lines changed
Lines changed: 358 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,358 @@
1+
#!/usr/bin/env zsh
2+
3+
#############################
4+
# Internal helper functions #
5+
#############################
6+
7+
###
8+
# Output help text
9+
###
10+
function _crash_usage() {
11+
echo "\033[0;33mUsage:\033[0;m"
12+
echo " crash [options] <command>"
13+
echo
14+
echo "\033[0;33mOptions:\033[0;m"
15+
echo " -h, --help Output this help text and exit"
16+
echo " -v, --version Output version information and exit"
17+
echo
18+
echo "\033[0;33mCommands:\033[0;m"
19+
echo " register Register the global error handler"
20+
}
21+
22+
###
23+
# Map a process exit code to its signal name
24+
###
25+
function _crash_map_exit_code() {
26+
local sig_name code="$1"
27+
28+
case $code in
29+
# is this a signal name (error code = signal + 128) ?
30+
129) sig_name=HUP ;;
31+
130) sig_name=INT ;;
32+
131) sig_name=QUIT ;;
33+
132) sig_name=ILL ;;
34+
134) sig_name=ABRT ;;
35+
136) sig_name=FPE ;;
36+
137) sig_name=KILL ;;
37+
139) sig_name=SEGV ;;
38+
141) sig_name=PIPE ;;
39+
143) sig_name=TERM ;;
40+
41+
# usual exit codes
42+
-1) sig_name=FATAL ;;
43+
# Miscellaneous errors, such as "divide by zero"
44+
1) sig_name=WARN ;;
45+
# misuse of shell builtins (pretty rare)
46+
2) sig_name=BUILTINMISUSE ;;
47+
# cannot invoke requested command (ex : source script_with_syntax_error)
48+
126) sig_name=CCANNOTINVOKE ;;
49+
# command not found (ex : source script_not_existing)
50+
127) sig_name=CNOTFOUND ;;
51+
52+
# assuming we are on an x86 system here
53+
# this MIGHT get annoying since these are in a range of exit codes
54+
# programs sometimes use.... we'll see.
55+
19) sig_name=STOP ;;
56+
20) sig_name=TSTP ;;
57+
21) sig_name=TTIN ;;
58+
22) sig_name=TTOU ;;
59+
60+
# Exit codes used internally by crash
61+
72) sig_name=EXCEPTION ;;
62+
63+
# Catch all - we have no idea what happened
64+
*) sig_name=FATAL ;;
65+
esac
66+
67+
echo $sig_name
68+
}
69+
70+
###
71+
# Format and output trace info
72+
###
73+
function _crash_format_trace_info() {
74+
local tracetype="$1"
75+
local -a str; str=("${(s/:/)2}")
76+
local node=${str[1]} line=${str[2]} color
77+
78+
if [[ $tracetype = 'file' ]]; then
79+
if builtin type realpath >/dev/null 2>&1; then
80+
node=$(realpath $node)
81+
fi
82+
color='\033[0;35m'
83+
fi
84+
85+
if [[ $tracetype = 'function' ]]; then
86+
color='\033[1;33m'
87+
fi
88+
89+
echo "$color$node\033[0;m \033[0;38;5;242m:\033[0;m line $line"
90+
}
91+
92+
###
93+
# A function which throws an exception if it hasn't been caught
94+
###
95+
function _crash_catch_all_exception() {
96+
if [[ ${2:0:5} != 'catch' ]]; then
97+
preexec_functions=()
98+
throw "$CRASH_THROWN_EXCEPTION" "$CRASH_THROWN_EXCEPTION_MESSAGE"
99+
fi
100+
}
101+
102+
###
103+
# A handler for catching global uncaught exceptions
104+
# and other exit codes
105+
###
106+
function _crash_global_exception_handler() {
107+
# Get the state passed to the handler
108+
local state="$1"
109+
110+
# We only want to report errors
111+
[[ $state -eq 0 ]] && exit 0
112+
113+
# Retrieve exception details stored by crash
114+
local exception="$CRASH_THROWN_EXCEPTION"
115+
local message="$CRASH_THROWN_EXCEPTION_MESSAGE"
116+
local -a trace; trace=($CRASH_FUNCTRACE)
117+
local -a files; files=($CRASH_FUNCFILETRACE)
118+
119+
# If no trace is found, revert to ZSH's builtin $functrace
120+
[[ ${#trace} -eq 0 ]] && trace=($functrace)
121+
[[ ${#files} -eq 0 ]] && files=($funcfiletrace)
122+
123+
# Get the signal name for the state
124+
sig_name="$(_crash_map_exit_code $state)"
125+
126+
# Print the signal name as a header
127+
echo
128+
echo " \033[1;37;41m $sig_name \033[0;m"
129+
130+
# If an exception is defined, print it and its message
131+
if [[ -n $exception ]]; then
132+
echo
133+
echo " \033[1;31m$exception\033[0;m"
134+
echo " \033[0;33m$message\033[0;m"
135+
fi
136+
137+
# Print the function and file/line where the exception was thrown
138+
echo
139+
echo " in $(_crash_format_trace_info 'function' ${trace[1]})"
140+
echo " at $(_crash_format_trace_info 'file' ${files[1]})"
141+
142+
# Loop backwards through the trace, printing the function
143+
# and file/line for each step
144+
integer i=1
145+
while [[ ${#trace} -gt 1 ]]; do
146+
shift trace
147+
shift files
148+
149+
echo
150+
echo " \033[0;38;5;242m$i\033[0;m $(_crash_format_trace_info 'function' ${trace[1]})"
151+
echo " $(_crash_format_trace_info 'file' ${files[1]})"
152+
153+
i=$(( i + 1 ))
154+
done
155+
156+
# Exit with the same state as reported, to ensure any
157+
# programs relying on the exit state receive the right one
158+
exit $state
159+
}
160+
161+
##################################
162+
# Public facing helper functions #
163+
##################################
164+
165+
###
166+
# Execute a command in a subprocess, and record any exceptions
167+
# which are thrown so that they can be caught later
168+
###
169+
function try() {
170+
local state
171+
172+
# Check if an exception is already recorded and clear it
173+
# so that we have a blank slate
174+
if [[ -n $CRASH_THROWN_EXCEPTION ]]; then
175+
CRASH_THROWN_EXCEPTION=''
176+
CRASH_THROWN_EXCEPTION_MESSAGE=''
177+
fi
178+
179+
# If the crash global error handler is set, remove it
180+
# while we handle anything returned from this function
181+
local global_handler_set=0
182+
if (( $+functions[zshexit] )); then
183+
global_handler_set=1
184+
unfunction zshexit
185+
fi
186+
187+
# Run the passed command so that the crash exit state
188+
# can be caught
189+
output=$($@)
190+
state=$?
191+
192+
# The exit wasn't caused by a crash exception, so unset any saved exception
193+
# and message, and call the global error handler directly
194+
if [[ $state -ne 72 ]]; then
195+
CRASH_THROWN_EXCEPTION=''
196+
CRASH_THROWN_EXCEPTION_MESSAGE=''
197+
198+
# Print the output back to the screen
199+
echo $output
200+
201+
# Call the globa error handler
202+
_crash_global_exception_handler $state
203+
fi
204+
205+
# Ensure the crash global error handler is re-registered if necessary
206+
[[ -n $global_handler_set ]] && crash register
207+
208+
# Check for the crash exit state, and parse the output to retrieve
209+
# the exception and message so that catch can handle them
210+
if [[ $state -eq 72 ]]; then
211+
# First separate the output into an array of lines
212+
local IFS
213+
local -a lines; IFS=$'\n' lines=($(echo "$output"))
214+
215+
# Get the line count
216+
integer line_count=${#lines}
217+
218+
# Get the last two lines of output
219+
# Since throw was used the exeption and message are printed here
220+
CRASH_THROWN_EXCEPTION="${lines[-2]}"
221+
CRASH_THROWN_EXCEPTION_MESSAGE="${lines[-1]}"
222+
223+
# The remaining output all came from the subprocess, so we print
224+
# that as if it had been run on the main thread
225+
echo "${(@F)lines:0:$(( line_count - 2 ))}"
226+
227+
# Store the functrace and funcfiletrace so that they can be used
228+
# if the exception is not caught
229+
CRASH_FUNCTRACE=($functrace)
230+
CRASH_FUNCFILETRACE=($funcfiletrace)
231+
232+
# Add a preexec hook, which will execute the error handler for
233+
# the exception if it is not caught
234+
add-zsh-hook preexec _crash_catch_all_exception
235+
fi
236+
}
237+
238+
###
239+
# Catch a thrown exception
240+
###
241+
function catch() {
242+
local exception="$1" handler="$2"
243+
244+
# If no exception has been thrown, then no further processing is required
245+
if [[ $CRASH_THROWN_EXCEPTION != $exception ]]; then
246+
return
247+
fi
248+
249+
# Great! We've caught the right exception. Now we can remove the
250+
# preexec zsh-hook so that the catch-all handler doesn't kick in
251+
preexec_functions=()
252+
253+
# Check that a handler has been passed to catch
254+
if [[ -z $handler ]]; then
255+
throw MissingHandlerException "catch must be passed a handler function"
256+
fi
257+
258+
# Check that the handler exists as a function
259+
if ! (( $+functions[$handler] )); then
260+
throw MissingHandlerException "Handler function '$handler' could not be found"
261+
fi
262+
263+
# Execute the handler, passing it the exception and message
264+
$handler $CRASH_THROWN_EXCEPTION $CRASH_THROWN_EXCEPTION_MESSAGE
265+
266+
CRASH_THROWN_EXCEPTION=''
267+
CRASH_THROWN_EXCEPTION_MESSAGE=''
268+
CRASH_FUNCTRACE=()
269+
CRASH_FUNCFILETRACE=()
270+
}
271+
272+
###
273+
# Throw an exception
274+
###
275+
function throw() {
276+
local exception="$1" message="${(@)@:2}"
277+
278+
# Store the exception, message and traces in their respective global variables
279+
CRASH_THROWN_EXCEPTION="$exception"
280+
CRASH_THROWN_EXCEPTION_MESSAGE="$message"
281+
CRASH_FUNCTRACE=($functrace)
282+
CRASH_FUNCFILETRACE=($funcfiletrace)
283+
284+
# Check for the global crash error handler. If it is set, then we don't
285+
# need to output the exception and message, as they can be read from
286+
# the variables we set above. If the global handler is not set, then we
287+
# are probably in a try/catch block, so print them here so that they
288+
# can be picked up by the subprocess launched by try
289+
if ! (( $+functions[zshexit] )); then
290+
echo $CRASH_THROWN_EXCEPTION
291+
echo $CRASH_THROWN_EXCEPTION_MESSAGE
292+
fi
293+
294+
exit 72
295+
}
296+
297+
###
298+
# Register the global variables and exception handler
299+
###
300+
function _crash_register() {
301+
# Autoload preexec hook
302+
autoload -Uz add-zsh-hook
303+
304+
# Initialise variables used by the exception handler
305+
export CRASH_THROWN_EXCEPTION=''
306+
export CRASH_THROWN_EXCEPTION_MESSAGE=''
307+
export -a CRASH_FUNCTRACE
308+
export -a CRASH_FUNCFILETRACE
309+
310+
# Run the exception hander after any exit signal is received
311+
zshexit() {
312+
_crash_global_exception_handler $?
313+
}
314+
}
315+
316+
function _crash_unload() {
317+
# Remove the catch all exception in case we are
318+
# in the middle of a catch block
319+
add-zsh-hook -D preexec _crash_catch_all_exception
320+
321+
# Remove the global exception handler
322+
zshexit() {}
323+
}
324+
325+
###
326+
# The main process
327+
###
328+
function crash() {
329+
local help version ctx="$1"
330+
331+
zparseopts -D \
332+
h=help -help=help \
333+
v=version -version=version
334+
335+
if [[ -n $help ]]; then
336+
_crash_usage
337+
return
338+
fi
339+
340+
if [[ -n $version ]]; then
341+
echo 0.1.2
342+
return
343+
fi
344+
345+
case $ctx in
346+
'register' )
347+
_crash_register "${(@)@:2}"
348+
;;
349+
'unload' )
350+
_crash_unload
351+
;;
352+
* )
353+
throw UnrecognisedCommandError "The command '$1' is unrecognised"
354+
;;
355+
esac
356+
}
357+
358+
crash "$@"

0 commit comments

Comments
 (0)