Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 86 additions & 0 deletions e2e-tests/tests/cpp_script_execution.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ use common::{init, FIXTURES};

const CPP_NESTED_MEMBER_TRACE_LINE: u32 = 44;
const CPP_SIBLING_BLOCK_MEMBER_TRACE_LINE: u32 = 72;
const CPP_INHERITED_MEMBER_TRACE_LINE: u32 = 81;
const CPP_AMBIGUOUS_INHERITED_MEMBER_TRACE_LINE: u32 = 90;

async fn compile_cpp_complex_script(
script: &str,
Expand Down Expand Up @@ -129,6 +131,90 @@ trace {}:{CPP_SIBLING_BLOCK_MEMBER_TRACE_LINE} {{
Ok(())
}

#[tokio::test]
async fn test_cpp_inherited_member_access() -> anyhow::Result<()> {
init();

let binary_path = FIXTURES.get_test_binary("cpp_complex_program")?;
let source_path = binary_path
.parent()
.ok_or_else(|| anyhow::anyhow!("cpp_complex_program has no parent directory"))?
.join("main.cpp");
let script = format!(
r#"
trace {}:{CPP_INHERITED_MEMBER_TRACE_LINE} {{
print "INHERITED:{{}}", d.inherited;
}}
"#,
source_path.display()
);

let compiled = compile_cpp_complex_script(&script).await?;
assert!(
!compiled.uprobe_configs.is_empty(),
"expected inherited d.inherited access to compile; target_info={} failed_targets={:?}",
compiled.target_info,
compiled.failed_targets
);

let target = spawn_cpp_complex_program().await?;
let (exit_code, stdout, stderr) =
run_ghostscope_with_script_for_target(&script, 4, &target).await?;
target.terminate().await?;
assert_eq!(exit_code, 0, "stderr={stderr} stdout={stdout}");
assert!(
stdout.contains("INHERITED:222"),
"Expected inherited base member value. STDOUT: {stdout}"
);

Ok(())
}

#[tokio::test]
async fn test_cpp_ambiguous_inherited_member_access_is_rejected() -> anyhow::Result<()> {
init();

let binary_path = FIXTURES.get_test_binary("cpp_complex_program")?;
let source_path = binary_path
.parent()
.ok_or_else(|| anyhow::anyhow!("cpp_complex_program has no parent directory"))?
.join("main.cpp");
let script = format!(
r#"
trace {}:{CPP_AMBIGUOUS_INHERITED_MEMBER_TRACE_LINE} {{
print d.conflict;
}}
"#,
source_path.display()
);

let message = match compile_cpp_complex_script(&script).await {
Ok(compiled) => {
assert!(
compiled.uprobe_configs.is_empty(),
"ambiguous inherited member access should not compile: {compiled:?}"
);
assert!(
!compiled.failed_targets.is_empty(),
"ambiguous inherited member access should report a failed target: {compiled:?}"
);
compiled
.failed_targets
.iter()
.map(|target| target.error_message.as_str())
.collect::<Vec<_>>()
.join("\n")
}
Err(error) => error.to_string(),
};
assert!(
message.contains("Ambiguous member 'conflict'"),
"unexpected compile error: {message}"
);

Ok(())
}

#[tokio::test]
async fn test_cpp_script_print_globals() -> anyhow::Result<()> {
init();
Expand Down
58 changes: 58 additions & 0 deletions e2e-tests/tests/dwarf_index_regressions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,36 @@ fn assert_native_index_queries(
Ok(())
}

fn assert_type_unit_inherited_member_queries(
analyzer: &ghostscope_dwarf::DwarfAnalyzer,
target: &Path,
producer: &str,
) -> anyhow::Result<()> {
let derived = analyzer
.resolve_c_style_semantic_type_spec_in_module(target, "Derived")
.with_context(|| format!("{producer} type units did not resolve Derived"))?;
let layout = ghostscope_dwarf::member_layout(&derived.summary, "inherited")
.with_context(|| format!("{producer} type units dropped Base::inherited"))?;
assert_eq!(layout.offset, 0, "unexpected {producer} inherited offset");

let projection = analyzer.project_resolved_type(
&derived,
&ghostscope_dwarf::VariableAccessSegment::Field("inherited".to_string()),
Some(target),
)?;
assert_eq!(
projection.layout,
ghostscope_dwarf::TypeProjectionLayout::Member { offset: 0 },
"unexpected {producer} inherited projection"
);
assert_eq!(
projection.resolved_type.summary.type_name(),
"int",
"unexpected {producer} inherited member type"
);
Ok(())
}

async fn spawn_inline_callsite_program(
binary_path: &Path,
) -> anyhow::Result<common::targets::TargetHandle> {
Expand Down Expand Up @@ -905,6 +935,11 @@ async fn test_gdb_index_resolves_function_type_and_global_lazily() -> anyhow::Re
.resolve_struct_type_shallow_by_name("Outer")
.context(".debug_names did not resolve Outer from its type unit")?;
assert_eq!(outer_type.size(), 16, "unexpected Outer size");
assert_type_unit_inherited_member_queries(
&debug_names_type_analyzer,
&debug_names_type_units,
"clang++",
)?;

let gdb_type_units = temp_dir.path().join("cpp-complex.gdb-type-units");
fs::copy(&debug_names_type_units, &gdb_type_units)?;
Expand Down Expand Up @@ -936,6 +971,29 @@ async fn test_gdb_index_resolves_function_type_and_global_lazily() -> anyhow::Re
}
}

if command_available("g++") {
let cpp_source = Path::new(env!("CARGO_MANIFEST_DIR"))
.join("tests/fixtures/cpp_complex_program/main.cpp");
let gcc_type_units = temp_dir.path().join("cpp-complex.gcc-type-units");
run_command(
StdCommand::new("g++")
.arg("-gdwarf-5")
.arg("-fdebug-types-section")
.arg("-O0")
.arg(&cpp_source)
.arg("-o")
.arg(&gcc_type_units),
"g++ type-unit build",
)?;
anyhow::ensure!(
dwarf_has_type_unit(&gcc_type_units)?,
"g++ did not emit a DWARF5 type unit"
);
let gcc_type_analyzer =
ghostscope_dwarf::DwarfAnalyzer::from_exec_path(&gcc_type_units).await?;
assert_type_unit_inherited_member_queries(&gcc_type_analyzer, &gcc_type_units, "g++")?;
}

if command_available("gcc") && command_available("objcopy") {
let fixture_dir = source
.parent()
Expand Down
25 changes: 25 additions & 0 deletions e2e-tests/tests/fixtures/cpp_complex_program/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,23 @@ __attribute__((noinline)) int sibling_block_member_probe(int x) {
return block_path_sink;
}

struct Base { int inherited; };
struct Derived : Base { int own; };

__attribute__((noinline)) int inherited_member_probe(Derived* d) {
volatile int sink = d->inherited + d->own;
return sink;
}

struct LeftBase { int conflict; };
struct RightBase { int conflict; };
struct AmbiguousDerived : LeftBase, RightBase {};

__attribute__((noinline)) int ambiguous_inherited_member_probe(AmbiguousDerived* d) {
volatile int sink = d->LeftBase::conflict + d->RightBase::conflict;
return sink;
}

// Variables purposely ending with ::h and ::h264 to validate demangled leaf handling
int h = 5;
int h264 = 7;
Expand All @@ -87,13 +104,21 @@ static void touch_globals() {

int main() {
ns1::Foo f;
ns1::Derived derived;
derived.inherited = 222;
derived.own = 333;
ns1::AmbiguousDerived ambiguous;
ambiguous.LeftBase::conflict = 444;
ambiguous.RightBase::conflict = 555;
int acc = 0;
for (int i = 0; i < 50000; ++i) {
acc += f.bar(i);
acc += ns1::add(i, i+1);
acc += ns1::add(1.5, 2.5);
acc += ns1::nested_member_probe(i);
acc += ns1::sibling_block_member_probe(i);
acc += ns1::inherited_member_probe(&derived);
acc += ns1::ambiguous_inherited_member_probe(&ambiguous);
touch_globals();
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
}
Expand Down
Loading
Loading