Browse Source

cppparser: Fix problems with recursive expansion in preprocessor

Other half of #1635
rdb 1 year ago
parent
commit
80147990cc
2 changed files with 96 additions and 75 deletions
  1. 93 74
      dtool/src/cppparser/cppPreprocessor.cxx
  2. 3 1
      dtool/src/cppparser/cppPreprocessor.h

+ 93 - 74
dtool/src/cppparser/cppPreprocessor.cxx

@@ -34,6 +34,7 @@
 
 #include <assert.h>
 #include <ctype.h>
+#include <set>
 
 using std::cerr;
 using std::string;
@@ -796,80 +797,8 @@ expand_manifests(const string &input_expr, bool expand_undefined,
   // Get a copy of the expression string we can modify.
   string expr = input_expr;
 
-  // Repeatedly scan the expr for any manifest names or defined() function.
-
-  bool manifest_found;
-  do {
-    manifest_found = false;
-    size_t p = 0;
-    while (p < expr.size()) {
-      if (isalpha(expr[p]) || expr[p] == '_') {
-        size_t q = p;
-        while (p < expr.size() && (isalnum(expr[p]) || expr[p] == '_')) {
-          p++;
-        }
-        string ident = expr.substr(q, p - q);
-
-        // Here's an identifier.  Is it "defined"?
-        if (ident == "defined") {
-          expand_defined_function(expr, q, p);
-        } else if (expand_undefined && ident == "__has_include") {
-          expand_has_include_function(expr, q, p, loc);
-        } else {
-          // Is it a manifest?
-          Manifests::const_iterator mi = _manifests.find(ident);
-          if (mi != _manifests.end()) {
-            const CPPManifest *manifest = (*mi).second;
-            expand_manifest_inline(expr, q, p, manifest);
-            manifest_found = true;
-
-          } else if (ident == "__FILE__") {
-            // Special case: this is a dynamic definition.
-            string file = string("\"") + loc.file._filename_as_referenced.get_fullpath() + "\"";
-            expr = expr.substr(0, q) + file + expr.substr(p);
-            p = q + file.size();
-            manifest_found = true;
-
-          } else if (ident == "__LINE__") {
-            // So is this.
-            string line = format_string(loc.first_line);
-            expr = expr.substr(0, q) + line + expr.substr(p);
-            p = q + line.size();
-            manifest_found = true;
-
-          } else if (expand_undefined && ident != "true" && ident != "false") {
-            // It is not found.  Expand it to 0, but only if we are currently
-            // parsing an #if expression.
-            expr = expr.substr(0, q) + "0" + expr.substr(p);
-            p = q + 1;
-          }
-        }
-      } else if (expr[p] == '\'' || expr[p] == '"') {
-        // Skip the next part until we find a closing quotation mark.
-        char quote = expr[p];
-        p++;
-        while (p < expr.size() && expr[p] != quote) {
-          if (expr[p] == '\\') {
-            // This might be an escaped quote.  Skip an extra char.
-            p++;
-          }
-          p++;
-        }
-        if (p >= expr.size()) {
-          // Unclosed string.
-          warning("missing terminating " + string(1, quote) + " character", loc);
-        }
-        p++;
-      } else {
-        p++;
-      }
-    }
-
-    // If we expanded any manifests at all that time, then go back through the
-    // string and look again--we might have a manifest that expands to another
-    // manifest.
-  } while (manifest_found);
-
+  std::set<const CPPManifest *> expanded;
+  r_expand_manifests(expr, expand_undefined, loc, expanded);
   return expr;
 }
 
@@ -2187,6 +2116,96 @@ expand_manifest(const CPPManifest *manifest) {
   return internal_get_next_token();
 }
 
