Hello,
Find attached a plugin for GCC 14. It detects when the user forgets a dot and
initialize by mistake a local variable instead of a struct member.
I am not aware of any warning of gcc that can detect this mistake, but it would
be nice to not have to use a plugin for that, so if you know any flag to check
that please share it.
It is helpful for me, so I am posting it thinking it might help someone else.
I am not at all familiar with the gcc internals, so I did not write this plugin
myself: *it has been vibe-coded with claude.ai*
I have been able to compile and run it correctly on Debian 13, using GCC 14.2.
Usage example:
typedef struct {
int a, b, c;
} t;
int c = 0;
t f1() {
return (t){.a=1, .c=c}; // OK
}
t f3() {
return (t){.a=1, c=1}; // Should generate a warning
}
test.c: In function ‘f3’:
test.c:16:23: warning: assignment 'c=...' in initializer context - did you mean
'.c=...'?
16 | return (t){.a=1, c=1}; // Should generate a warning
#include <gcc-plugin.h>
#include <plugin-version.h>
#include <tree.h>
#include <c-family/c-pragma.h>
#include <c-family/c-common.h>
#include <diagnostic.h>
#include <langhooks.h>
int plugin_is_GPL_compatible;
static struct plugin_info designated_init_plugin_info = {
.version = "1.0",
.help = "Détecte les designated initializers mal formés"
};
static bool is_field_name(tree struct_type, const char* name)
{
if (!struct_type || TREE_CODE(struct_type) != RECORD_TYPE || !name)
return false;
tree field = TYPE_FIELDS(struct_type);
while (field) {
if (DECL_NAME(field)) {
const char* field_name = IDENTIFIER_POINTER(DECL_NAME(field));
if (strcmp(field_name, name) == 0) {
return true;
}
}
field = DECL_CHAIN(field);
}
return false;
}
static void deep_analyze_constructor(tree init, location_t loc)
{
if (!init || TREE_CODE(init) != CONSTRUCTOR)
return;
vec<constructor_elt, va_gc> *elts = CONSTRUCTOR_ELTS(init);
if (!elts)
return;
constructor_elt *elt;
unsigned int i;
int designated_count = 0;
int total_count = vec_safe_length(elts);
tree struct_type = TREE_TYPE(init);
FOR_EACH_VEC_SAFE_ELT(elts, i, elt) {
if (elt->index && TREE_CODE(elt->index) == FIELD_DECL) {
designated_count++;
} else {
if (elt->value && TREE_CODE(elt->value) == MODIFY_EXPR) {
tree lhs = TREE_OPERAND(elt->value, 0);
if (lhs && TREE_CODE(lhs) == VAR_DECL && DECL_NAME(lhs)) {
const char *var_name = IDENTIFIER_POINTER(DECL_NAME(lhs));
if (is_field_name(struct_type, var_name)) {
warning_at(loc, 0,
"suspicious assignment '%s=...' in initializer - "
"did you mean '.%s=...'?",
var_name, var_name);
}
}
}
}
}
if (designated_count > 0 && designated_count < total_count) {
warning_at(loc, 0,
"mixing designated and non-designated initializers");
}
}
static void analyze_initializer_expressions(tree init, tree struct_type, location_t loc)
{
if (!init)
return;
if (TREE_CODE(init) == MODIFY_EXPR) {
tree lhs = TREE_OPERAND(init, 0);
if (lhs && TREE_CODE(lhs) == VAR_DECL && DECL_NAME(lhs)) {
const char *var_name = IDENTIFIER_POINTER(DECL_NAME(lhs));
if (is_field_name(struct_type, var_name)) {
warning_at(loc, 0,
"assignment '%s=...' in initializer context - "
"did you mean '.%s=...'?",
var_name, var_name);
}
}
}
}
static tree analyze_tree_walker(tree *tp, int *walk_subtrees, void *data)
{
tree t = *tp;
if (!t)
return NULL_TREE;
location_t loc = EXPR_LOCATION(t);
if (loc == UNKNOWN_LOCATION)
loc = input_location;
if (TREE_CODE(t) == CONSTRUCTOR) {
deep_analyze_constructor(t, loc);
}
else if (TREE_CODE(t) == MODIFY_EXPR) {
tree parent = (tree)data;
if (parent && TREE_CODE(parent) == CONSTRUCTOR) {
tree struct_type = TREE_TYPE(parent);
analyze_initializer_expressions(t, struct_type, loc);
}
}
if (TREE_CODE(t) == CONSTRUCTOR) {
tree subtree;
unsigned int i;
FOR_EACH_CONSTRUCTOR_VALUE(CONSTRUCTOR_ELTS(t), i, subtree) {
walk_tree(&subtree, analyze_tree_walker, t, NULL);
}
}
return NULL_TREE;
}
static void pre_genericize_callback(void *gcc_data, void *user_data)
{
tree fndecl = (tree)gcc_data;
if (!fndecl || TREE_CODE(fndecl) != FUNCTION_DECL)
return;
tree body = DECL_SAVED_TREE(fndecl);
if (body) {
walk_tree(&body, analyze_tree_walker, NULL, NULL);
}
}
static void finish_decl_callback(void *gcc_data, void *user_data)
{
tree decl = (tree)gcc_data;
if (!decl)
return;
if (TREE_CODE(decl) == VAR_DECL && DECL_INITIAL(decl)) {
tree init = DECL_INITIAL(decl);
if (TREE_CODE(init) == CONSTRUCTOR) {
location_t loc = DECL_SOURCE_LOCATION(decl);
deep_analyze_constructor(init, loc);
}
}
}
int plugin_init(struct plugin_name_args *plugin_info,
struct plugin_gcc_version *version)
{
if (!plugin_default_version_check(version, &gcc_version)) {
error("Plugin incompatible avec cette version de GCC");
return 1;
}
register_callback(plugin_info->base_name,
PLUGIN_INFO,
NULL,
&designated_init_plugin_info);
register_callback(plugin_info->base_name,
PLUGIN_FINISH_DECL,
finish_decl_callback,
NULL);
register_callback(plugin_info->base_name,
PLUGIN_PRE_GENERICIZE,
pre_genericize_callback,
NULL);
return 0;
}
PLUGIN_NAME = designated_init_checker
GCC_VERSION = $(shell gcc -dumpversion)
GCC_PLUGIN_DIR = $(shell gcc -print-file-name=plugin)
CXXFLAGS = -fPIC -shared -O2 -Wall -fno-rtti
CXXFLAGS += -I$(GCC_PLUGIN_DIR)/include
CXXFLAGS += -std=c++11
LDFLAGS = -shared -fPIC
$(PLUGIN_NAME).so: $(PLUGIN_NAME).cpp
g++ $(CXXFLAGS) -o $@ $<
test: $(PLUGIN_NAME).so test.c
gcc -fplugin=./$(PLUGIN_NAME).so -c test.c
clean:
rm -f $(PLUGIN_NAME).so test.o
.PHONY: test clean install
typedef struct {
int a, b, c;
} t;
int c = 0;
t f1() {
return (t){.a=1, .c=c}; // OK
}
t f2() {
return (t){.a=1, c=c}; // Should generate a warning
}
t f3() {
return (t){.a=1, c=1}; // Should generate a warning
}
t f4() {
return (t){1, .c=1}; // OK
}
t f5() {
int b;
return (t){.a=1, b=1, .c=c}; // Should generate a warning
}
t f6() {
int autre;
return (t){autre=7, .c=1}; // Strange but isn't in conflict with struct
}
t f7() {
int foo=1,b=1;
return (t){.a=1, b=foo, .c=c}; // Should generate a warning
}