01558: pmwiki.php(336) : eval()'d code

Summary: pmwiki.php(336) : eval()'d code
Created: 2026-07-12 10:20
Status: InProgress
Category: Bug
From: simon
Assigned:
Priority:
Version: 2.6.0
OS:

Description: I'm seeing

PHP Parse error: Unmatched ')' in D:\Home\KiwiWiki\pmwiki\pmwiki.php(336) : eval()'d code on line 1

when this page is opened.

I'm unclear as to the cause of this, and am struggling to trace it back to a cause (probably me, so next step is to disable all recipes to see which one might be causing it. OTOH I'm not sure it is not caused by markup on a page either).

But I suggest that perhaps if possible eval'd code should be protected by try catch to increase resiliency.

simon

See Troubleshooting on how to track bugs. Line 336 is may be related to a custom conditional. Petko

A try-catch block will not allow the script to continue.

In PHP, a Parse error (like an unmatched parenthesis) is thrown at compile-time when PHP first tries to read the code, rather than at runtime when the code is actually executing.

Because eval() compiles the string you pass to it before running it, a syntax error inside eval() triggers a fatal compile-time parse error that halts the execution of the entire script immediately, bypassing any try-catch blocks you have set up.

When eval() encounters a syntax/parse error, it emits a traditional fatal diagnostic error before an exception can even be thrown. The engine gives up right there.

PmWiki's logic is, let it fail immediately and allow for the admin to fix it rather than catch and hide it.

My plan is to allow for the PageVar, the MarkupExpressions and the Conditionals to be reworked to not use eval() but it is a lot of work, and not obvious how to achieve it (but at least the core-based ones are a finite number). Petko


Thanks for that.
I've inserted temporarily in pmwiki.php

  #return eval("return( $code );");
  return PMW_safeEval("return( $code );");
}
function PMW_safeEval($code) {
    global $pagename;
    # set the error handler for non-fatal notices
    set_error_handler(function($errno, $errstr) use ($code) {
        error_log("eval exception: $errstr");
        error_log("eval code: \"$code\"");
	error_log("call stack: from $pagename\n" . $e->getTraceAsString());
        return false; # let normal PHP logging happen too if configured
    });
    try {
        $result = eval($code);
    } catch (\ParseError $e) {
        # catches syntax errors
        error_log("eval ParseError: " . $e->getMessage());
        error_log("eval code: \"$code\"");
	error_log("call stack: from $pagename\n" . $e->getTraceAsString());
        $result = null; # fallback value
    } catch (\Throwable $e) {
        # catch other execution errors
        error_log("eval RuntimeException: " . $e->getMessage());
        error_log("eval code: \"$code\"");
	error_log("call stack: from $pagename\n" . $e->getTraceAsString());
        $result = null; # fallback value
    } finally { # always restore the error handler
        restore_error_handler();
    }
    return $result;
} # end PMW_safeEval 

and now obtain the output

[13-Jul-2026 08:21:09 Pacific/Auckland] eval ParseError: Unmatched ')'
[13-Jul-2026 08:21:09 Pacific/Auckland] eval code: "return( 1 )   AND 1 );"
[13-Jul-2026 08:21:09 Pacific/Auckland] call stack:
#0 D:\Home\KiwiWiki\pmwiki\pmwiki.php(337): PMW_safeEval()
#1 D:\Home\KiwiWiki\pmwiki\scripts\stdmarkup.php(124) : eval()'d code(1): CondExpr()
#2 D:\Home\KiwiWiki\pmwiki\scripts\stdmarkup.php(124): eval()
#3 D:\Home\KiwiWiki\pmwiki\scripts\stdmarkup.php(106): CondText2()
#4 [internal function]: MarkupCondText2()
#5 D:\Home\KiwiWiki\pmwiki\scripts\stdmarkup.php(126): preg_replace_callback()
#6 D:\Home\KiwiWiki\pmwiki\scripts\stdmarkup.php(106): CondText2()
#7 [internal function]: MarkupCondText2()
#8 D:\Home\KiwiWiki\pmwiki\scripts\stdmarkup.php(122): preg_replace_callback()
#9 D:\Home\KiwiWiki\pmwiki\scripts\stdmarkup.php(106): CondText2()
#10 [internal function]: MarkupCondText2()
#11 D:\Home\KiwiWiki\pmwiki\pmwiki.php(2761): preg_replace_callback()
#12 D:\Home\KiwiWiki\pmwiki\pmwiki.php(2815): MarkupToHTML()
#13 D:\Home\KiwiWiki\pmwiki\pmwiki.php(628): HandleBrowse()
#14 D:\Home\KiwiWiki\pmwiki\pmwiki.php(609): HandleDispatch()
#15 {main} 