+/**
+ * Recursive implementation of expand_manifests().
+ */
+void CPPPreprocessor::
+r_expand_manifests(string &expr, bool expand_undefined,
+                   const YYLTYPE &loc, std::set<const CPPManifest *> &expanded) {
+  size_t p = 0;
+  while (p < expr.size()) {
+    if (isalpha(expr[p]) || expr[p] == '_') {
+      size_t q = p;
+      while (p < expr.size() && (isalnum(expr[p]) || expr[p] == '_')) {
+        p++;
+      }
+      string ident = expr.substr(q, p - q);
+
+      // Here's an identifier.  Is it "defined"?
+      if (ident == "defined") {
+        expand_defined_function(expr, q, p);
+      }
+      else if (expand_undefined && ident == "__has_include") {
+        expand_has_include_function(expr, q, p, loc);
+      }
+      else {
+        // Is it a manifest?
+        Manifests::const_iterator mi = _manifests.find(ident);
+        if (mi != _manifests.end()) {
+          const CPPManifest *manifest = (*mi).second;
+          if (expanded.count(manifest) == 0) {
+            vector_string args;
+            if (manifest->_has_parameters) {
+              extract_manifest_args_inline(manifest->_name, manifest->_num_parameters,
+                                           manifest->_variadic_param, args, expr, p);
+            }
+
+            string result = manifest->expand(args);
+
+            // Recurse, but adding the manifest we just expanded to the list
+            // to be ignored for future expansion, to prevent recursion.
+            std::set<const CPPManifest *> ignore = expanded;
+            ignore.insert(manifest);
+            r_expand_manifests(result, expand_undefined, loc, ignore);
+
+            expr = expr.substr(0, q) + result + expr.substr(p);
+            p = q + result.size();
+          }
+        }
+        else if (ident == "__FILE__") {
+          // Special case: this is a dynamic definition.
+          string file = string("\"") + loc.file._filename_as_referenced.get_fullpath() + "\"";
+          expr = expr.substr(0, q) + file + expr.substr(p);
+          p = q + file.size();
+
+        }
+        else if (ident == "__LINE__") {
+          // So is this.
+          string line = format_string(loc.first_line);
+          expr = expr.substr(0, q) + line + expr.substr(p);
+          p = q + line.size();
+        }
+        else if (expand_undefined && ident != "true" && ident != "false") {
+          // It is not found.  Expand it to 0, but only if we are currently
+          // parsing an #if expression.
+          expr = expr.substr(0, q) + "0" + expr.substr(p);
+          p = q + 1;
+        }
+      }
+    }
+    else if (expr[p] == '\'' || expr[p] == '"') {
+      // Skip the next part until we find a closing quotation mark.
+      char quote = expr[p];
+      p++;
+      while (p < expr.size() && expr[p] != quote) {
+        if (expr[p] == '\\') {
+          // This might be an escaped quote.  Skip an extra char.
+          p++;
+        }
+        p++;
+      }
+      if (p >= expr.size()) {
+        // Unclosed string.
+        warning("missing terminating " + string(1, quote) + " character", loc);
+      }
+      p++;
+    }
+    else {
+      p++;
+    }
+  }
+}
+
 /**
  *
  */

+ 3 - 1
dtool/src/cppparser/cppPreprocessor.h

@@ -115,7 +115,7 @@ protected:
   bool push_string(const std::string &input, bool lock_position);
 
   std::string expand_manifests(const std::string &input_expr, bool expand_undefined,
-                          const YYLTYPE &loc);
+                               const YYLTYPE &loc);
   CPPExpression *parse_expr(const std::string &expr, CPPScope *current_scope,
                             CPPScope *global_scope, const YYLTYPE &loc);
 
@@ -152,6 +152,8 @@ private:
   CPPToken get_literal(int token, YYLTYPE loc, const std::string &str,
                        const YYSTYPE &result = YYSTYPE());
   CPPToken expand_manifest(const CPPManifest *manifest);
+  void r_expand_manifests(std::string &expr, bool expand_undefined,
+                          const YYLTYPE &loc, std::set<const CPPManifest *> &expanded);
   void extract_manifest_args(const std::string &name, int num_args,
                              int va_arg, vector_string &args);
   void expand_defined_function(std::string &expr, size_t q, size_t &p);