Skip to content

Commit

Permalink
introduce suppressed exceptions. now maintain ignored exception from …
Browse files Browse the repository at this point in the history
…finally/defer block. close #768
  • Loading branch information
sekiguchi-nagisa committed Dec 9, 2024
1 parent 2313a41 commit cc0f5ae
Show file tree
Hide file tree
Showing 14 changed files with 137 additions and 102 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@
- change ``TERM_HOOK`` interface, now do not pass extra information (exit status, termination kind)
- also remove ``ON_ASSERT``, ``ON_ERR``, ``ON_EXIT`` constants
- now allow signal handler invocation
- **Breaking Change**: now not ignore exceptions from finally/defer block even if currently thrown
- maintain these exceptions and get theme via ``Throwable#suppressed`` method
- send ``SIGHUP`` to manged jobs before process termination (even if subshell)

#### Builtin
Expand Down
2 changes: 2 additions & 0 deletions doc/std.md
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,8 @@ function status(): Int for Throwable
function lineno(): Int for Throwable
function source(): String for Throwable
function suppressed(): [Throwable] for Throwable
```

## Error type
Expand Down
13 changes: 13 additions & 0 deletions src/builtin.h
Original file line number Diff line number Diff line change
Expand Up @@ -2198,6 +2198,19 @@ ARSH_METHOD error_source(RuntimeContext &ctx) {
RET(Value::createStr(source));
}

//!bind: function suppressed($this: Throwable): Array<Throwable>
ARSH_METHOD error_suppressed(RuntimeContext &ctx) {
SUPPRESS_WARNING(error_suppressed);
auto &suppressed = typeAs<ErrorObject>(LOCAL(0)).getSuppressed();
auto typeOrError = ctx.typePool.createArrayType(ctx.typePool.get(TYPE::Throwable));
assert(typeOrError);
std::vector<Value> values(suppressed.size());
for (unsigned int i = 0; i < suppressed.size(); i++) {
values[i] = suppressed[i];
}
RET(Value::create<ArrayObject>(*typeOrError.asOk(), std::move(values)));
}

// ################
// ## FD ##
// ################
Expand Down
1 change: 1 addition & 0 deletions src/constant.h
Original file line number Diff line number Diff line change
Expand Up @@ -505,6 +505,7 @@ constexpr size_t SYS_LIMIT_XTRACE_LINE_LEN = 128;
constexpr size_t SYS_LIMIT_JOB_TABLE_SIZE = UINT16_MAX;
constexpr size_t SYS_LIMIT_SUBSHELL_LEVEL = 1024;
constexpr size_t SYS_LIMIT_ERROR_MSG_MAX = UINT16_MAX; // for internal error message
constexpr size_t SYS_LIMIT_SUPPRESSED_EXCEPT_MAX = UINT8_MAX;
constexpr size_t SYS_LIMIT_STRING_MAX = INT32_MAX;
constexpr size_t SYS_LIMIT_ARRAY_MAX = INT32_MAX;
constexpr size_t SYS_LIMIT_KEY_BINDING_MAX = UINT8_MAX;
Expand Down
55 changes: 39 additions & 16 deletions src/object.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -749,6 +749,23 @@ bool CmdArgsBuilder::add(Value &&arg) {
// ## Error_Object ##
// ##########################

static void printMessage(FILE *fp, const ErrorObject &obj) {
auto ref = obj.getName().asStrRef();
fwrite(ref.data(), sizeof(char), ref.size(), fp);
ref = ": ";
fwrite(ref.data(), sizeof(char), ref.size(), fp);
ref = obj.getMessage().asStrRef();
fwrite(ref.data(), sizeof(char), ref.size(), fp);
fputc('\n', fp);
}

static void printStackTraceImpl(FILE *fp, const std::vector<StackTraceElement> &stackTrace) {
for (auto &s : stackTrace) {
fprintf(fp, " from %s:%d '%s()'\n", s.getSourceName().c_str(), s.getLineNum(),
s.getCallerName().c_str());
}
}

void ErrorObject::printStackTrace(const ARState &state, PrintOp op) const {
// print header
const auto level = state.subshellLevel();
Expand Down Expand Up @@ -782,29 +799,35 @@ void ErrorObject::printStackTrace(const ARState &state, PrintOp op) const {
break;
}
}

// print message (suppress error)
{
auto ref = state.typePool.get(this->getTypeID()).getNameRef();
fwrite(ref.data(), sizeof(char), ref.size(), stderr);
ref = ": ";
fwrite(ref.data(), sizeof(char), ref.size(), stderr);
ref = this->message.asStrRef();
fwrite(ref.data(), sizeof(char), ref.size(), stderr);
fputc('\n', stderr);
}

// print stack trace
for (auto &s : this->stackTrace) {
fprintf(stderr, " from %s:%d '%s()'\n", s.getSourceName().c_str(), s.getLineNum(),
s.getCallerName().c_str());
printMessage(stderr, *this); // FIXME: check io error ?
printStackTraceImpl(stderr, this->stackTrace);

// show suppressed exceptions
if (!this->suppressed.empty()) {
fputs("[note] the following exceptions are suppressed\n", stderr);
for (auto &e : this->suppressed) {
printMessage(stderr, *e);
printStackTraceImpl(stderr, e->getStackTrace());
}
}
if (op == PrintOp::IGNORED) {
fputs("\n", stderr);
}
fflush(stderr);
}

ObjPtr<ErrorObject> ErrorObject::addSuppressed(ObjPtr<ErrorObject> &&except) {
ObjPtr<ErrorObject> oldest;
if (reinterpret_cast<uintptr_t>(this) != reinterpret_cast<uintptr_t>(except.get())) {
if (this->suppressed.size() == SYS_LIMIT_SUPPRESSED_EXCEPT_MAX) {
oldest = std::move(this->suppressed[0]);
this->suppressed.erase(this->suppressed.begin());
}
this->suppressed.push_back(std::move(except));
}
return oldest;
}

ObjPtr<ErrorObject> ErrorObject::newError(const ARState &state, const Type &type, Value &&message,
int64_t status) {
std::vector<StackTraceElement> traces;
Expand Down
9 changes: 9 additions & 0 deletions src/object.h
Original file line number Diff line number Diff line change
Expand Up @@ -1067,6 +1067,7 @@ class ErrorObject : public ObjectWithRtti<ObjectKind::Error> {
Value name;
int64_t status;
std::vector<StackTraceElement> stackTrace;
std::vector<ObjPtr<ErrorObject>> suppressed;

public:
ErrorObject(const Type &type, Value &&message, Value &&name, int64_t status,
Expand Down Expand Up @@ -1096,6 +1097,14 @@ class ErrorObject : public ObjectWithRtti<ObjectKind::Error> {

const std::vector<StackTraceElement> &getStackTrace() const { return this->stackTrace; }

/**
* @param
* @return if suppressed except size reaches limit, remove and return oldest entry
*/
ObjPtr<ErrorObject> addSuppressed(ObjPtr<ErrorObject> &&except);

const auto &getSuppressed() const { return this->suppressed; }

/**
* create new Error_Object and create stack trace
*/
Expand Down
7 changes: 4 additions & 3 deletions src/vm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2038,8 +2038,9 @@ bool VM::mainLoop(ARState &state) {
auto entry = state.stack.exitFinally();
if (entry.hasError()) {
if (state.hasError()) {
auto e = state.stack.takeThrownObject();
e->printStackTrace(state, ErrorObject::PrintOp::IGNORED);
if (auto e = entry.asError()->addSuppressed(state.stack.takeThrownObject())) {
e->printStackTrace(state, ErrorObject::PrintOp::IGNORED);
}
}
state.stack.setThrownObject(entry.asError());
}
Expand Down Expand Up @@ -2617,7 +2618,7 @@ void VM::handleUncaughtException(ARState &state, ARError *dsError, bool inTermHo

// print error message
if (kind == AR_ERROR_KIND_RUNTIME_ERROR || kind == AR_ERROR_KIND_ASSERTION_ERROR ||
state.has(RuntimeOption::TRACE_EXIT) || inTermHook) {
state.has(RuntimeOption::TRACE_EXIT) || inTermHook || !except->getSuppressed().empty()) {
except->printStackTrace(state, inTermHook ? ErrorObject::PrintOp::IGNORED_TERM
: ErrorObject::PrintOp::UNCAUGHT);
}
Expand Down
34 changes: 26 additions & 8 deletions test/cmdline/cmdline_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -297,20 +297,38 @@ TEST_F(CmdlineTest, marker4) {
}
}

TEST_F(CmdlineTest, except) {
const char *msg = R"([warning]
the following exception within finally/defer block is ignored
TEST_F(CmdlineTest, except1) {
const char *msg = R"([runtime error]
AssertionFailed: `$false'
from (string):10 '<toplevel>()'
[note] the following exceptions are suppressed
SystemError: execution error: hoge: command not found
from (string):7 '<toplevel>()'
[warning]
the following exception within finally/defer block is ignored
ShellExit: terminated by exit 34
from (string):3 '<toplevel>()'
)";

