preprocessor: reject a malformed macro call instead of silently accepting Address review feedback: when the macro-argument scan reaches an unexpected token (in particular the EOF from an unterminated call), ConsumeAndParseMacroCall now returns an InvalidArgumentError and the caller records it as a preprocessor error at the call site, rather than silently back-filling the arguments. An early ')' remains the legal way to pass fewer arguments than parameters. Signed-off-by: Eylon Krause <eylon1909@gmail.com>
diff --git a/verible/verilog/preprocessor/verilog-preprocess.cc b/verible/verilog/preprocessor/verilog-preprocess.cc index d8f5f5b..9c27df6 100644 --- a/verible/verilog/preprocessor/verilog-preprocess.cc +++ b/verible/verilog/preprocessor/verilog-preprocess.cc
@@ -270,10 +270,14 @@ } // Any other token -- in particular the EOF token from an unterminated // macro call -- would otherwise leave token_iter and parameters_size - // unchanged and spin this loop forever. Stop scanning; the loop below - // back-fills the remaining parameters with default TokenInfo (the same - // terminal state produced by an early ')'). - break; + // unchanged and spin this loop forever. An early ')' (handled above) is the + // legal way to pass fewer arguments than parameters; anything else here is + // a malformed call, so reject it rather than silently accepting it. The + // caller records the returned message as a preprocessor error. + return absl::InvalidArgumentError(absl::StrCat( + "unexpected token while scanning arguments of macro call `", + macro_name_str, + ": expected ',' or ')', but got: ", (*token_iter)->ToString())); } if (parameters_size > 0) { while (parameters_size--) { @@ -307,8 +311,15 @@ if (config_.expand_macros) { verible::MacroCall macro_call; - RETURN_IF_ERROR( - ConsumeAndParseMacroCall(iter, generator, ¯o_call, *found)); + const absl::Status call_status = + ConsumeAndParseMacroCall(iter, generator, ¯o_call, *found); + if (!call_status.ok()) { + // Surface a malformed macro call (e.g. an unterminated argument list) as + // a preprocessor error at the call site instead of silently accepting it. + std::string message(call_status.message()); + preprocess_data_.errors.emplace_back(**iter, message); + return call_status; + } RETURN_IF_ERROR(ExpandMacro(macro_call, found)); } auto &lexed = preprocess_data_.lexed_macros_backup.back();
diff --git a/verible/verilog/preprocessor/verilog-preprocess_test.cc b/verible/verilog/preprocessor/verilog-preprocess_test.cc index 7048888..64817c7 100644 --- a/verible/verilog/preprocessor/verilog-preprocess_test.cc +++ b/verible/verilog/preprocessor/verilog-preprocess_test.cc
@@ -139,14 +139,15 @@ } } -// A function-like macro invoked with fewer arguments than parameters and no -// closing ')' (EOF reached mid-call) must not spin ConsumeAndParseMacroCall -// forever. Reaching any assertion below proves the preprocessor terminated and -// back-filled the missing arguments instead of hanging. -TEST(VerilogPreprocessTest, UnterminatedMacroCallDoesNotHang) { +// A function-like macro invoked with no closing ')' (EOF reached mid-call) must +// not spin ConsumeAndParseMacroCall forever. It is a malformed call, so the +// preprocessor rejects it with an error rather than silently accepting it. +// (Completing the test at all also proves the scan terminated instead of +// hanging.) +TEST(VerilogPreprocessTest, UnterminatedMacroCallIsRejected) { PreprocessorTester tester("`define FOO(a, b) a\n`FOO(x", VerilogPreprocess::Config({.expand_macros = true})); - SUCCEED(); + EXPECT_FALSE(tester.PreprocessorData().errors.empty()); } #define EXPECT_PARSE_OK() \