From 446379d2ba196f5070e0838cdf9ca3e10280e532 Mon Sep 17 00:00:00 2001 From: KBS Date: Thu, 10 Sep 2026 15:03:56 +0900 Subject: [PATCH] Throw instead of trapping on modulo by zero (#347) Op::Modulo was the only arithmetic operator in the switch without a zero-divisor guard, so {{ a % b }} raised SIGFPE and aborted the host process. Op::Division already throws at renderer.hpp:307 and Op::DivisibleBy already checks at :359. Guard the converted divisor, which is what DivisibleBy does, so a fractional divisor that truncates to zero is caught too. --- include/inja/renderer.hpp | 6 +++++- single_include/inja/inja.hpp | 6 +++++- test/test-functions.cpp | 4 ++++ 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/include/inja/renderer.hpp b/include/inja/renderer.hpp index 61b6ff0..5174bbe 100644 --- a/include/inja/renderer.hpp +++ b/include/inja/renderer.hpp @@ -321,7 +321,11 @@ class Renderer : public NodeVisitor { } break; case Op::Modulo: { const auto args = get_arguments<2>(node); - make_result(args[0]->get() % args[1]->get()); + const auto divisor = args[1]->get(); + if (divisor == 0) { + throw_renderer_error("modulo by zero", node); + } + make_result(args[0]->get() % divisor); } break; case Op::AtId: { const auto container = get_arguments<1, 0, false>(node)[0]; diff --git a/single_include/inja/inja.hpp b/single_include/inja/inja.hpp index bf18089..1a36521 100644 --- a/single_include/inja/inja.hpp +++ b/single_include/inja/inja.hpp @@ -2479,7 +2479,11 @@ class Renderer : public NodeVisitor { } break; case Op::Modulo: { const auto args = get_arguments<2>(node); - make_result(args[0]->get() % args[1]->get()); + const auto divisor = args[1]->get(); + if (divisor == 0) { + throw_renderer_error("modulo by zero", node); + } + make_result(args[0]->get() % divisor); } break; case Op::AtId: { const auto container = get_arguments<1, 0, false>(node)[0]; diff --git a/test/test-functions.cpp b/test/test-functions.cpp index 4208cfe..aae1dd2 100644 --- a/test/test-functions.cpp +++ b/test/test-functions.cpp @@ -36,11 +36,15 @@ TEST_CASE("functions") { CHECK(env.render("{{ 1 + 1 * 3 }}", data) == "4"); CHECK(env.render("{{ (1 + 1) * 3 }}", data) == "6"); CHECK(env.render("{{ 5 / 2 }}", data) == "2.5"); + CHECK(env.render("{{ 5 % 2 }}", data) == "1"); + CHECK(env.render("{{ 7 % -2 }}", data) == "1"); CHECK(env.render("{{ 5^3 }}", data) == "125"); CHECK(env.render("{{ 5 + 12 + 4 * (4 - (1 + 1))^2 - 75 * 1 }}", data) == "-42"); CHECK_THROWS_WITH(env.render("{{ +1 }}", data), "[inja.exception.parser_error] (at 1:7) too few arguments"); CHECK_THROWS_WITH(env.render("{{ 1 + }}", data), "[inja.exception.parser_error] (at 1:8) too few arguments"); + CHECK_THROWS_WITH(env.render("{{ 5 % 0 }}", data), "[inja.exception.render_error] (at 1:6) modulo by zero"); + CHECK_THROWS_WITH(env.render("{{ 5 % 0.5 }}", data), "[inja.exception.render_error] (at 1:6) modulo by zero"); } SUBCASE("upper") {