Closed macournoyer closed 3 years ago
The issue seems to be that
ip
is null. But why is it null
After an exception is handled, the render_args->ip
is used to specify where it should resume from. This is set to NULL
outside of rendering a variable compiled into a block body.
Tags aren't yet compiled into the block body VM code, so they are handled by the OP_WRITE_NODE
instruction which delegates to Liquid::BlockBody.render_node
, which has its own exception handling. Normally the exception will either be handled and rendered by render_node
or get re-raised by both render_node
and vm_render_rescue
. So it looks like this problem is with there being an exception that is (re)raised by render_node
but handled by vm_render_rescue
.
There is a bug in vm_render_rescue
in this unexpected case, where it should re-raise the exception if ip
is NULL
, rather than relying on Liquid::BlockBody.rescue_render_node
to handle this case.
Ah so to reproduce, we need to raise from inside a tag body I suppose.
I tried updating my test case to do this, but still can't repro, for ref:
def test_exception_in_exception_renderer_propagate
old_exception_renderer = Liquid::Template.default_exception_renderer
Liquid::Template.default_exception_renderer = ->(e) {
raise StandardError
}
template = Liquid::Template.parse('This is a runtime error: {% if true %}{{ errors.runtime_error }}{% endif %}')
assert_raises StandardError do
template.render('errors' => ErrorDrop.new)
end
ensure
Liquid::Template.default_exception_renderer = old_exception_renderer if old_exception_renderer
end
To reproduce it, you need the exception renderer to not just raise an exception the first time it is called, but to also render the exception when it is called the second time with the exception raised from the first call to it. I've pushed a regression test to https://github.com/Shopify/liquid-c/pull/116 which reproduces the crash with the corresponding fix reverted.
I don't think it's an urgent issue, because it only happens when an exception is raised from inside the exception renderer, and only the exception is raised from inside this else branch. Raising from anywhere else in the method does not cause a segfault.
So, as long as
LiquidExceptionRenderer#track_liquid_unreported_exception
does not raise, no segfault should happen.The issue seems to be that
ip
is null. But why is it null, and only on that else branch? I don't know.I tried reproducing with a test in Liquid-C, but haven't been able to yet, so I'm documenting this issue in case I forget.