@@ -72,10 +72,10 @@ invokeHelper(Value const& fn, dom::Array const& args)
7272 // Passing the options object would trigger expensive recursive conversion
7373 // of symbol contexts which contain circular references.
7474 std::vector<dom::Value> callArgs;
75- callArgs.reserve (args.size ());
76- for (std:: size_t i = 0 ; i + 1 < args.size () ; ++i )
75+ callArgs.reserve (args.size () - 1 );
76+ for (auto it = args. begin (); it != std::prev ( args.end ()) ; ++it )
7777 {
78- callArgs.push_back (args. at (i) );
78+ callArgs.push_back (*it );
7979 }
8080
8181 auto ret = fn.apply (callArgs);
@@ -1270,6 +1270,15 @@ makeObjectProxy(dom::Object obj, std::shared_ptr<Context::Impl> impl)
12701270 jerry_value_free (target);
12711271 jerry_value_free (handler);
12721272
1273+ // If proxy creation fails, return an empty object rather than an exception.
1274+ // The holder is cleaned up via the free_cb when holderRef (attached to
1275+ // handler) is garbage collected.
1276+ if (jerry_value_is_exception (proxy))
1277+ {
1278+ jerry_value_free (proxy);
1279+ return jerry_object ();
1280+ }
1281+
12731282 return proxy;
12741283}
12751284
@@ -1537,26 +1546,22 @@ resolveHelperFunction(
15371546 std::string_view name,
15381547 std::string_view script)
15391548{
1540- // Attempt to coerce user-provided helper source into a callable: first
1541- // evaluate directly, then as a parenthesized expression, then fall back to
1542- // a global lookup. This mirrors Handlebars' permissive helper loading while
1543- // still rejecting non-functions with clear errors. Note: first two paths
1544- // can execute side effects twice if the first expression is not a
1545- // function; callers rely on deterministic ordering.
1549+ // Coerce user-provided helper source into a callable. Resolution order:
1550+ //
1551+ // 1. Parenthesized eval - handles function declarations without side effects
1552+ // e.g., "function add(a,b) { return a+b; }" -> "(function add(a,b)...)"
1553+ //
1554+ // 2. Direct eval - handles IIFEs and expressions that return functions
1555+ // e.g., "(function(){ return function(){}; })()"
1556+ //
1557+ // 3. Global lookup - handles scripts that define globals
1558+ // e.g., "var helper = function(){}; helper;"
1559+ //
1560+ // This order minimizes side effects: parenthesized eval of a function
1561+ // declaration is pure, while direct eval may execute statements.
15461562 Error firstErr (" code did not evaluate to a function" );
15471563
1548- if (auto exp = scope.eval (script))
1549- {
1550- if (exp->isFunction ())
1551- {
1552- return *exp;
1553- }
1554- }
1555- else
1556- {
1557- firstErr = exp.error ();
1558- }
1559-
1564+ // Try parenthesized first (common case: function declarations)
15601565 std::string wrapped;
15611566 wrapped.reserve (script.size () + 2 );
15621567 wrapped.push_back (' (' );
@@ -1572,9 +1577,24 @@ resolveHelperFunction(
15721577 }
15731578 else
15741579 {
1575- return Unexpected (expr.error ());
1580+ firstErr = expr.error ();
1581+ }
1582+
1583+ // Try direct eval (IIFEs, expressions)
1584+ if (auto exp = scope.eval (script))
1585+ {
1586+ if (exp->isFunction ())
1587+ {
1588+ return *exp;
1589+ }
1590+ }
1591+ else if (firstErr.message () == " code did not evaluate to a function" )
1592+ {
1593+ // Keep the more informative error
1594+ firstErr = exp.error ();
15761595 }
15771596
1597+ // Fall back to global lookup
15781598 if (Value global = scope.getGlobalObject ())
15791599 {
15801600 Value candidate = global.get (name);
0 commit comments