【问题标题】:pycparser ParseError with typedefpycparser ParseError with typedef
【发布时间】:2015-09-11 08:49:31
【问题描述】:

我正在使用 pycparser 解析一些在解析之前无法使用 cpp 编译的 C 代码,因此我使用以下函数手动剥离了所有 cmets 和预处理器指令:

def remove_comments(text):
    def replacer(match):
        s = match.group(0)
        if s.startswith('/') or s.startswith('#'):
            return ""
        else:
            return s

    pattern = re.compile(
        r'#.*?$|//.*?$|/\*.*?\*/|\'(?:\\.|[^\\\'])*\'|"(?:\\.|[^\\"])*"',
        re.DOTALL | re.MULTILINE
    )
    return re.sub(pattern, replacer, text)

这是示例中memmgr.c 文件中此函数的输出:

typedef ulong Align;

union mem_header_union
{
    struct 
    {


        union mem_header_union* next;



        ulong size; 
    } s;



    Align align_dummy;
};

typedef union mem_header_union mem_header_t;



static mem_header_t base;



static mem_header_t* freep = 0;



static byte pool[POOL_SIZE] = {0};
static ulong pool_free_pos = 0;


void memmgr_init()
{
    base.s.next = 0;
    base.s.size = 0;
    freep = 0;
    pool_free_pos = 0;
}


static mem_header_t* get_mem_from_pool(ulong nquantas)
{
    ulong total_req_size;

    mem_header_t* h;

    if (nquantas < MIN_POOL_ALLOC_QUANTAS)
        nquantas = MIN_POOL_ALLOC_QUANTAS;

    total_req_size = nquantas * sizeof(mem_header_t);

    if (pool_free_pos + total_req_size <= POOL_SIZE)
    {
        h = (mem_header_t*) (pool + pool_free_pos);
        h->s.size = nquantas;
        memmgr_free((void*) (h + 1));
        pool_free_pos += total_req_size;
    }
    else
    {
        return 0;
    }

    return freep;
}










void* memmgr_alloc(ulong nbytes)
{
    mem_header_t* p;
    mem_header_t* prevp;





    ulong nquantas = (nbytes + sizeof(mem_header_t) - 1) / sizeof(mem_header_t) + 1;




    if ((prevp = freep) == 0)
    {
        base.s.next = freep = prevp = &base;
        base.s.size = 0;
    }

    for (p = prevp->s.next; ; prevp = p, p = p->s.next)
    {

        if (p->s.size >= nquantas) 
        {

            if (p->s.size == nquantas)
            {



                prevp->s.next = p->s.next;
            }
            else 
            {
                p->s.size -= nquantas;
                p += p->s.size;
                p->s.size = nquantas;
            }

            freep = prevp;
            return (void*) (p + 1);
        }



        else if (p == freep)
        {
            if ((p = get_mem_from_pool(nquantas)) == 0)
            {

                printf("!! Memory allocation failed !!\n");

                return 0;
            }
        }
    }
}

但是我得到了这个 ParseError:

pycparser.plyparser.ParseError: :1:15: before: Align

pycparser 出了什么问题?

【问题讨论】:

  • 如果将 ulong 重命名为 long 会怎样?
  • @Har 我得到另一个 ParseError, pycparser.plyparser.ParseError: :13:9: before: ulong
  • hmm 所以它理解 long 而不是 ulong 但不理解 typedef...奇怪...

标签: python parsing python-3.x pycparser


【解决方案1】:

C 预处理器不仅剥离 cmets。它还处理所有的宏,包括#include,它会引入头文件。在你的情况下,它正在尝试#include "memmgr.h",其中有这个typedef

typedef unsigned long ulong;

和其他一些人一起。

底线是 - 你应该在 pycparser 之前调用预处理器。如果您有有效的 C 代码,则预处理器没有理由不工作。这可能是C 标记中单独的 SO 问题的主题。

【讨论】:

  • 如前所述,我想解析一些任意代码并获取函数、变量等的列表。当您的目的是检查您的代码是否“可以工作”时,满足依赖关系是可以的,但是如果您只想要一些关于代码结构的大致信息,并且代码甚至可能是从一些更复杂的方法中提取的方法声明,则既耗时又无用代码。
【解决方案2】:

