Skip to content

Commit

Permalink
Report macros that can be made into C constants
Browse files Browse the repository at this point in the history
- Add required attributes
- Set them at macro definition and expansion
- Report result in identifier page

While at it refactor the macro subst function to remove an unneeded
parameter.
  • Loading branch information
dspinellis committed Dec 1, 2024
1 parent ac75aa5 commit 51c617f
Show file tree
Hide file tree
Showing 68 changed files with 14,941 additions and 14,478 deletions.
18 changes: 14 additions & 4 deletions src/attr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,13 @@ vector<string> Project::projnames(attr_end);
string Attributes::attribute_names[] = {
"__attribute(__unused__)",
"Identifier token from a macro",
"Function-like macro",
"Used in preprocessor constant",
"Value used as preprocessor string operand",
"Value defined as a C compile-time constant",
"Value defined as not a C compile-time constant",
"Value expanded as a C compile-time constant",
"Value expanded as not a C compile-time constant",

// User-visible attributes start here
"Read-only",
Expand All @@ -64,8 +71,6 @@ string Attributes::attribute_names[] = {
"Macro",
"Undefined macro",
"Macro argument",
"Used in preprocessor constant",
"Value used as preprocessor string operand",

"File scope",
"Project scope",
Expand All @@ -80,6 +85,13 @@ string Attributes::attribute_names[] = {
string Attributes::attribute_short_names[] = {
"__attribute(__unused__)",
"idmtoken",
"funmacro",
"cppconst",
"cppstrval",
"defcconst",
"defnotcconst",
"expcconst",
"expnotcconst",

// User-visible attributes start here
"ro",
Expand All @@ -92,8 +104,6 @@ string Attributes::attribute_short_names[] = {
"macro",
"umacro",
"macroarg",
"cppconst",
"cppstrval",

"fscope",
"pscope",
Expand Down
19 changes: 15 additions & 4 deletions src/attr.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,20 @@ enum e_attribute {
is_declared_unused, // Declared with __unused__ attribute
is_macro_token, // Identifier stored in a macro
// Used to determine macro nesting
// The following are used to determine object-like macros that
// can be converted into C constants
is_fun_macro, // Defined as a function-like macro
is_cpp_const, // Used to derive a preprocessor constant
// used in #if, #include, defined()
is_cpp_str_val, // Macro's value is used as a string (pasting
// or stringization) in the preprocessor
// Below are probable values; findings can include both cases.
// Findings based on defined object-like macros.
is_def_c_const, // Value seen as a C compile-time constant.
is_def_not_c_const, // Value seen as not a C compile-time constant.
// Findings based on expanded object-like macros.
is_exp_c_const, // Value seen as a C compile-time constant.
is_exp_not_c_const, // Value seen as not a C compile-time constant.

// User-visible attributes start here
is_readonly, // Read-only; true if any member
Expand All @@ -50,10 +64,7 @@ enum e_attribute {
is_macro, // Name of an object or function-like macro
is_undefined_macro, // Macro (heuristic: ifdef, defined)
is_macro_arg, // Macro argument
is_cpp_const, // Used to derive a preprocessor constant
// used in #if, #include, defined()
is_cpp_str_val, // Macro's value is used as a string (pasting
// or stringization) in the preprocessor

// The following are valid if is_ordinary is true:
is_cscope, // Compilation-unit (file) scoped
// identifier (static)
Expand Down
23 changes: 22 additions & 1 deletion src/cscout.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1392,7 +1392,26 @@ static void
show_id_prop(FILE *fo, const string &name, bool val)
{
if (!Option::show_true->get() || val)
fprintf(fo, ("<li>" + name + ": %s\n").c_str(), val ? "Yes" : "No");
fprintf(fo, ("<li>" + name + ": %s</li>\n").c_str(), val ? "Yes" : "No");
}

// Display whether a macro can be replaced by a C constant
static void
show_c_const(FILE *fo, Eclass *e)
{
bool val = !e->get_attribute(is_fun_macro)
&& !e->get_attribute(is_cpp_const)
&& !e->get_attribute(is_cpp_str_val)
&& ((e->get_attribute(is_def_c_const)
&& !e->get_attribute(is_def_not_c_const))
|| (e->get_attribute(is_exp_c_const)
&& !e->get_attribute(is_exp_not_c_const))
);
fprintf(fo, "<li>Can be replaced by C constant: %s\n", val ? "Yes" : "No");
fprintf(fo, "<ul>\n");
for (int i = is_fun_macro; i <= is_exp_not_c_const; i++)
show_id_prop(fo, Attributes::name(i), e->get_attribute(i));
fprintf(fo, "</ul></li>\n");
}

// Details for each identifier
Expand Down Expand Up @@ -1426,6 +1445,8 @@ identifier_page(FILE *fo, void *p)
show_id_prop(fo, Attributes::name(i), e->get_attribute(i));
show_id_prop(fo, "Crosses file boundary", id.get_xfile());
show_id_prop(fo, "Unused", e->is_unused());
if (e->get_attribute(is_macro))
show_c_const(fo, e);
fprintf(fo, "<li> Matches %d occurence(s)\n", e->get_size());
if (Option::show_projects->get()) {
fprintf(fo, "<li> Appears in project(s): \n<ul>\n");
Expand Down
57 changes: 38 additions & 19 deletions src/macro.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -364,7 +364,7 @@ Macro::Macro( const Ptoken& name, bool id, bool isfun, bool isimmutable) :
mcall = NULL; // To void nasty surprises
}

static PtokenSequence subst(const Macro &m, dequePtoken is, const mapArgval &args, HideSet hs, bool skip_defined, Macro::CalledContext context, const Macro *caller);
static PtokenSequence subst(const Macro &m, const mapArgval &args, HideSet hs, bool skip_defined, Macro::CalledContext context, const Macro *caller);
static PtokenSequence glue(PtokenSequence ls, PtokenSequence rs);
static bool fill_in(PtokenSequence &ts, bool get_more, PtokenSequence &removed);

Expand All @@ -386,9 +386,10 @@ macro_expand(PtokenSequence ts,
const Macro *caller)
{
PtokenSequence r; // Return value
set<Macro> expanded_macros; // For adding attributes
auto ts_size = ts.size();

if (DP()) cout << "Expanding: " << ts << endl;
if (DP()) cout << "macro_expand: expanding: " << ts << endl;
while (!ts.empty()) {
const Ptoken head(ts.front());
ts.pop_front();
Expand All @@ -407,11 +408,6 @@ macro_expand(PtokenSequence ts,
continue;
}

// Mark the identifier as used as a preprocessor constant
if (context == Macro::CalledContext::process_include
|| context == Macro::CalledContext::process_if)
head.set_ec_attribute(is_cpp_const);

const string name = head.get_val();
mapMacro::const_iterator mi(Pdtoken::macros_find(name));
if (!Pdtoken::macro_is_defined(mi)) {
Expand All @@ -420,22 +416,28 @@ macro_expand(PtokenSequence ts,
continue;
}

// Mark the macro as used as a preprocessor constant
if (context == Macro::CalledContext::process_include
|| context == Macro::CalledContext::process_if)
head.set_ec_attribute(is_cpp_const);

const Macro& m = mi->second;
if (head.hideset_contains(m.get_name_token())) {
// Skip the head token if it is in the hideset
if (DP()) cout << "Skipping (head is in HS)" << endl;
if (DP()) cout << "macro_expand: skipping (head is in HS)" << endl;
r.push_back(head);
continue;
}

if (DP()) cout << "replacing for " << name << " tokens " << ts << endl;
if (DP()) cout << "macro_expand: replacing for " << name << " tokens " << ts << endl;
expanded_macros.insert(m);
PtokenSequence removed_spaces;
if (!m.is_function) {
// Object-like macro
Token::unify((*mi).second.name_token, head);
HideSet hs(head.get_hideset());
hs.insert(m.get_name_token());
PtokenSequence s(subst(m, m.value, mapArgval(), hs, defined_handling == Macro::DefinedHandlingOption::skip, context, caller));
PtokenSequence s(subst(m, mapArgval(), hs, defined_handling == Macro::DefinedHandlingOption::skip, context, caller));
ts.splice(ts.begin(), s);
caller = &m;
} else if (fill_in(ts, token_source == Macro::TokenSourceOption::get_more, removed_spaces) && ts.front().get_code() == '(') {
Expand All @@ -444,7 +446,7 @@ macro_expand(PtokenSequence ts,
mapArgval args; // Map from formal name to value

if (DP())
cout << "Expanding " << m << " inside " << caller << "\n";
cout << "macro_expand: expanding " << m << " inside " << caller << "\n";
if (caller && caller->is_function)
// Macro to macro call
Call::register_call(caller->get_mcall(), m.get_mcall());
Expand All @@ -460,21 +462,36 @@ macro_expand(PtokenSequence ts,
close.get_hideset().begin(), close.get_hideset().end(),
inserter(hs, hs.begin()));
hs.insert(m.get_name_token());
PtokenSequence s(subst(m, m.value, args, hs, defined_handling == Macro::DefinedHandlingOption::skip, context, caller));
PtokenSequence s(subst(m, args, hs, defined_handling == Macro::DefinedHandlingOption::skip, context, caller));
ts.splice(ts.begin(), s);
caller = &m;
} else {
// Function-like macro name lacking a (
if (DP()) cout << "splicing: [" << removed_spaces << ']' << endl;
if (DP()) cout << "macro_expand: splicing: [" << removed_spaces << ']' << endl;
ts.splice(ts.begin(), removed_spaces);
r.push_back(head);
}
}
if (DP()) cout << "Result: " << r << endl;
if (DP()) cout << "macro_expand: result: " << r << endl;

Metrics::add_pre_cpp_metric(Metrics::em_nmacrointoken, ts_size);
Metrics::add_pre_cpp_metric(Metrics::em_nmacroouttoken, r.size());

/*
* After all macros have been expanded, mark the expanded macros
* on whether the value can or cannot be a C compile-time constant.
* Over a heuristic check made by examining the macro's definition,
* the expansion method has the advantage of resolving constants
* defined as other macros, (e.g. FLAG_MASK) *provided* the macro is
* actually used.
*/
if (context == Macro::CalledContext::process_c) {
enum e_attribute attr = is_c_const(r)
? is_exp_c_const : is_exp_not_c_const;
for (const auto &m : expanded_macros)
m.get_name_token().set_ec_attribute(attr);
}

return (r);
}

Expand All @@ -492,14 +509,16 @@ find_nonspace(dequePtoken::iterator pos, dequePtoken::iterator end)
}

/*
* Substitute the arguments args appearing in the input sequence is
* Substitute the arguments args (may be empty) in the body of of macro m,
* also handling stringization and concatenation.
* Result is created in the output sequence os and finally has the specified
* hide set added to it, before getting returned.
*/
static PtokenSequence
subst(const Macro &m, dequePtoken is, const mapArgval &args, HideSet hs, bool skip_defined, Macro::CalledContext context, const Macro *caller)
subst(const Macro &m, const mapArgval &args, HideSet hs, bool skip_defined, Macro::CalledContext context, const Macro *caller)
{
PtokenSequence os; // output sequence
dequePtoken is(m.get_value());// Input sequence
PtokenSequence os; // Output sequence

while (!is.empty()) {
if (DP())
Expand Down Expand Up @@ -573,7 +592,7 @@ subst(const Macro &m, dequePtoken is, const mapArgval &args, HideSet hs, bool sk
}
if ((ai = find_formal_argument(args, head)) == args.end())
break;
// Othewise expand head
// Othewise expand the formal argument's value
PtokenSequence expanded(macro_expand(ai->second, Macro::TokenSourceOption::use_supplied, skip_defined ? Macro::DefinedHandlingOption::skip : Macro::DefinedHandlingOption::process, context, caller));
os.splice(os.end(), expanded);
continue;
Expand All @@ -584,7 +603,7 @@ subst(const Macro &m, dequePtoken is, const mapArgval &args, HideSet hs, bool sk
// Add hs to the hide set of every element of os
for (PtokenSequence::iterator oi = os.begin(); oi != os.end(); ++oi)
oi->hideset_insert(hs.begin(), hs.end());
if (DP()) cout << "os after adding hs: " << os << endl;
if (DP()) cout << "subst: os after adding hs: " << os << endl;

return os;
}
Expand Down
12 changes: 11 additions & 1 deletion src/macro.h
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,11 @@ class Macro {
// Process the defined() function or skip its processing
enum class DefinedHandlingOption { process, skip };
// Context in which macro_expand is called
enum class CalledContext { process_c, process_if, process_include };
enum class CalledContext {
process_c, // From C code
process_if, // From #if #elif
process_include, // From #include file name
};
private:
Ptoken name_token; // Name (used for unification)
bool is_function; // True if it is a function-macro
Expand All @@ -83,6 +87,7 @@ class Macro {
void form_args_push_back(Ptoken& t) { formal_args.push_back(t); };
void value_push_back(Ptoken& t) { value.push_back(t); };
MCall *get_mcall() const { return mcall; }
const dequePtoken& get_value() const { return value; }

// Remove trailing whitespace
void value_rtrim();
Expand All @@ -97,6 +102,11 @@ class Macro {
friend ostream& operator<<(ostream& o,const Macro &m);

friend PtokenSequence macro_expand(PtokenSequence ts, Macro::TokenSourceOption token_source, Macro::DefinedHandlingOption defined_handling, Macro::CalledContext context, const Macro *caller);
// Name-based comparison for the small set of visible macros
// constructed in expand_macro.
bool operator<(const Macro& other) const {
return name_token < other.name_token;
}
};

PtokenSequence macro_expand(PtokenSequence ts, Macro::TokenSourceOption token_source, Macro::DefinedHandlingOption defined_handling, Macro::CalledContext context, const Macro *caller = NULL);
Expand Down
14 changes: 11 additions & 3 deletions src/pdtoken.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -773,7 +773,6 @@ Pdtoken::process_include(bool next)
void
Pdtoken::process_define(bool is_immutable)
{
string name;
typedef map <string, Token> mapToken; // To unify args with body
mapToken args;
Pltoken t;
Expand Down Expand Up @@ -801,7 +800,7 @@ Pdtoken::process_define(bool is_immutable)
CTag::add(t, 'd');
t.set_ec_attribute(is_macro);
Pltoken nametok = t;
name = t.get_val();
string name = t.get_val();
t.getnext<Fchar>(); // Space is significant: a(x) vs a (x)
bool is_function = (t.get_code() == '(');
Macro m(nametok, true, is_function, is_immutable);
Expand Down Expand Up @@ -939,8 +938,17 @@ Pdtoken::process_define(bool is_immutable)
macros.insert(mapMacro::value_type(name, m));
else if (!mi->second.get_is_immutable())
mi->second = m;
if (is_function)

if (is_function) {
m.register_macro_body(macro_body_tokens);
m.get_name_token().set_ec_attribute(is_fun_macro);
} else {
m.get_name_token().set_ec_attribute(
is_c_const(m.get_value())
? is_def_c_const : is_def_not_c_const
);
}

if (DP()) cout << "Macro define " << m;
Pltoken::clear_echo();
}
Expand Down
46 changes: 46 additions & 0 deletions src/ptoken.h
Original file line number Diff line number Diff line change
Expand Up @@ -82,4 +82,50 @@ inline bool Ptoken::is_space() const
{
return (code == SPACE || code == '\n');
}

/*
* Return true if (conservatively) the passed Ptoken sequence
* can be a C compile-time constant (constant expression).
* See ISO/IEC 9899:2017 section 6.6.
* This implementation doesn't support enumeration constants, sizeof,
* _Alignof_, which can be part of a C compile-time constant.
*/
template <typename TokenSequence> bool
is_c_const(const TokenSequence& ts)
{
int bracket_nesting = 0;
for (const auto &tok : ts)
switch (tok.get_code()) {
case '(':
bracket_nesting++;
break;
case ')':
bracket_nesting--;
break;
case PP_NUMBER:
// Check for floating point constant
if (tok.get_val().find_first_of(".efEF")
!= string::npos)
return false;
case SPACE:
case CHAR_LITERAL:
// Allowed operators in order of precedence
case '+': case '-': case '~': case '!':
// Binary versions of above unary: case '+': case '-':
case '*': case '/': case '%':
case LEFT_OP: case RIGHT_OP:
case '<': case '>': case LE_OP: case GE_OP:
case EQ_OP: case NE_OP:
case '&':
case '^':
case '|':
case AND_OP:
case OR_OP:
case '?': case ':':
continue;
default:
return false;
}
return bracket_nesting == 0;
}
#endif /* PTOKEN_ */
Loading

0 comments on commit 51c617f

Please sign in to comment.