--- /dev/null
+\version "2.15.11"
+#(ly:set-option 'warning-as-error #f)
+
+\header {
+ texidoc = "Cyclic references in header fields should cause a warning, but
+not crash LilyPond with an endless loop"
+
+ title = \markup {Cyclic reference to \fromproperty #'header:title }
+
+ composer = \markup {Cyclic reference to \fromproperty #'header:temp }
+ temp = \markup {Cyclic reference to \fromproperty #'header:composer }
+}
+\score {
+ { c' d' e' f' }
+}
--- /dev/null
+\version "2.15.11"
+#(ly:set-option 'warning-as-error #f)
+
+\header {
+ texidoc = "Cyclic markup definitions should cause a warning, but
+not crash LilyPond with an endless loop"
+}
+
+% A simple markup function that calls itself in a loop.
+#(define-markup-command (cycle layout props m)
+ (markup?)
+ (interpret-markup layout props (make-cycle-markup m)))
+
+% Two simple markup functions that call each other in a loop.
+#(define-markup-command (cycleI layout props m)
+ (markup?)
+ (interpret-markup layout props (make-cycleII-markup m)))
+#(define-markup-command (cycleII layout props m)
+ (markup?)
+ (interpret-markup layout props (make-cycleI-markup m)))
+
+
+\markup { \cycle "a" }
+\markup { \cycleI "a" }
if (!is_markup (markup))
programming_error ("markup head has no markup signature");
- return scm_apply_2 (func, layout_smob, props, args);
+ /* Use a hare/tortoise algorithm to detect whether we are in a cycle,
+ * i.e. whether we have already encountered the same markup in the
+ * current branch of the markup tree structure. */
+ static vector<SCM> encountered_markups;
+ size_t depth = encountered_markups.size ();
+ if (depth > 0)
+ {
+ int slow = depth / 2;
+ if (ly_is_equal (encountered_markups[slow], markup))
+ {
+ string name = ly_symbol2string (scm_procedure_name (func));
+ // TODO: Also print the arguments of the markup!
+ non_fatal_error (_f("Cyclic markup detected: %s", name));
+ return Stencil().smobbed_copy ();
+ }
+ }
+
+ encountered_markups.push_back (markup);
+ SCM retval = scm_apply_2 (func, layout_smob, props, args);
+ encountered_markups.pop_back ();
+ return retval;
}
else
{