Created
June 9, 2025 09:21
-
-
Save dareg/5c691d6ceda2ffeb70d12b0273aae89a to your computer and use it in GitHub Desktop.
GCC plugin to detect suspicious initialization of variables in struct initializations
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #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> | |
| /* Informations du plugin */ | |
| 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" | |
| }; | |
| /* Fonction pour obtenir le nom d'un champ de structure */ | |
| static const char* get_field_name_from_type(tree struct_type, unsigned int index) __attribute__((unused)); | |
| static const char* get_field_name_from_type(tree struct_type, unsigned int index) | |
| { | |
| if (!struct_type || TREE_CODE(struct_type) != RECORD_TYPE) | |
| return NULL; | |
| tree field = TYPE_FIELDS(struct_type); | |
| unsigned int i = 0; | |
| while (field && i < index) { | |
| field = DECL_CHAIN(field); | |
| i++; | |
| } | |
| if (field && DECL_NAME(field)) { | |
| return IDENTIFIER_POINTER(DECL_NAME(field)); | |
| } | |
| return NULL; | |
| } | |
| /* Fonction pour vérifier si un nom correspond à un champ de structure */ | |
| 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; | |
| } | |
| /* Analyse améliorée des CONSTRUCTOR */ | |
| 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); | |
| /* Compter les designated initializers et analyser chaque élément */ | |
| FOR_EACH_VEC_SAFE_ELT(elts, i, elt) { | |
| if (elt->index && TREE_CODE(elt->index) == FIELD_DECL) { | |
| designated_count++; | |
| } else { | |
| /* Élément non-désigné - vérifier si c'est une assignation suspecte */ | |
| 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)); | |
| /* Vérifier si c'est un nom de champ de la structure */ | |
| 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); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| /* Détection du mélange de styles */ | |
| if (designated_count > 0 && designated_count < total_count) { | |
| warning_at(loc, 0, | |
| "mixing designated and non-designated initializers"); | |
| } | |
| } | |
| /* Analyse spéciale pour les expressions dans les initialiseurs */ | |
| static void analyze_initializer_expressions(tree init, tree struct_type, location_t loc) | |
| { | |
| if (!init) | |
| return; | |
| /* Chercher des expressions d'assignation dans l'initializer */ | |
| 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)); | |
| /* Vérifier si c'est un nom de champ */ | |
| 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); | |
| } | |
| } | |
| } | |
| } | |
| /* Walker amélioré pour détecter plus de cas */ | |
| 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; | |
| /* Analyser les CONSTRUCTOR */ | |
| if (TREE_CODE(t) == CONSTRUCTOR) { | |
| deep_analyze_constructor(t, loc); | |
| } | |
| /* Analyser les expressions d'assignation */ | |
| else if (TREE_CODE(t) == MODIFY_EXPR) { | |
| /* Essayer de déterminer si on est dans un contexte d'initialisation */ | |
| tree parent = (tree)data; | |
| if (parent && TREE_CODE(parent) == CONSTRUCTOR) { | |
| tree struct_type = TREE_TYPE(parent); | |
| analyze_initializer_expressions(t, struct_type, loc); | |
| } | |
| } | |
| /* Passer le contexte aux sous-arbres */ | |
| 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); | |
| } | |
| *walk_subtrees = 0; /* On a déjà traité les sous-arbres */ | |
| } | |
| return NULL_TREE; | |
| } | |
| /* Callback pour analyser chaque fonction */ | |
| 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); | |
| } | |
| } | |
| /* Callback pour analyser les déclarations */ | |
| 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); | |
| } | |
| } | |
| } | |
| /* Fonction d'initialisation du plugin */ | |
| int plugin_init(struct plugin_name_args *plugin_info, | |
| struct plugin_gcc_version *version) | |
| { | |
| /* Vérification de la version de GCC */ | |
| if (!plugin_default_version_check(version, &gcc_version)) { | |
| error("Plugin incompatible avec cette version de GCC"); | |
| return 1; | |
| } | |
| /* Enregistrement des informations du plugin */ | |
| register_callback(plugin_info->base_name, | |
| PLUGIN_INFO, | |
| NULL, | |
| &designated_init_plugin_info); | |
| /* Enregistrement des callbacks */ | |
| 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; | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Cible principale | |
| $(PLUGIN_NAME).so: $(PLUGIN_NAME).cpp | |
| g++ $(CXXFLAGS) $(LDFLAGS) -o $@ $# Makefile pour le plugin designated_init_checker | |
| # Configuration | |
| PLUGIN_NAME = designated_init_checker | |
| GCC_VERSION = $(shell gcc -dumpversion) | |
| GCC_PLUGIN_DIR = $(shell gcc -print-file-name=plugin) | |
| # Flags de compilation | |
| CXXFLAGS = -fPIC -shared -O2 -Wall -fno-rtti | |
| CXXFLAGS += -I$(GCC_PLUGIN_DIR)/include | |
| CXXFLAGS += -std=c++11 | |
| # Flags de liaison | |
| LDFLAGS = -shared -fPIC | |
| # Cible principale | |
| $(PLUGIN_NAME).so: $(PLUGIN_NAME).cpp | |
| g++ $(CXXFLAGS) -o $@ $< | |
| # Test du plugin | |
| test: $(PLUGIN_NAME).so test.c | |
| gcc -fplugin=./$(PLUGIN_NAME).so -c test.c | |
| # Nettoyage | |
| clean: | |
| rm -f $(PLUGIN_NAME).so test.o | |
| # Installation (optionnelle) | |
| install: $(PLUGIN_NAME).so | |
| sudo cp $(PLUGIN_NAME).so $(GCC_PLUGIN_DIR)/ | |
| .PHONY: test clean install |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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}; // Devrait générer un warning | |
| } | |
| t f3() { | |
| return (t){.a=1, c=1}; // Devrait générer un warning | |
| } | |
| t f4() { | |
| return (t){1, .c=1}; // OK | |
| } | |
| t f5() { | |
| int b; | |
| return (t){.a=1, b=1, .c=c}; // Devrait générer un warning | |
| } | |
| t f6() { | |
| int autre; | |
| return (t){autre=7, .c=1}; // Bizarre mais OK | |
| } | |
| t f7() { | |
| int foo,b; | |
| return (t){.a=1, b=foo, .c=c}; // Devrait générer un warning | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment