Skip to content

Commit

Permalink
Do not capture the backtrace for OutOfRange exception. (#194)
Browse files Browse the repository at this point in the history
Do not capture the backtrace for OutOfRange exception.

As the iterator moves beyond the end of the array or dict, the implementation will throw FleeceException with error code OutOfRange. This is necessary to handle the damaged iterator. However, we don't need to capture the backtrace in this case, which is very expensive as shown in a customer's performance profile, c.f. CBL 5044.
  • Loading branch information
jianminzhao authored Nov 10, 2023
1 parent afd3e21 commit e5115d3
Show file tree
Hide file tree
Showing 3 changed files with 119 additions and 2 deletions.
13 changes: 11 additions & 2 deletions Fleece/Support/FleeceException.cc
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,17 @@ namespace fleece {
:std::runtime_error(what)
,code(code_)
,err_no(errno_)
,backtrace(Backtrace::capture(2))
{ }
{
// The Fleece iterator (of array and dict) throws OutOfRange exception as one moves
// the iterator, operator++ or FLArrayIterator_Next, for instances, beyond the
// end of the array. Although the exception is caught inside Fleece and the
// client gets return of false, the effort to capture the backtrace can be
// very significant as shown in performance profilings. And, the backtrace is not
// used anyway.
if (code_ != OutOfRange) {
backtrace = Backtrace::capture(2);
}
}


__cold
Expand Down
106 changes: 106 additions & 0 deletions Tests/ValueTests.cc
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#define NOMINMAX
#endif

#include "fleece/Fleece.h"
#include "FleeceTests.hh"
#include "Value.hh"
#include "Pointer.hh"
Expand Down Expand Up @@ -232,4 +233,109 @@ namespace fleece {
docs.push_back(Doc::fromJSON("[]"));
}
}

TEST_CASE("Array Iterators") {
FLDoc doc = nullptr;
SECTION("Empty Array") {
doc = FLDoc_FromJSON("[]"_sl, nullptr);
}
SECTION("Non Empty Array") {
doc = FLDoc_FromJSON("[1]"_sl, nullptr);
}
REQUIRE(doc);

FLValue val = FLDoc_GetRoot(doc);
FLArray arr = FLValue_AsArray(val);
REQUIRE(arr);
Array::iterator iter = Array::iterator((Array*)arr);

bool caughtException = false;
bool capturedBacktrace = true;

// No exception with typical loop.
try {
for (; iter; ++iter);
} catch (...) {
caughtException = true;
}
CHECK(!caughtException);

caughtException = false;
// ++iter will throw if already at the end.
// OutOfRange exception should contain the backtrace.
try {
++iter;
} catch (const FleeceException& exc) {
if (exc.code == (int)kFLOutOfRange) {
caughtException = true;
capturedBacktrace = !!exc.backtrace;
}
}
CHECK((caughtException && !capturedBacktrace));

// FL Itarator
FLArrayIterator flIter;
FLArrayIterator_Begin(arr, &flIter);
FLValue value;
while (NULL != (value = FLArrayIterator_GetValue(&flIter))) {
FLArrayIterator_Next(&flIter);
}
// Calling Next is okay. It will trigger exception but we won't try to capture the backtrace.
CHECK(!FLArrayIterator_Next(&flIter));

FLDoc_Release(doc);
}

TEST_CASE("Dict Iterators") {
FLDoc doc = nullptr;
SECTION("Empty Dict") {
doc = FLDoc_FromJSON("{}"_sl, nullptr);
}
SECTION("Non Empty Dict") {
doc = FLDoc_FromJSON(R"({"key": 1})"_sl, nullptr);
}
REQUIRE(doc);

FLValue val = FLDoc_GetRoot(doc);
FLDict dict = FLValue_AsDict(val);
REQUIRE(dict);
Dict::iterator iter = Dict::iterator((Dict*)dict);

bool caughtException = false;
bool capturedBacktrace = true;

// No exception with typical loop pattern.
try {
for (; iter; ++iter);
} catch (...) {
caughtException = true;
}
CHECK(!caughtException);

caughtException = false;
// ++iter will throw if already at the end.
// OutOfRange exception should contain the backtrace.
try {
++iter;
} catch (const FleeceException& exc) {
if (exc.code == (int)kFLOutOfRange) {
caughtException = true;
capturedBacktrace = !!exc.backtrace;
}
}
CHECK((caughtException && !capturedBacktrace));

// FL Itarator
FLDictIterator flIter;
FLDictIterator_Begin(dict, &flIter);
FLValue value;
while (NULL != (value = FLDictIterator_GetValue(&flIter))) {
FLDictIterator_Next(&flIter);
}
// Calling Next is okay. It will trigger exception but we won't try to capture the backtrace.
// Cannot assert it directly. Internally, it uses Dict::iterator::operator++().
CHECK(!FLDictIterator_Next(&flIter));

FLDoc_Release(doc);
}
}
2 changes: 2 additions & 0 deletions Xcode/xcconfigs/FleeceTest.xcconfig
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,5 @@ MACOSX_DEPLOYMENT_TARGET = 10.14
CLANG_WARN__EXIT_TIME_DESTRUCTORS = NO
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) _LIBCPP_DEBUG=0
RUN_CLANG_STATIC_ANALYZER = NO

CODE_SIGNING_ALLOWED = NO

0 comments on commit e5115d3

Please sign in to comment.