c++parsingrustlibclang

rust libclang, why am I parsing only c functions from a cpp file?


I am trying to write a rust script that parses a C++ file, finds all extern C declared functions and prints them out.

To that effect I have this first attempt:

use clang::*;
use clap::{Arg, App};


fn main()
{
    let matches = App::new("Shared C++ API parser")
        .version("0.0.1")
        .author("Makogan")
        .about("Parse a cpp file that exposes functions to be loaded at runtime from \
                A shared object (.so/.dll).")
        .arg(Arg::with_name("file")
                .short('f')
                .long("file")
                .takes_value(true)
                .help("The cpp file that will be parsed")
                .required(true))
        .get_matches();

    let cpp_file = matches.value_of("file").unwrap();
    println!("The file passed is: {}", cpp_file);

    let clang = Clang::new().unwrap();
    let index = Index::new(&clang, false, false);

    let tu = index.parser(cpp_file).parse().unwrap();

    let funcs = tu.get_entity().get_children().into_iter().filter(
        |e| {
            e.get_kind() == EntityKind::FunctionDecl
        }).collect::<Vec<_>>();

    for func_ in funcs
    {
        let type_ =  func_.get_type().unwrap();
        let size = type_.get_sizeof().unwrap();
        println!("func: {:?} (size: {} bytes)", func_.get_display_name().unwrap(), size);
    }

}

I am giving it a true cpp file with lots of functions, this is one such declared funciton:


void DrawOffScreen(
    void* vk_data_ptr,
    const NECore::RenderRequest& render_request,
    const NECore::UniformBufferDataContainer& uniform_container);

When I give it a very very simple file I stil don't get an output

extern "C"
{

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wreturn-type-c-linkage"

void InitializeRendering(void* window, bool use_vsync)
{
}
}

If I comment out the extern C part I do get the correct output. It seems that when the defientiion is inside an extern C block clang thinks the funciton is undeclared:

 Some(Entity { kind: UnexposedDecl, display_name: None, location: Some(SourceLocation { file: Some(File { path: "/path/dummy.cpp" }), line: 1, column: 8, offset: 7 }) })

What do I do?


Solution

  • UnexposedDecl is actually not the function definition, but the extern 'C' itself.

    If you print all items recursively, you will see what the tree actually looks like:

    fn print_rec(entity: Entity, depth: usize) {
        for _ in 0..depth {
            print!("  ");
        }
        println!("{:?}", entity);
    
        for child in entity.get_children() {
            print_rec(child, depth + 1);
        }
    }
    
    fn main() {
        // ...
    
        print_rec(tu.get_entity(), 0);
    }
    
    Entity { kind: TranslationUnit, display_name: Some("simple.cpp"), location: None }
      Entity { kind: UnexposedDecl, display_name: None, location: Some(SourceLocation { file: Some(File { path: "simple.cpp" }), line: 1, column: 8, offset: 7 }) }
        Entity { kind: FunctionDecl, display_name: Some("InitializeRendering(void *, bool)"), location: Some(SourceLocation { file: Some(File { path: "simple.cpp" }), line: 7, column: 6, offset: 105 }) }
          Entity { kind: ParmDecl, display_name: Some("window"), location: Some(SourceLocation { file: Some(File { path: "simple.cpp" }), line: 7, column: 32, offset: 131 }) }
          Entity { kind: ParmDecl, display_name: Some("use_vsync"), location: Some(SourceLocation { file: Some(File { path: "simple.cpp" }), line: 7, column: 45, offset: 144 }) }
          Entity { kind: CompoundStmt, display_name: None, location: Some(SourceLocation { file: Some(File { path: "simple.cpp" }), line: 8, column: 1, offset: 155 }) }
    

    So your actual problem is that you only iterate over the topmost level of the tree, and don't recurse into the segments.

    To iterate recursively, use visit_children() instead of get_children():

    use clang::*;
    use clap::{App, Arg};
    
    fn main() {
        let matches = App::new("Shared C++ API parser")
            .version("0.0.1")
            .author("Makogan")
            .about(
                "Parse a cpp file that exposes functions to be loaded at runtime from \
                    a shared object (.so/.dll).",
            )
            .arg(
                Arg::with_name("file")
                    .short('f')
                    .long("file")
                    .takes_value(true)
                    .help("The cpp file that will be parsed")
                    .required(true),
            )
            .get_matches();
    
        let cpp_file = matches.value_of("file").unwrap();
        println!("The file passed is: {}", cpp_file);
    
        let clang = Clang::new().unwrap();
        let index = Index::new(&clang, false, false);
    
        let tu = index.parser(cpp_file).parse().unwrap();
    
        let mut funcs = vec![];
        tu.get_entity().visit_children(|e, _| {
            if e.get_kind() == EntityKind::FunctionDecl {
                funcs.push(e);
                EntityVisitResult::Continue
            } else {
                EntityVisitResult::Recurse
            }
        });
    
        for func_ in funcs {
            let type_ = func_.get_type().unwrap();
            let size = type_.get_sizeof().unwrap();
            println!(
                "func: {:?} (size: {} bytes)",
                func_.get_display_name().unwrap(),
                size
            );
        }
    }
    
    The file passed is: simple.cpp
    func: "InitializeRendering(void *, bool)" (size: 1 bytes)