I have pretty good visibility of $Conditions here thanks to PmWikiInfo and nothing looks out of the ordinary
Here is the problem, please uncomment to view

(:RefP::)
(:if5 expr (! empty {*$:RefP}) AND (! equal "{*$:RefP}" "Wikipedia:"):) '^[[{*$:RefP}|#]]^'(:if5end:)
(:ifend:)

Excellent work tracking this! We should add a similar function to send the message to Stopwatch. The problem here is all control characters like "(", ")", "[", "]", "!", "AND" must be separated by spaces for the complex conditional to work:

(:RefP2:PITS:)
Added spaces around controls (empty [@{*$:RefP}@]):
(:if5 expr ( ! empty {*$:RefP} ) AND ( ! equal "{*$:RefP}" "Wikipedia:" ):) '^[[{*$:RefP}|#]]^'(:if5end:)

Added spaces around controls (not empty [@{*$:RefP2}@]):
(:if5 expr ( ! empty {*$:RefP2} ) AND ( ! equal "{*$:RefP2}" "Wikipedia:" ):) '^[[{*$:RefP2}|#]]^'(:if5end:)

Or you could simplify this somewhat, remove expr, remove middle parentheses:
(:if5 ( ! empty {*$:RefP2} AND ! equal "{*$:RefP2}" "Wikipedia:" ):) '^[[{*$:RefP2}|#]]^'(:if5end:)
(:ifend:)

Or even: keep expr, remove all parentheses:
(:if5 expr ! empty {*$:RefP2} AND ! equal "{*$:RefP2}" "Wikipedia:":) '^[[{*$:RefP2}|#]]^'(:if5end:)
(:ifend:)

Added spaces around controls (empty {*$:RefP}):

Added spaces around controls (not empty {*$:RefP2}):

 [1]

Or you could simplify this somewhat, remove expr, remove middle parentheses:

 [2]

Or even: keep expr, remove all parentheses:

 [3]

Nevertheless, PmWiki should not allow for a parse error to be caused by text written by a user, and it is not obvious how to recover this as the page cannot be edited directly, one must restore it from history. --Petko

I was able to track this down as using PMW_safeEval() meant the page was displayed. Perhaps using something similar to PMW_safeEval() as a wrapper for eval() where user input is involved (conditions, variables, etc) would mitigate at least some of these errors.
I think it is better for such an eval to return null (and perhaps log a message) than to stop the page or the website displaying.

simon

Oh, earlier today I had already added a new helper function PEVAL($code, $vars); when I wrote "similar function to send the message to Stopwatch" it was already visible below. I now rewrote most core conditionals to not use eval, except for "expr" (but normally individual chunks in expr should not be evaluated). Existing custom conditionals should still work, with PEVAL, but it is easy to update them for to newer interface (to be documented sometime soon).

The parse error is now likely to be caught and a message added to error log and to StopWatch.

It may be a good idea for you to get the pre-release from ChangeLog and check if every conditional works as expected. --Petko


Here is the new PHP log message from the pre-release, the information is helpful

[14-Jul-2026 17:09:30 Pacific/Auckland] ParseError: syntax error, unexpected end of file
Code: "01"
Vars: {"pagename":"Songbook.TomoMaiHokiMai","text":"(:if01 incategory waiata:)N\u0101:(:else01:)By:(:if01end:)","code":"01","condspec":"incategory waiata","condtext":"N\u0101:","x":"incategory waiata","not":"","condname":"incategory","condparm":"waiata"}
#0 D:\Home\KiwiWiki\pmwiki\scripts\stdmarkup.php(129): PEVAL()
#1 D:\Home\KiwiWiki\pmwiki\scripts\stdmarkup.php(106): CondText2()
# ...

Thanks, there was an omission, should be fixed now. In addition, I rewrote core PageVars to not use eval(). Make sure you update all core files. I also updated Cookbook:InCategory to the new recommended format (the old should work for some time). --Petko