我想在某些包含的文件中有一个typedef unsigned long ulong;。如果没有该声明,ulong 就不能出现在语法需要类型名的地方。

尝试在第一次使用之前添加ulong 的声明。


更具体地说,关于这个问题:“pycparser 出了什么问题?”:

pycparser 的目标是解析 C 程序。它不是近似解析器;它实际上旨在生成对任何有效 C99 程序的完整、准确的解析。

不幸的是,不可能在不知道哪些标识符是类型名的情况下准确解析 C 程序。不需要知道标识符的确切类型,因此 pycparser 不需要访问所有原型和全局定义;但是,它确实需要访问所有相关的typedefs。

这记录在section 3.2 of pycparser's readme file 中,它指向a longer discussion at the author's website

这里要理解的关键点是 pycparser 并不真正关心类型的语义。它只需要知道源中遇到的某个token是否是先前定义的类型。这对于正确解析 C 至关重要。

正如 Eli 所建议的,最好的办法是只收集要分析的代码使用的 typedef,然后将它们插入代码的开头。他们可能不会太多

Eli Bendersky 的文章非常出色,值得一读。让我提供几个 C 代码示例,如果不知道名称是否为 typedef,则无法解析。

我认为经典的例子是众所周知的:

(foo) - 3 * 4

这个表达式有两种可能的解析,只有一种可以应用于任何给定的程序。左边,解析 if foo 是一个变量;在右边,解析 if foo 是一个类型:

    -                              *
   / \                            / \
  /   \                          /   \
foo    *                       cast   4
      / \                      /  \
     /   \                    /    \
    3     4                 foo     -   
                                    |
                                    |
                                    3

换句话说,如果foo 是一个变量,则表达式从foo 中减去3*4。但如果foo 是一个类型,则表达式将-3 强制转换为foo 类型,然后将结果乘以4`。

显然,这个问题所源自的特定应用实际上并不需要详细了解每个表达式的解析。但是没有办法将这个事实传达给 pycparser; pycparser 旨在提供完整的解析。

在任何情况下,都可以构建一个可能更相关的示例。考虑这两个语句(不能出现在同一个翻译单元中):

foo (*bar()) ();

foo (*bar()) ();

尽管它们相似 (:-)),但这两种说法是完全不同的。第一个声明一个名为bar的函数。第二个调用一个名为foo的函数,在调用一个名为bar的函数来计算foo的参数之后。

即使您只是收集声明,了解该声明是否是声明也很重要。 (是的,这种结构非常罕见;它可能不会出现在正在分析的代码库中的任何地方。但是 pycparser 是如何知道的呢?)

以下是完整的上下文:

#include <stdio.h>                 | #include <stdio.h>
typedef int foo;                   |
int answer() { return 42; }        | int answer() { return 42; }
                                   |
                                   | int (*foo(int a)) () {
                                   |   printf("%d\n", a);
                                   |   return answer;
                                   | }
                                   |
                                   | static const int unused = 43;
int (*bar()) () { return answer; } | int const* bar() { return &unused; }
                                   |
int main() {                       | int main() {
  /* Declare bar */                |  /* Call foo */
  foo (*bar()) ();                 |  foo (*bar()) ();
  printf("%d\n", bar()());         |  return 0;
  return 0;                        | }
}                                  |

【讨论】:

  • 如果我只关心任意代码 sn-ps 的语法解析,例如列出所有方法,该怎么办?
  • @Vektor88:在不知道哪些标识符是类型名的情况下无法解析 C 代码。 (可以得到近似解析,但我认为 pycparser 想要正确解析输入。)为什么不能预处理文件?
  • 我只关心代码的语法方面,所以我真的不需要满足依赖关系,如果我想处理整个 linux 内核代码,需要很长时间和很多努力才能满足所有依赖。显然 pycparser 不适合我。
  • @Vektor88:也许 pycparser 不是您正在寻找的工具,但任何 C 解析器都可能产生类似的结果。我添加了一些示例,试图解释为什么在不知道标识符是类型名称还是变量/函数名称的情况下无法解析 C。
猜你喜欢
  • 2018-08-14
  • 2019-08-11
  • 2020-10-02
  • 2023-03-04
  • 2013-05-26
  • 1970-01-01
  • 1970-01-01
  • 2017-06-09
  • 1970-01-01
相关资源
最近更新 更多