Problem/Motivation

No test coverage for the recursion protection exists. #3052482: Expand EntityEmbedFilter's test coverage: test many more edge cases, change to kernel test wanted to add it, but realized that it is impossible to test right now because it is broken, for the same reasons EntityReferenceEntityFormatter's was broken. That was fixed in #2073753: Fix and add tests for the recursive rendering protection of the 'Rendered entity' formatter.

Proposed resolution

Remaining tasks

User interface changes

API changes

Data model changes

Release notes snippet

Comments

Wim Leers created an issue. See original summary.

wim leers’s picture

Quoting myself from #3052482-130: Expand EntityEmbedFilter's test coverage: test many more edge cases, change to kernel test:

So: recursive embed test coverage. Let's start.

  1. \Drupal\entity_embed\Exception\RecursiveRenderingException was last touched >3.5 years ago. By a commit that says it's about something else. Without an issue. Apparently that commit moved it from \Drupal\entity_embed\RecursiveRenderingException.
  2. That file in turn was renamed after another commit introduced it but named it incorrectly. (See next bullet.) Again without an issue.
  3. That file in turn was introduced in a commit ~1.5 year prior, that commit said Removed dependency on entity_reference. — because apparently this is not a problem that Entity Embed encountered, it's one that it learned to protect against from the Entity Reference module and its field formatter. Again without issue. The class contained a more useful comment at that time:
     * Exception thrown when the embed entity post_render_cache callback goes into
     * a potentially infinite loop.
    

    … but despite that seemingly pointing to something Entity Embed-specific, it seems to have its roots in the Entity Reference module still.

  4. And it actually goes back to the very first commit of Entity Embed: 48763795 Dave Reid <dave@davereid.net> on 2014-04-18! Clearly, Dave knew about this recursive rendering thing being a problem, probably from his vast experience with Drupal and seemingly having run into complex problems with entity reference field formatters :) The "without an issue" remarks are not meant as snark, but as factual observations that explain why it's hard to figure out the how and why of this and hence how to test this!

So, let's look at the Entity Reference module. It used to contain Drupal\entity_reference\RecursiveRenderingException. Also in Drupal 8 core. Not anymore though. #2404021: entity_reference formatters should be in Core removed it: rather than throwing an exception, it now just logs an error, because that avoids a broken site. And #2073753: Fix and add tests for the recursive rendering protection of the 'Rendered entity' formatter added test coverage for this, more than a year later, and it hasn't been touched since: \Drupal\Tests\field\Kernel\EntityReference\EntityReferenceFormatterTest::testEntityFormatterRecursiveRendering() — yay!

Ironically (and thankfully!) this led me to realize that Entity Embed's recursion protection is currently broken, for the same reasons EntityReferenceEntityFormatter's was broken: see #2073753: Fix and add tests for the recursive rendering protection of the 'Rendered entity' formatter. But that means I'd have to start violating an important principle of this issue: test coverage changes only, no code changes!

So created #3063398: Update EntityEmbedFilter's recursion protection to match EntityReferenceEntityFormatter's and add test coverage for that.

Next: patch.

wim leers’s picture

Status: Active » Needs review
StatusFileSize
new1.35 KB
wim leers’s picture

StatusFileSize
new3.88 KB
new5.18 KB

Here's a fix, that ports the exact same pattern that #2073753: Fix and add tests for the recursive rendering protection of the 'Rendered entity' formatter introduced — I'm glad we can follow an existing pattern 🙏

The last submitted patch, 3: 3063398-3.patch, failed testing. View results

wim leers’s picture

StatusFileSize
new3.38 KB
new7.35 KB

#4 made the minimal changes necessary; we should also port the use of the logger from #2073753: Fix and add tests for the recursive rendering protection of the 'Rendered entity' formatter.

wim leers’s picture

Now the tricky try … catch structure can just disappear! That has been known to cause problems (see #3055050: Loosen EntityEmbedFilter's greedy exception capture: catch only relevant exceptions).

wim leers’s picture

Assigned: wim leers » Unassigned

This is probably ready, but I'm not gonna commit it right away, I wanna look at it with a fresh set of eyes prior to committing.

oknate’s picture

I must be missing something, but I don't see how testRecursionProtection tests for recursion, rather than just the number of times the entity is rendered through the filter.

+    $text = $this->createEmbedCode([
+      'data-entity-type' => 'node',
+      'data-entity-uuid' => static::EMBEDDED_ENTITY_UUID,
+      'data-view-mode' => 'default',
+    ]);
+
+    // Render and verify the presence of the embedded entity 20 times.
+    for ($i = 0; $i < 20; $i++) {
+      $this->applyFilter($text);
+      $this->assertCount(1, $this->cssSelect('div.embedded-entity > [data-entity-embed-test-view-mode="default"]'));
+    }

I was thinking that recursion would be making sure if Node 1 embeds node 2 and node 2 embeds node 1, that it would prevent it from blowing up the site, by endlessly rendering the same entities that self-reference.

But this just tests that you don't render the same entity more than 20 times on a page, right?

I guess it's a simple solution for the more complex problem. Because there shouldn't be a real world situation where someone adds the same embed code 21 times in a body field.

Part of me thinks that it's cheating, as you're not allowing for "other use cases" other than recursive rendering. But it does make the solution much simpler. It would be hard to track if the rendering was done in a parent entity, especially with entity embed. It's been in core for a while and no one complains they can't reference the same entity in the same context node 21 times on a parent. So I it seems to be a nice solution.

Perhaps, we should have another test, not necessarily a Kernel test, where it's actually this:

Node 1 embeds node 2 and node 2 embeds node 1

wim leers’s picture

You're not missing something, you're exactly right!

See #2073753: Fix and add tests for the recursive rendering protection of the 'Rendered entity' formatter for why this approach was chosen in Drupal core, because Drupal 8's render caching meant that the function call stack based recursion detection broke.

The goal is not to perfectly detect recursion (which never makes sense anyway, and core also doesn't manage to detect this reliably). The goal is to not let this crash a site.

wim leers’s picture

Status: Needs review » Needs work

That being said, I think it'd be reasonable to add a functional test with a node embedding itself in the "full" display mode using the Entity Embed filter in the body field. Then accessing /node/NID should assert that the entity label is present precisely 21 times (the recursion limit in the filter plus the original rendered entity).

I'll do that on Monday (or perhaps tomorrow), unless you want to :)

oknate’s picture

Status: Needs work » Needs review
StatusFileSize
new3.67 KB
new10.74 KB

Adding functional test, and one coding standard fix:
-use Drupal\entity_embed\Exception\RecursiveRenderingException;

oknate’s picture

StatusFileSize
new2.49 KB
new10.72 KB

Minor cleanup.

wim leers’s picture

Status: Needs review » Reviewed & tested by the community
StatusFileSize
new4.84 KB
new11.18 KB

Fixing nits and the sole remaining coding standard: the one I introduced!

  • Wim Leers committed 56256c6 on 8.x-1.x
    Issue #3063398 by Wim Leers, oknate: Update EntityEmbedFilter's...
wim leers’s picture

Status: Reviewed & tested by the community » Fixed

🚢

oknate’s picture

Sweet!

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.

tom.moffett’s picture

This change caused the same issue seen here, https://www.drupal.org/project/drupal/issues/2940605, now with embedded entities.

oknate’s picture

Tom, can you create a separate issue for it, so this one doesn’t get too long?

tom.moffett’s picture