protected function fosterParent(Tag $tag) { if (!empty($this->openTags)) { $tagName = $tag->getName(); $tagConfig = $this->tagsConfig[$tagName]; if (!empty($tagConfig['rules']['fosterParent'])) { $parent = \end($this->openTags); $parentName = $parent->getName(); if (isset($tagConfig['rules']['fosterParent'][$parentName])) { if ($parentName !== $tagName && $this->currentFixingCost < $this->maxFixingCost) { $child = $this->addCopyTag($parent, $tag->getPos() + $tag->getLen(), 0); $tag->cascadeInvalidationTo($child); $child->setSortPriority($tag->getSortPriority() + 1); } $this->tagStack[] = $tag; $this->addMagicEndTag($parent, $tag->getPos())->setSortPriority($tag->getSortPriority() - 1); $this->currentFixingCost += \count($this->tagStack); return \true; } } } return \false; }
/** * @testdox Mutual invalidation doesn't cause an infinite loop */ public function testInvalidateNoInfiniteLoop() { $tag1 = new Tag(Tag::START_TAG, 'X', 0, 0); $tag2 = new Tag(Tag::START_TAG, 'X', 0, 0); $tag1->cascadeInvalidationTo($tag2); $tag2->cascadeInvalidationTo($tag1); $tag1->invalidate(); }
/** * Apply fosterParent rules associated with given tag * * NOTE: this rule has the potential for creating an unbounded loop, either if a tag tries to * foster itself or two or more tags try to foster each other in a loop. We mitigate the * risk by preventing a tag from creating a child of itself (the parent still gets closed) * and by checking and increasing the currentFixingCost so that a loop of multiple tags * do not run indefinitely. The default tagLimit and nestingLimit also serve to prevent the * loop from running indefinitely * * @param Tag $tag Tag * @return bool Whether a new tag has been added */ protected function fosterParent(Tag $tag) { if (!empty($this->openTags)) { $tagName = $tag->getName(); $tagConfig = $this->tagsConfig[$tagName]; if (!empty($tagConfig['rules']['fosterParent'])) { $parent = end($this->openTags); $parentName = $parent->getName(); if (isset($tagConfig['rules']['fosterParent'][$parentName])) { if ($parentName !== $tagName && $this->currentFixingCost < $this->maxFixingCost) { // Add a 0-width copy of the parent tag right after this tag, and make it // depend on this tag $child = $this->addCopyTag($parent, $tag->getPos() + $tag->getLen(), 0); $tag->cascadeInvalidationTo($child); } ++$this->currentFixingCost; // Reinsert current tag $this->tagStack[] = $tag; // And finally close its parent $this->addMagicEndTag($parent, $tag->getPos()); return true; } } } return false; }