mirror of
https://github.com/ChaiScript/ChaiScript.git
synced 2026-06-15 08:26:16 +08:00
Fix #633: Bound ChaiScript call stack to prevent native stack overflow
Recursive user-defined operators (e.g. a `string::/=` whose body calls itself through string interpolation) drove the AST evaluator into unbounded native recursion and crashed the host process with SIGSEGV. The dispatcher now refuses to enter a new function frame once `Stack_Holder::call_depth` reaches `chaiscript::max_call_depth` (default 256, overridable via the `CHAISCRIPT_MAX_CALL_DEPTH` macro) and throws the new `chaiscript::exception::stack_overflow_error` instead, letting both ChaiScript-level `try`/`catch` and C++ hosts recover from runaway recursion. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
d3c94e4451
commit
5cf5049549
@ -75,6 +75,14 @@ static_assert(_MSC_FULL_VER >= 190024210, "Visual C++ 2015 Update 3 or later req
|
|||||||
#define CHAISCRIPT_DEBUG false
|
#define CHAISCRIPT_DEBUG false
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// Upper bound on the depth of nested ChaiScript function calls. Hitting it
|
||||||
|
// causes the dispatcher to throw chaiscript::exception::stack_overflow_error
|
||||||
|
// instead of letting the native call stack overflow. Defining the macro on
|
||||||
|
// the command line overrides the default.
|
||||||
|
#ifndef CHAISCRIPT_MAX_CALL_DEPTH
|
||||||
|
#define CHAISCRIPT_MAX_CALL_DEPTH 256
|
||||||
|
#endif
|
||||||
|
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
@ -88,6 +96,9 @@ namespace chaiscript {
|
|||||||
constexpr static const char *compiler_name = CHAISCRIPT_COMPILER_NAME;
|
constexpr static const char *compiler_name = CHAISCRIPT_COMPILER_NAME;
|
||||||
constexpr static const bool debug_build = CHAISCRIPT_DEBUG;
|
constexpr static const bool debug_build = CHAISCRIPT_DEBUG;
|
||||||
|
|
||||||
|
constexpr static const int max_call_depth = CHAISCRIPT_MAX_CALL_DEPTH;
|
||||||
|
static_assert(max_call_depth > 0, "CHAISCRIPT_MAX_CALL_DEPTH must be a positive integer");
|
||||||
|
|
||||||
template<typename B, typename D, typename... Arg>
|
template<typename B, typename D, typename... Arg>
|
||||||
inline std::shared_ptr<B> make_shared(Arg &&...arg) {
|
inline std::shared_ptr<B> make_shared(Arg &&...arg) {
|
||||||
#ifdef CHAISCRIPT_USE_STD_MAKE_SHARED
|
#ifdef CHAISCRIPT_USE_STD_MAKE_SHARED
|
||||||
|
|||||||
@ -121,6 +121,19 @@ namespace chaiscript {
|
|||||||
global_non_const(const global_non_const &) = default;
|
global_non_const(const global_non_const &) = default;
|
||||||
~global_non_const() noexcept override = default;
|
~global_non_const() noexcept override = default;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Exception thrown when the ChaiScript call stack exceeds
|
||||||
|
/// chaiscript::max_call_depth, signalling runaway recursion before the
|
||||||
|
/// native stack can overflow.
|
||||||
|
class stack_overflow_error : public std::runtime_error {
|
||||||
|
public:
|
||||||
|
stack_overflow_error() noexcept
|
||||||
|
: std::runtime_error("Maximum call stack depth exceeded") {
|
||||||
|
}
|
||||||
|
|
||||||
|
stack_overflow_error(const stack_overflow_error &) = default;
|
||||||
|
~stack_overflow_error() noexcept override = default;
|
||||||
|
};
|
||||||
} // namespace exception
|
} // namespace exception
|
||||||
|
|
||||||
/// \brief Holds a collection of ChaiScript settings which can be applied to the ChaiScript runtime.
|
/// \brief Holds a collection of ChaiScript settings which can be applied to the ChaiScript runtime.
|
||||||
@ -1010,6 +1023,10 @@ namespace chaiscript {
|
|||||||
void save_function_params(const Function_Params &t_params) { save_function_params(*m_stack_holder, t_params); }
|
void save_function_params(const Function_Params &t_params) { save_function_params(*m_stack_holder, t_params); }
|
||||||
|
|
||||||
void new_function_call(Stack_Holder &t_s, Type_Conversions::Conversion_Saves &t_saves) {
|
void new_function_call(Stack_Holder &t_s, Type_Conversions::Conversion_Saves &t_saves) {
|
||||||
|
if (t_s.call_depth >= max_call_depth) {
|
||||||
|
throw chaiscript::exception::stack_overflow_error{};
|
||||||
|
}
|
||||||
|
|
||||||
if (t_s.call_depth == 0) {
|
if (t_s.call_depth == 0) {
|
||||||
m_conversions.enable_conversion_saves(t_saves, true);
|
m_conversions.enable_conversion_saves(t_saves, true);
|
||||||
}
|
}
|
||||||
|
|||||||
39
unittests/recursion_depth_protection.chai
Normal file
39
unittests/recursion_depth_protection.chai
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
// Regression test for issue #633: Stack-overflow due to infinite recursion
|
||||||
|
// in user-defined operator (string interpolation).
|
||||||
|
//
|
||||||
|
// Before the fix the recursive `/=` invocation triggered unbounded native
|
||||||
|
// recursion in the evaluator and crashed the host process with a SIGSEGV.
|
||||||
|
// The engine now bounds call_depth and throws a catchable exception
|
||||||
|
// instead of letting the native stack overflow.
|
||||||
|
|
||||||
|
def string::`/=`(double d) {
|
||||||
|
this = "${this/= 2}/=${d}";
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
var s = "o World"
|
||||||
|
var caught = false
|
||||||
|
var message = ""
|
||||||
|
|
||||||
|
try {
|
||||||
|
s /= 2
|
||||||
|
// unreachable: the recursive operator must abort with an exception
|
||||||
|
assert_true(false)
|
||||||
|
} catch (e) {
|
||||||
|
caught = true
|
||||||
|
message = e.what()
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_true(caught)
|
||||||
|
|
||||||
|
// The reported error must mention the call-stack overflow so users can
|
||||||
|
// distinguish it from an arbitrary script-level error.
|
||||||
|
assert_true(find(message, "call stack") != -1)
|
||||||
|
|
||||||
|
// A bounded recursion that stays below the limit must keep working.
|
||||||
|
def count_down(n) {
|
||||||
|
if (n <= 0) { return 0 }
|
||||||
|
return count_down(n - 1) + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_equal(50, count_down(50))
|
||||||
Loading…
x
Reference in New Issue
Block a user