From bc89805368119ca191026424a6970da29f89d168 Mon Sep 17 00:00:00 2001 From: Cameron Fischer Date: Sun, 10 Dec 2023 23:08:02 -0500 Subject: [PATCH] Introduced preliminary idea for infinite recurse exception --- core/modules/utils/errors.js | 17 +++++++++++++++++ core/modules/widgets/transclude.js | 19 ++++++++++++++++++- core/modules/widgets/widget.js | 4 +--- editions/test/tiddlers/tests/test-widget.js | 20 ++++++++++++++++++++ 4 files changed, 56 insertions(+), 4 deletions(-) create mode 100644 core/modules/utils/errors.js diff --git a/core/modules/utils/errors.js b/core/modules/utils/errors.js new file mode 100644 index 000000000..1719aa60a --- /dev/null +++ b/core/modules/utils/errors.js @@ -0,0 +1,17 @@ +/*\ +title: $:/core/modules/utils/errors.js +type: application/javascript +module-type: utils + +Custom errors for TiddlyWiki. + +\*/ +(function(){ + +function TranscludeRecursionError(transcludeMarker) { + this.marker = transcludeMarker; +}; + +exports.TranscludeRecursionError = TranscludeRecursionError; + +})(); diff --git a/core/modules/widgets/transclude.js b/core/modules/widgets/transclude.js index d30ab1fa7..7702641be 100755 --- a/core/modules/widgets/transclude.js +++ b/core/modules/widgets/transclude.js @@ -30,7 +30,24 @@ TranscludeWidget.prototype.render = function(parent,nextSibling) { this.parentDomNode = parent; this.computeAttributes(); this.execute(); - this.renderChildren(parent,nextSibling); + try { + this.renderChildren(parent,nextSibling); + } catch(error) { + if(error instanceof $tw.utils.TranscludeRecursionError) { + // We were infinite looping. + // 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")) { + this.children = [this.makeChildWidget({type: "error", attributes: { + "$message": {type: "string", value: $tw.language.getString("Error/RecursiveTransclusion")} + }})]; + this.renderChildren(parent,nextSibling); + return; + } + } + throw error; + } }; /* diff --git a/core/modules/widgets/widget.js b/core/modules/widgets/widget.js index af4892b9e..f45e608a8 100755 --- a/core/modules/widgets/widget.js +++ b/core/modules/widgets/widget.js @@ -495,9 +495,7 @@ Widget.prototype.makeChildWidgets = function(parseTreeNodes,options) { var self = this; // Check for too much recursion if(this.getAncestorCount() > MAX_WIDGET_TREE_DEPTH) { - this.children.push(this.makeChildWidget({type: "error", attributes: { - "$message": {type: "string", value: $tw.language.getString("Error/RecursiveTransclusion")} - }})); + throw new $tw.utils.TranscludeRecursionError(this.getVariable("transclusion")); } 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 0d1351f31..25db8aad7 100755 --- a/editions/test/tiddlers/tests/test-widget.js +++ b/editions/test/tiddlers/tests/test-widget.js @@ -160,6 +160,26 @@ describe("Widget module", function() { expect(wrapper.innerHTML).toBe("Recursive transclusion error in transclude widget"); }); + it("should handle recursion with branching nodes", function() { + var wiki = new $tw.Wiki(); + // Add a tiddler + wiki.addTiddlers([ + {title: "TiddlerOne", text: "<$tiddler tiddler='TiddlerOne'><$transclude /> <$transclude />"}, + ]); + // 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 Recursive transclusion error in transclude widget"); + }); + it("should deal with SVG elements", function() { var wiki = new $tw.Wiki(); // Construct the widget node