aboutsummaryrefslogtreecommitdiff

Problem: Repeated printing of (parts of) the stack trace

Gist

  • With subshells, several stack traces may be printed.
  • All but the first stack trace can be ignored.

Problematic behaviour

  • For each subshell in the current stack of subshells (including the root shell), we get a stack trace.
    • The ERR trap is caught for each subshell.
    • Reason: The trap on ERR returns non-zero, and so does the subshell.
      • If it returned zero, the error would be ignored on the upper level, and program execution continue, which is undesired.
  • More precisely, each subshell gives a stack trace on the stack from itself up to the root shell.
    • Reason: Subshells inherit the knowledge of its ancestors, but do not know of its descendant subshells.
    • Thus, the closer to the root of the stack, the more often we get the (same) information printed; while the information on where the error originally occurred is printed only once.

Desired behaviour

  • The whole stack trace is printed once, and nothing more.
    • This would be achieved if only the subshell where the error originally occurred were to print a stack trace.

Considerations on fixes / improvements

Only print the stack trace for the root shell.

  • This is easy, just check for [[ $BASH_SUBSHELL -eq 0 ]].
  • This would mean that on error within a subshell, we do not get information below where the first subshell was invoked.
  • This may be deemed acceptable if subshells are rarely used and/or only with short local code within.
    • Short local code would be, e.g., $(head -n 1 FILE).
    • Short local code would not be, e.g., $(local_nontrivial_function).

Reserve a special exit code.

  • Let $SPECIAL_EXIT_CODE be some exit code distinct from 0 and 1.
  • Change ERR trap to trap 'basic::on_error 0' ERR, and define on_error() as follows: function basic::on_error() { [[ $? -eq $SPECIAL_EXIT_CODE ]] && exit 1 local -ri offset="$1" basic::print_stacktrace $((offset + 1)) exit $SPECIAL_EXIT_CODE }
  • The idea is that on_error() only prints the stack trace in the lowest subshell, which has the full stack trace.
  • Naturally, this does not work properly if the original actual error had $SPECIAL_EXIT_CODE as exit code.
  • That is, we'd need an exit code that cannot occur anywhere else.
    • This should be impossible in the general case.

Use a temporary file

  • Use a temporary file to indicate whether print_stacktrace() was already called in a subshell.
  • This feels somewhat evil.

Inspect $BASH_COMMAND

  • The $BASH_COMMAND variable contains the command executed that caused the trap.
  • We'd have to identify whether the command spawned a subshell (and the error came from there).
  • Note that ( is a valid command name---but $BASH_COMMAND maintains any necessary quoting.
  • It might suffice to check for (.* and ${varname_regex}=\$(.*, given the other self-imposed restrictions.
    • Note, though, that there may be several assignments on a single line, and thus within $BASH_COMMAND.