cppparser: Fix problems with recursive expansion in preprocessor

Other half of #1635
This commit is contained in:
rdb 2024-03-26 15:54:58 +01:00
parent 685b98021c
commit 80147990cc
2 changed files with 96 additions and 75 deletions

View File

@ -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++;
}
}
}
/**
*
*/

View File

@ -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);