[runtime error]
const char *script = R"(
defer {
call exit 34
}
defer {
hoge
}
assert $false
)";
ASSERT_NO_FATAL_FAILURE(this->expect(ds("-c", script), 1, "", msg));
}

TEST_F(CmdlineTest, except2) { // suppress its self
const char *msg = R"([runtime error]
AssertionFailed: `$false'
from (string):10 '<toplevel>()'
[note] the following exceptions are suppressed
ShellExit: terminated by exit 34
from (string):3 '<toplevel>()'
)";

const char *script = R"(
Expand All @@ -319,7 +337,7 @@ defer {
}
defer {
hoge
(function() => { if $true { throw $THROWN!;} })()
}
assert $false
Expand Down
2 changes: 1 addition & 1 deletion test/exec/cases/base/builtin_complete2.ds
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ $assertArray(['abs', 'toFloat'], $(complete 'var a = try { 34; } finally { retur
$assertArray(['abs', 'toFloat'], $(complete 'function f($a: Int) { $a.'))
$assertArray(['abs', 'toFloat'], $(complete 'var b = function ($a: Int) => $a.'))
$assertArray(['abs', 'toFloat'], $(complete 'typedef AAA($a : Int) { var b = $a.'))
$assertArray(['show', 'source', 'ss', 'ssum', 'status'], $(complete ' typedef ANY : Error; typedef INT : ANY
$assertArray(['show', 'source', 'ss', 'ssum', 'status', 'suppressed'], $(complete ' typedef ANY : Error; typedef INT : ANY
function ss() : String for ANY { return "$this"; } # also complete super type method
function ssum($a : Int) : String for INT {
return $this as String + $a
Expand Down
8 changes: 3 additions & 5 deletions test/exec/cases/output/thrown1.ds
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,9 @@ try {
# CHECK: ArithmeticError is thrown at 5
# CHECK: AssertionFailed is thrown at 17

# CHECKERR: [warning]
# CHECKERR: the following exception within finally/defer block is ignored
# CHECKERR: AssertionFailed: `! $THROWN'
# CHECKERR_RE: from .+/test/exec/cases/output/thrown1\.ds:17 '<toplevel>\(\)'
# CHECKERR_RE: ^$
# CHECKERR: [runtime error]
# CHECKERR: ArithmeticError: zero division
# CHECKERR_RE: from .+/test/exec/cases/output/thrown1\.ds:5 '<toplevel>\(\)'
# CHECKERR: [note] the following exceptions are suppressed
# CHECKERR: AssertionFailed: `! $THROWN'
# CHECKERR_RE: from .+/test/exec/cases/output/thrown1\.ds:17 '<toplevel>\(\)'
12 changes: 6 additions & 6 deletions test/exec/cases/output/thrown2.ds
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# RUN: exec $cmd --trace-exit $self
# RUN: exec $cmd $self
# STATUS: 56

function ff() {
Expand Down Expand Up @@ -30,17 +30,17 @@ try {
$THROWN!.message()[999]
}

# show ShellExit having suppressed exceptions even if trace-exit=off

# CHECK: in ff: ShellExit is thrown at 9
# CHECK: ShellExit is thrown at 9
# CHECK: OutOfRangeError is thrown at 30
# CHECK: in defer: ShellExit is thrown at 9

# CHECKERR: [warning]
# CHECKERR: the following exception within finally/defer block is ignored
# CHECKERR: OutOfRangeError: size is 21, but index is 999
# CHECKERR_RE: from .+/test/exec/cases/output/thrown2\.ds:30 '<toplevel>\(\)'
# CHECKERR_RE: ^$
# CHECKERR: [runtime error]
# CHECKERR: ShellExit: terminated by exit 56
# CHECKERR_RE: from .+/test/exec/cases/output/thrown2\.ds:9 'function ff\(\)'
# CHECKERR_RE: from .+/test/exec/cases/output/thrown2\.ds:18 '<toplevel>\(\)'
# CHECKERR: [note] the following exceptions are suppressed
# CHECKERR: OutOfRangeError: size is 21, but index is 999
# CHECKERR_RE: from .+/test/exec/cases/output/thrown2\.ds:30 '<toplevel>\(\)'
37 changes: 12 additions & 25 deletions test/exec/cases/output/thrown3.ds
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ function throw(e: Throwable?) {
}
}

defer { ## check suppressed error
$THROWN!.suppressed().clear() # not affect underlying data
assert $THROWN!.suppressed().size() == 2
assert $THROWN!.suppressed()[0].suppressed().empty()
assert $THROWN!.suppressed()[1].suppressed().empty()
}

defer {
defer {
Expand All @@ -34,33 +40,14 @@ defer {
# CHECK: rethrow AssertionFailed
# CHECK: rethrow ShellExit

# CHECKERR: [warning]
# CHECKERR: the following exception within finally/defer block is ignored
# CHECKERR: AssertionFailed: `$THROWN is ShellExit'
# CHECKERR: binary expression `<EXPR> is <TYPE>' is false
# CHECKERR: <EXPR>: ArithmeticError
# CHECKERR: <TYPE>: ShellExit
# CHECKERR_RE: from .+/test/exec/cases/output/thrown3\.ds:29 '<toplevel>\(\)'
# CHECKERR_RE: ^$
# CHECKERR: [warning]
# CHECKERR: the following exception within finally/defer block is ignored
# CHECKERR: [runtime error]
# CHECKERR: ArithmeticError: zero division
# CHECKERR_RE: from .+/test/exec/cases/output/thrown3\.ds:38 '<toplevel>\(\)'
# CHECKERR: [note] the following exceptions are suppressed
# CHECKERR: AssertionFailed: `$THROWN is ShellExit'
# CHECKERR: binary expression `<EXPR> is <TYPE>' is false
# CHECKERR: <EXPR>: ArithmeticError
# CHECKERR: <TYPE>: ShellExit
# CHECKERR_RE: from .+/test/exec/cases/output/thrown3\.ds:29 '<toplevel>\(\)'
# CHECKERR_RE: ^$
# CHECKERR: [warning]
# CHECKERR: the following exception within finally/defer block is ignored
# CHECKERR: ShellExit: terminated by exit 0
# CHECKERR_RE: from .+/test/exec/cases/output/thrown3\.ds:21 '<toplevel>\(\)'
# CHECKERR_RE: ^$
# CHECKERR: [warning]
# CHECKERR: the following exception within finally/defer block is ignored
# CHECKERR_RE: from .+/test/exec/cases/output/thrown3\.ds:35 '<toplevel>\(\)'
# CHECKERR: ShellExit: terminated by exit 0
# CHECKERR_RE: from .+/test/exec/cases/output/thrown3\.ds:21 '<toplevel>\(\)'
# CHECKERR_RE: ^$
# CHECKERR: [runtime error]
# CHECKERR: ArithmeticError: zero division
# CHECKERR_RE: from .+/test/exec/cases/output/thrown3\.ds:32 '<toplevel>\(\)'

# CHECKERR_RE: from .+/test/exec/cases/output/thrown3\.ds:27 '<toplevel>\(\)'
Loading

0 comments on commit cc0f5ae

Please sign in to comment.