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
}

Reply via email to