Skip to content

Commit 19142b4

Browse files
committed
postprocess: Preprocess the C source code passed to transforms
The CommentsTransform can fail if presented with a C and Rust function pair where some comments are inside directives that are compiled out during preprocessing and thus should not be transferred to the Rust. We can simply run the C compiler on the C snippet and it will pick up compile commands (if any) and give us the preprocessed version back.
1 parent c1d7cd9 commit 19142b4

3 files changed

Lines changed: 47 additions & 6 deletions

File tree

c2rust-postprocess/postprocess/transforms/base.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
get_rust_definitions,
99
)
1010
from postprocess.exclude_list import IdentifierExcludeList
11-
from postprocess.utils import get_highlighted_c
11+
from postprocess.utils import get_highlighted_c, get_preprocessed_c
1212

1313

1414
class AbstractTransform(ABC):
@@ -31,7 +31,7 @@ def apply_ident(
3131
c_definition: str,
3232
identifier: str,
3333
update_rust: bool = True,
34-
) -> None:
34+
) -> str:
3535
"""
3636
Implementations should apply transform to a single Rust definition
3737
with the given identifier.
@@ -99,15 +99,17 @@ def apply_file(
9999

100100
c_definition = c_definitions[identifier]
101101

102-
highlighted_c_definition = get_highlighted_c(c_definition)
102+
preprocessed_c_definition = get_preprocessed_c(c_definition, identifier)
103+
104+
highlighted_c_definition = get_highlighted_c(preprocessed_c_definition)
103105
logging.debug(
104106
f"C function {identifier} definition:\n{highlighted_c_definition}\n"
105107
)
106108

107109
self.apply_ident(
108110
rust_source_file=rust_source_file,
109111
rust_definition=rust_definition,
110-
c_definition=c_definition,
112+
c_definition=preprocessed_c_definition,
111113
identifier=identifier,
112114
update_rust=update_rust,
113115
)

c2rust-postprocess/postprocess/transforms/comments.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,7 @@ def apply_ident(
6565
if rust_comments:
6666
logging.info(
6767
f"Skipping Rust fn {identifier} with existing comments:\
68-
\n{rust_comments} in\
69-
\n{rust_definition}"
68+
\n{get_highlighted_rust(rust_definition)}"
7069
)
7170
return
7271

c2rust-postprocess/postprocess/utils.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import argparse
22
import json
3+
import logging
34
import os
45
from pathlib import Path
56
from shutil import which
7+
import subprocess
68
from typing import Any
79

810
from pygments import highlight
@@ -116,3 +118,41 @@ def get_highlighted_rust(rust_code: str, bg="dark") -> str:
116118
def get_highlighted_code(code: str, lexer: RegexLexer, bg: str) -> str:
117119
# TODO: detect when terminal supports colors
118120
return highlight(code, lexer, TerminalFormatter(bg=bg))
121+
122+
123+
# Flags that resolve only #if/#elif/#else/#endif: -fdirectives-only leaves
124+
# #define/macro bodies untouched, -P drops line markers, -C keeps comments,
125+
# and -undef/-nostdinc avoid pulling in any predefined or system macros.
126+
CLANG_PREPROCESS_FLAGS = ["-E", "-fdirectives-only", "-P", "-C", "-undef", "-nostdinc"]
127+
128+
129+
def get_preprocessed_c(c_function: str, identifier: str) -> str:
130+
"""
131+
Runs `clang -E -fdirectives-only -P -undef -nostdinc`, reading the
132+
function from stdin and returning the preprocessed text; returns None
133+
if clang fails.
134+
"""
135+
136+
# skip functions without any preprocessor directives
137+
if "#" not in c_function or not any(
138+
line.lstrip().startswith("#") for line in c_function.splitlines()
139+
):
140+
return c_function
141+
142+
# TODO: check that current directory or parent has compile_commands.json
143+
144+
# NOTE: clang locates compile_commands.json automatically if it's in the
145+
# current directory or any parent directory, no need to pass it explicitly
146+
cmd = ["clang", *CLANG_PREPROCESS_FLAGS, "-x", "c", "-", "-o", "-"]
147+
try:
148+
result = subprocess.run(
149+
cmd, input=c_function, capture_output=True, text=True, check=True
150+
)
151+
except subprocess.CalledProcessError as e:
152+
logging.error(
153+
"clang failed to preprocess %s: %s",
154+
identifier,
155+
e.stderr,
156+
)
157+
raise e
158+
return result.stdout

0 commit comments

Comments
 (0)