Readline 自定义完成器

Readline custom completer

我正在尝试用 Rust 编写一个 readline 自定义完成器(tab 完成)。我想我什么都弄清楚了,但是当我尝试 en vivo 时,它掉进了杂草中,再也没有回来。奇怪的是,当我直接从 main() 调用它时,我似乎得到了一个有效的指针。在这两种情况下,我都没有看到崩溃或恐慌。回溯输出在运行中不一致(它正忙着做某事)。也许一个线索是 gdb 指示传递给完成者的参数不正确(尽管我实际上并没有使用它们)。例如,回调后:

#2  0x00007f141f701272 in readlinetest::complete (text=0x7f141ff27d10 "", start=2704437, end=499122176) at src/main.rs:24

或者直接在main:

中断点调用
#0  readlinetest::complete (text=0x555555559190 <complete::hcda8d6cb2ef52a1bKaa> "dH;$%p", start=0, end=0) at src/main.rs:21

我有 ABI 问题吗?似乎不太可能,函数签名也不复杂:(

这是一个小测试项目:Cargo.toml:

[package]
name = "readlinetest"           
version = "0.1.0"
authors = ["You <you@example.com>"]

[dependencies]
libc = "*"
readline = "*" 

main.rs

extern crate libc;
extern crate readline;

use libc::{c_char, c_int};
use std::ffi::CString;
use std::process::exit;
use std::ptr;
use std::str;

extern { fn puts(s: *const libc::c_char); } 

#[link(name = "readline")]
// Define the global in libreadline that will point to our completion function.
extern {
    static mut rl_completion_entry_function: extern fn(text: *const c_char,
                                                       start: c_int,
                                                       end: c_int) -> *const *const c_char; 
} 

// Our completion function. Returns two strings.
extern fn complete(text: *const c_char, start: c_int, end: c_int) -> *const *const c_char {
    let _ = text; let _ = start; let _ = end;
    let mut words:Vec<*const c_char> =
        vec!(CString::new("one").unwrap(), CString::new("two").unwrap()).
        iter().
        map(|arg| arg.as_ptr()).
        collect();
    words.push(ptr::null()); // append null
    words.as_ptr() as *const *const c_char
} 

fn main() {
    let words = complete(string_to_mut_c_char("hi"), 1, 2);
    unsafe { puts(*words) } // prints "one"
    //unsafe { puts((*words + ?)) } // not sure hot to get to "two"
    unsafe { rl_completion_entry_function = complete }
    // Loop until EOF: echo input to stdout
    loop {
        if let Ok(input) = readline::readline_bare(&CString::new("> ").unwrap()) {
            let text = str::from_utf8(&input.to_bytes()).unwrap();
            println!("{}", text);
        } else { // EOF/^D
            exit(0)
        }
    }
}

// Just for testing
fn string_to_mut_c_char(s: &str) -> *mut c_char {
    let mut bytes = s.to_string().into_bytes(); // Vec<u8>
    bytes.push(0); // terminating null
    let mut cchars = bytes.iter().map(|b| *b as c_char).collect::<Vec<c_char>>();
    let name: *mut c_char = cchars.as_mut_ptr();
    name

}

Ubuntu 14.04,64 位带 Rust 1.3。

我错过了什么?感谢指点(哈哈...)。

and the function signature isn't complicated

它不是,但它确实有助于拥有正确的...^_^ 来自我本地版本的 readline (6.3.8):

extern rl_compentry_func_t *rl_completion_entry_function;
typedef char *rl_compentry_func_t PARAMS((const char *, int));

此外,您有多个 use after free 错误:

vec![CString::new("one").unwrap()].iter().map(|s| s.as_ptr());

这将创建一个 CString 并获取指向它的指针。语句完成后,nothing owns 拥有字符串的向量。向量将立即被删除,删除字符串,使指针无效。

words.as_ptr() as *const *const c_char

这里有类似的事情——你拿了指针,但是 没有任何东西拥有 words 向量了,所以它被丢弃,使指针无效。所以现在你有一个无效的指针,它试图指向一系列无效的指针。

同样的问题可以在string_to_mut_c_char中找到。

我对 readline 的了解不足,无法理解谁应该拥有返回的字符串,但看起来您将所有权传递给 readline 并释放它们。如果是这样,那意味着您将不得不使用与 readline 相同的分配器,以便它可以为您释放字符串。您可能必须编写一些自定义代码,使用适当的分配器复制 CString 的数据。


在风格方面,您可以在变量名中使用下划线来表示它们未被使用:

extern fn complete(_text: *const c_char, _start: c_int, _end: c_int)

:后应该有一个space,不需要指定向量内容的类型:

let mut words: Vec<_>