Skip to content

Commit

Permalink
Fix macro recursion issues
Browse files Browse the repository at this point in the history
The code previously only prevented replacement of the innermost macro being replaced, but mutual recursion of macros was not handled quite according to the standard (to my understanding, also checked with clang). This commit keeps a set of macros that have already been used in the current macro expansion, and refuses to expand them further.

The relevant part of the standard is:

> Then, the resulting preprocessing token sequence is rescanned, along with all subsequent preprocessing tokens of the source file, for more macro names to replace.
>
> If the name of the macro being replaced is found during this scan of the replacement list (not including the rest of the source file’s preprocessing tokens), it is not replaced. Furthermore, if any nested replacements encounter the name of the macro being replaced, it is not replaced. These nonreplaced macro name preprocessing tokens are no longer available for further replacement even if they are later (re)examined in contexts in which that macro name preprocessing token would otherwise have been replaced.
  • Loading branch information
MattX committed Dec 19, 2024
1 parent 261deb7 commit 3b3a20f
Show file tree
Hide file tree
Showing 3 changed files with 44 additions and 42 deletions.
9 changes: 9 additions & 0 deletions tests/fixtures/macro_recursion.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#include <stdio.h>

int main() {
int B = 42;
#define A B
#define B C
#define C B, B
printf("%d %d\n", A);
}
1 change: 1 addition & 0 deletions tests/snapshots/success_macro_recursion
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
42 42
76 changes: 34 additions & 42 deletions wrecc_compiler/src/preprocessor/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use crate::compiler::typechecker::TypeChecker;
use crate::preprocessor::scanner::{Token, TokenKind};
use crate::PPScanner;

use std::collections::HashMap;
use std::collections::{HashMap, HashSet};
use std::fs;
use std::path::{Path, PathBuf};

Expand Down Expand Up @@ -269,31 +269,33 @@ impl<'a> Preprocessor<'a> {
))
}
}
fn replace_macros(&self, macro_name: Token, replace_list: Vec<Token>) -> Vec<Token> {
replace_list
fn replace_macros(&self, macro_name: Token, used_macros: &mut HashSet<String>) -> Vec<Token> {
let macro_ident = if let Some(ident) = macro_name.kind.as_ident() {
ident
} else {
return vec![macro_name];
};
if used_macros.contains(&macro_ident) {
return vec![macro_name];
}
used_macros.insert(macro_ident.clone());
let replacement = if let Some(replacement) = self.defines.get(&macro_ident) {
replacement
} else {
return vec![macro_name];
};
let result = replacement
.into_iter()
.flat_map(|token| {
match (token.kind.to_string(), self.defines.get(&token.kind.to_string())) {
(token_name, Some(replacement))
if token_name != macro_name.kind.as_ident().unwrap() =>
{
let replacement = replacement
.iter()
.map(|replace_t| Token {
kind: replace_t.kind.clone(),
..macro_name.clone()
})
.collect::<Vec<Token>>();

pad_whitespace(self.replace_macros(macro_name.clone(), replacement))
}
_ => {
// can't further replace if replacement is current macro name
vec![Token { kind: token.kind, ..macro_name.clone() }]
}
}
self.replace_macros(token.clone(), used_macros)
})
.collect()
.map(|token| Token {
kind: token.kind,
..macro_name.clone()
})
.collect();
used_macros.remove(&macro_ident);
pad_whitespace(result)
}
fn undef(&mut self, directive: Token) -> Result<(), Error> {
self.skip_whitespace()?;
Expand Down Expand Up @@ -518,15 +520,10 @@ impl<'a> Preprocessor<'a> {
}
}
_ => {
if let Some(identifier) = token.kind.as_ident() {
if token.kind.as_ident().is_some() {
let mut macros_used = HashSet::new();
// if ident is defined replace it
if let Some(replacement) = self.defines.get(&identifier) {
let expanded_replacement =
pad_whitespace(self.replace_macros(token, replacement.clone()));
result.extend(expanded_replacement)
} else {
result.push(token)
}
result.extend(self.replace_macros(token, &mut macros_used))
} else {
result.push(token)
}
Expand Down Expand Up @@ -664,15 +661,9 @@ impl<'a> Preprocessor<'a> {
}
}
_ => {
if let Some(identifier) = token.kind.as_ident() {
if let Some(replacement) = self.defines.get(&identifier) {
let expanded_replacement =
pad_whitespace(self.replace_macros(token, replacement.clone()));

result.append(expanded_replacement)
} else {
result.push(token)
}
if token.kind.as_ident().is_some() {
let mut macros_used = HashSet::new();
result.append(self.replace_macros(token, &mut macros_used))
} else {
result.push(token)
}
Expand Down Expand Up @@ -890,7 +881,8 @@ mod tests {
let pp = Preprocessor::new(Path::new(""), Vec::new(), defined.clone(), &v, &h, 0);

let mut result = HashMap::new();
for (name, replace_list) in defined {
for (name, _) in defined {
let mut macros_used = HashSet::new();
result.insert(
name.to_string(),
pp.replace_macros(
Expand All @@ -900,7 +892,7 @@ mod tests {
line: 1,
line_string: "".to_string(),
},
replace_list,
&mut macros_used,
)
.into_iter()
.map(|t| t.kind.to_string())
Expand Down

0 comments on commit 3b3a20f

Please sign in to comment.