From 777176d7ec4dd93907d53770d771a6690fe0d3d0 Mon Sep 17 00:00:00 2001 From: Cameron Fischer Date: Mon, 11 Dec 2023 21:55:03 -0500 Subject: [PATCH] Better handling of infinite recursion But it could be better still... --- core/modules/utils/errors.js | 5 +++-- core/modules/widgets/transclude.js | 8 ++++++- core/modules/widgets/widget.js | 2 +- editions/test/tiddlers/tests/test-widget.js | 23 ++++++++++++++++++++- 4 files changed, 33 insertions(+), 5 deletions(-) diff --git a/core/modules/utils/errors.js b/core/modules/utils/errors.js index 1719aa60a64..73f60ae84e2 100644 --- a/core/modules/utils/errors.js +++ b/core/modules/utils/errors.js @@ -8,8 +8,9 @@ Custom errors for TiddlyWiki. \*/ (function(){ -function TranscludeRecursionError(transcludeMarker) { - this.marker = transcludeMarker; +function TranscludeRecursionError(depth) { + this.depth = depth; + this.signatures = Object.create(null); }; exports.TranscludeRecursionError = TranscludeRecursionError; diff --git a/core/modules/widgets/transclude.js b/core/modules/widgets/transclude.js index 7702641be17..ce51e21023e 100755 --- a/core/modules/widgets/transclude.js +++ b/core/modules/widgets/transclude.js @@ -38,7 +38,13 @@ TranscludeWidget.prototype.render = function(parent,nextSibling) { // We need to try and abort as much of the loop as we can, so we will keep "throwing" upward until we find a transclusion that has a different signature. // Hopefully that will land us just outside where the loop began. That's where we want to issue an error. // Rendering widgets beneath this point may result in a freezing browser if they explode exponentially. - if(error.marker !== this.getVariable("transclusion")) { + var transcludeSignature = this.getVariable("transclusion"); + if(this.getAncestorCount() > error.depth - 50) { + // For the first fifty transcludes we climb up, we simply collect signatures. + // We're assuming that those first 50 will likely include all transcludes involved in the loop. + error.signatures[transcludeSignature] = true; + } else if(!error.signatures[transcludeSignature]) { + // Now that we're past the first 50, let's look for the first signature that wasn't in the loop. That'll be where we print the error and resume rendering. this.children = [this.makeChildWidget({type: "error", attributes: { "$message": {type: "string", value: $tw.language.getString("Error/RecursiveTransclusion")} }})]; diff --git a/core/modules/widgets/widget.js b/core/modules/widgets/widget.js index f45e608a8d1..1fe95178739 100755 --- a/core/modules/widgets/widget.js +++ b/core/modules/widgets/widget.js @@ -495,7 +495,7 @@ Widget.prototype.makeChildWidgets = function(parseTreeNodes,options) { var self = this; // Check for too much recursion if(this.getAncestorCount() > MAX_WIDGET_TREE_DEPTH) { - throw new $tw.utils.TranscludeRecursionError(this.getVariable("transclusion")); + throw new $tw.utils.TranscludeRecursionError(MAX_WIDGET_TREE_DEPTH); } else { // Create set variable widgets for each variable $tw.utils.each(options.variables,function(value,name) { diff --git a/editions/test/tiddlers/tests/test-widget.js b/editions/test/tiddlers/tests/test-widget.js index 25db8aad7d0..9724c72b450 100755 --- a/editions/test/tiddlers/tests/test-widget.js +++ b/editions/test/tiddlers/tests/test-widget.js @@ -160,7 +160,7 @@ describe("Widget module", function() { expect(wrapper.innerHTML).toBe("Recursive transclusion error in transclude widget"); }); - it("should handle recursion with branching nodes", function() { + it("should handle single-tiddler recursion with branching nodes", function() { var wiki = new $tw.Wiki(); // Add a tiddler wiki.addTiddlers([ @@ -180,6 +180,27 @@ describe("Widget module", function() { expect(wrapper.innerHTML).toBe("Recursive transclusion error in transclude widget Recursive transclusion error in transclude widget"); }); + fit("should handle many-tiddler recursion with branching nodes", function() { + var wiki = new $tw.Wiki(); + // Add a tiddler + wiki.addTiddlers([ + {title: "TiddlerOne", text: "<$transclude tiddler='TiddlerTwo'/> <$transclude tiddler='TiddlerTwo'/>"}, + {title: "TiddlerTwo", text: "<$transclude tiddler='TiddlerOne'/>"} + ]); + // Test parse tree + var parseTreeNode = {type: "widget", children: [ + {type: "transclude", attributes: { + "tiddler": {type: "string", value: "TiddlerOne"} + }} + ]}; + // Construct the widget node + var widgetNode = createWidgetNode(parseTreeNode,wiki); + // Render the widget node to the DOM + var wrapper = renderWidgetNode(widgetNode); + // Test the rendering + expect(wrapper.innerHTML).toBe("Recursive transclusion error in transclude widget"); + }); + it("should deal with SVG elements", function() { var wiki = new $tw.Wiki(); // Construct the widget node