Browse Source

Signal an error when a circular macro expansion happens

* lisp/org.el (org-macro-replace-all): Signal an error when a circular
  macro expansion happens.
(org-macro-initialize-templates): Fix docstring.
* testing/lisp/test-org.el: Add test.
Nicolas Goaziou 5 years ago
parent
commit
6290da183c
2 changed files with 36 additions and 18 deletions
  1. 29 17
      lisp/org.el
  2. 7 1
      testing/lisp/test-org.el

+ 29 - 17
lisp/org.el

@@ -20927,28 +20927,40 @@ TEMPLATES is an alist of templates used for expansion.  See
 `org-macro-templates' for a buffer-local default value."
   (save-excursion
     (goto-char (point-min))
-    (while (re-search-forward "{{{[-A-Za-z0-9_]" nil t)
-      (let ((object (org-element-context)))
-        (when (eq (org-element-type object) 'macro)
-          (let ((value (org-macro-expand object templates)))
-            (when value
-              (delete-region
-               (org-element-property :begin object)
-               ;; Preserve white spaces after the macro.
-               (progn (goto-char (org-element-property :end object))
-                      (skip-chars-backward " \t")
-                      (point)))
-              ;; Leave point before replacement in case of recursive
-              ;; expansions.
-              (save-excursion (insert value)))))))))
+    (let (record)
+      (while (re-search-forward "{{{[-A-Za-z0-9_]" nil t)
+	(let ((object (org-element-context)))
+	  (when (eq (org-element-type object) 'macro)
+	    (let* ((value (org-macro-expand object templates))
+		   (begin (org-element-property :begin object))
+		   (signature (list begin
+				    object
+				    (org-element-property :args object))))
+	      ;; Avoid circular dependencies by checking if the same
+	      ;; macro with the same arguments is expanded at the same
+	      ;; position twice.
+	      (if (member signature record)
+		  (error "Circular macro expansion: %s"
+			 (org-element-property :key object))
+		(when value
+		  (push signature record)
+		  (delete-region
+		   begin
+		   ;; Preserve white spaces after the macro.
+		   (progn (goto-char (org-element-property :end object))
+			  (skip-chars-backward " \t")
+			  (point)))
+		  ;; Leave point before replacement in case of recursive
+		  ;; expansions.
+		  (save-excursion (insert value)))))))))))
 
 (defun org-macro-initialize-templates ()
   "Collect macro templates defined in current buffer.
 Templates are stored in buffer-local variable
 `org-macro-templates'.  In addition to buffer-defined macros, the
-function installs the following ones: \"property\", \"date\",
-\"time\". and, if appropriate, \"input-file\" and
-\"modification-time\"."
+function installs the following ones: \"property\",
+\"time\". and, if the buffer is associated to a file,
+\"input-file\" and \"modification-time\"."
   (let ((case-fold-search t)
 	(set-template
 	 (lambda (cell)

+ 7 - 1
testing/lisp/test-org.el

@@ -410,7 +410,13 @@ http://article.gmane.org/gmane.emacs.orgmode/21459/"
 	"#+MACRO: in inner\n#+MACRO: out {{{in}}} outer\n{{{out}}}"
       (progn (org-macro-initialize-templates)
 	     (org-macro-replace-all org-macro-templates)
-	     (buffer-string))))))
+	     (buffer-string)))))
+  ;; Error out when macro expansion is circular.
+  (should-error
+   (org-test-with-temp-text
+       "#+MACRO: mac1 {{{mac2}}}\n#+MACRO: mac2 {{{mac1}}}\n{{{mac1}}}"
+     (org-macro-initialize-templates)
+     (org-macro-replace-all org-macro-templates))))