1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142
|
#include "ast/passes/macro_expansion.h"
#include "ast/passes/parser.h"
#include "ast/passes/printer.h"
#include "mocks.h"
#include "gtest/gtest.h"
namespace bpftrace::test::macro_expansion {
using ::testing::HasSubstr;
void test(const std::string& input,
const std::string& error = "",
const std::string& warn = "")
{
auto mock_bpftrace = get_mock_bpftrace();
BPFtrace& bpftrace = *mock_bpftrace;
bpftrace.config_->unstable_macro = ConfigUnstable::enable;
// The input provided here is embedded into an expression.
ast::ASTContext ast("stdin", input);
std::stringstream msg;
msg << "\nInput:\n" << input << "\n\nOutput:\n";
// N.B. No C macro or tracepoint expansion.
auto ok = ast::PassManager()
.put(ast)
.put(bpftrace)
.add(CreateParsePass())
.add(ast::CreateMacroExpansionPass())
.run();
std::ostringstream out;
ast::Printer printer(out);
printer.visit(ast.root);
ast.diagnostics().emit(out);
if (error.empty()) {
ASSERT_TRUE(ok && ast.diagnostics().ok()) << msg.str() << out.str();
if (!warn.empty()) {
EXPECT_THAT(out.str(), HasSubstr(warn)) << msg.str() << out.str();
}
} else {
ASSERT_FALSE(ok && ast.diagnostics().ok()) << msg.str() << out.str();
EXPECT_THAT(out.str(), HasSubstr(error)) << msg.str() << out.str();
}
}
void test_error(const std::string& input, const std::string& error)
{
test(input, error);
}
void test_warning(const std::string& input, const std::string& warn)
{
test(input, "", warn);
}
TEST(macro_expansion, basic_checks)
{
test("macro print_me() { print(\"me\"); } begin { print_me(); }");
test("macro print_me() { print(\"me\"); } begin { print_me; }");
test("macro add1(x) { x + 1 } macro add2(x) { x + add1(x) } macro "
"add3(x) { x + add2(x) } begin { print(add3(1)); }");
test("macro add1($x) { $x += 1; } begin { $y = 1; add1($y); }");
test("macro add3($x) { $x += 1; $x += 1; $x += 1; } begin { $y = 1; "
"add3($y); }");
test("macro add1($x) { $x += 1; $x } begin { $y = 1; add1($y); }");
test("macro add2($x) { $x += 2; $x } macro add1($x) { $x += 1; add2($x); } "
"begin { $y = 1; add1($y); }");
test("macro add1(x) { x + 1 } begin { $y = 1; add1($y); }");
test("macro add1(x) { x + 1 } begin { @y = 1; add1(@y); }");
test_error("macro add1($x) { $x += 1; } begin { $y = 1; $z = add1($y); }",
"Macro 'add1' expanded to a block instead of a block expression. "
"Try removing the semicolon from the end of the last statement in "
"the macro body.");
test_error("macro add2($y) { $y += 1; } macro add1($x) { $x += add2($x); } "
"begin { $y = 1; add1($y); }",
"Macro 'add2' expanded to a block instead of a block expression. "
"Try removing the semicolon from the end of the last statement in "
"the macro body.");
test_error("macro set($x) { $x += 1; $x } begin { $a = 1; set($a, 1); }",
"Call to macro has wrong number arguments. Expected: 1 but got 2");
test_error("macro set($x, $x) { $x += 1; $x } begin { $a = 1; set($a, 1); }",
"Variable for macro argument has already been used: $x");
test_error("macro set($x) { $x += 1; $x } macro set($x) { $x } begin { $a = "
"1; set($a, 1); }",
"Redifinition of macro: set");
test_error(
"macro add1($x) { $x + add3($x) } macro add2($x) { $x + add1($x) } macro "
"add3($x) { $x + add2($x) } begin { print(add3(1)); }",
"Recursive macro call detected. Call chain: add3 > add2 > add1 > add3");
test_error("macro add1($x) { $x += 1; $x } begin { $y = 1; $z = add1; }",
"ERROR: Call to macro has no number arguments. Expected: 1");
}
TEST(macro_expansion, variables)
{
test("macro set($x) { $x += 1; $x } begin { $a = 1; set($a); }");
test("macro set($x, y) { $x + y } begin { $a = 1; set($a, 1); }");
test("macro set($x) { $b = $x + 1; $b } begin { $a = 1; set($a); }");
test("macro set($x) { let $b = $x + 1; $b } begin { $a = 1; set($a); }");
test_error(
"macro set($x) { $x += 1; $x } begin { @a = 1; set(@a); }",
"Mismatched arg to macro call. Macro expects a variable for arg $x "
"but got a map.");
test_error("macro add1($x) { $x += 1; $x } begin { add1(1 + 1); }",
"Mismatched arg to macro call. Macro expects a variable for arg "
"$x but got an expression.");
test_error("macro set($x) { let $x; $x } begin { $y = 1; set($y); }",
"Variable declaration shadows macro arg $x");
}
TEST(macro_expansion, maps)
{
test("macro set(@x) { @x[1] } begin { @a[1] = 0; set(@a); }");
test("macro set(@x) { @x[1] = 1; @x[1] } begin { @a[1] = 0; set(@a); }");
test_error("macro set(@x) { @x[1] = 1; @x[1] } begin { $a = 0; set($a); }",
"Mismatched arg to macro call. Macro expects a map for arg @x but "
"got a variable.");
test_error("macro set(@x) { @x[1] = 1; @x[1] } begin { $a = 0; set(1); }",
"Mismatched arg to macro call. Macro expects a map for arg @x but "
"got an expression.");
test_error("macro set() { @x[1] = 1; 1 } begin { @x[0] = 0; set(); }",
"Unhygienic access to map: @x. Maps must be passed into the macro "
"as arguments");
test_error("macro set() { @x[1] } begin { @x[0] = 0; set(); }",
"Unhygienic access to map: @x. Maps must be passed into the macro "
"as arguments");
}
TEST(macro_expansion, misc)
{
// semantic_analyser will catch this undefined call/macro
test("macro add3(x) { x + add5(x) } begin { print(add3(1)); }");
}
} // namespace bpftrace::test::macro_expansion
|