Hi,
I'm using `build_function_call()` in a plugin I'm writing,
creating `printf()` and `sprintf()` calls.

A few days ago I tested it with recent GCC (9.2.0) and -Wall, and
got "internal compiler error: Segmentation fault" somewhere deep in
`build_function_call()`. Spent some time comparing the `tree`s I
pass to this function with `tree` nodes generated by the compiler
(c-parser.c) for equivalent calls, and they were the same.

Went ahead with GDB to find the crashing code in c-format.c:3123
(on releases/gcc-9.1.0), function is `check_format_types()`:
``
      location_t param_loc = UNKNOWN_LOCATION;
      if (EXPR_HAS_LOCATION (cur_param))
        param_loc = EXPR_LOCATION (cur_param);
      else if (arglocs)
        {
          /* arg_num is 1-based.  */
          gcc_assert (types->arg_num > 0);
          param_loc = (*arglocs)[types->arg_num - 1];
        }
```

In my case `cur_param` doesn't have a location (it's an INTEGER_CST).
`arglocs` is not NULL, actually it can never be NULL in that
particular code flow: `build_function_call_vec()` accepts
`vec<location_t> arg_loc`, and it's passed onward to
`check_function_arguments()` as `&arg_loc`. This `&arg_loc`
later reaches the code snippet I presented above.

`*arglocs` was initialized with `vNULL` by `build_function_call()`,
so dereferencing it is invalid, and cc1 crashes.

`build_function_call()` always uses `arglocs=vNULL`, but I see it's
not used by the C frontend. I wrote a simple test program invoking
the same behavior (`printf("%d\n", 0)`), and compiled it with a
custom GCC with some added debug prints, and it proves `*arglocs`
is not vNULL (the C frontend uses `c_build_function_call_vec()`
directly and passes a non-vNULL `arglocs`).

I tried wrapping the location-less INTEGER_CSTs with location-ed
NOP_EXPR / `non_lvalue_loc()`, but these are seemingly stripped
before this code is reached. So eventually to solve the problem
locally in my plugin, I resorted to using
`c_build_function_call_vec()` directly, passing non-vNULL `arglocs`.

I think this behavior is weird; either `build_function_call`,
or the snippet above, are broken. It's a legit (I think?) code
flow that always crashes if it's executed...

I created a "fixed" version of GCC by changing `else if (arglocs)`
to `else if (arglocs && *arglocs != vNULL)`. Does this fix make
sense, or I've been using the API incorrectly?
I'd be happy to send a fixing patch if it's deemed relevant.

Reference plugin exhibiting the error (tested with GCC 9.2.0 + 9.1.0):
```
$ cat bug.c
#include <stdio.h>

#include <gcc-plugin.h>
#include <tree.h>
#include <c-family/c-common.h>

int plugin_is_GPL_compatible;

static void finish_decl_callback(void *event_data, void *user_data) {
    tree decl = (tree)event_data;

    if (TREE_CODE(decl) == FUNCTION_DECL && 0 == strcmp("printf",
IDENTIFIER_POINTER(DECL_NAME(decl)))) {

        build_function_call(BUILTINS_LOCATION, decl,
            tree_cons(NULL_TREE, build_string_literal(4, "%d\n"),
            tree_cons(NULL_TREE, integer_zero_node, NULL_TREE)));
    }
}

int plugin_init(struct plugin_name_args *plugin_info,
struct plugin_gcc_version *version) {
    register_callback(plugin_info->base_name,
PLUGIN_FINISH_DECL, finish_decl_callback, NULL);

    return 0;
}

$ cat trigger.c
#include <stdio.h>

$ g++ -g -Wall -Werror -I`gcc -print-file-name=plugin`/include \
-fpic -shared -o bug.so bug.c
$ gcc -Wall -fplugin=./bug.so -c trigger.c
...
internal compiler error: Segmentation fault
...
```

Am I using it wrong? Would appreciate advice.

Thanks,
Yonatan

Reply via email to