/* -*- Mode: C++; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ /* * Tartan * Copyright © 2014 Collabora Ltd. * * Tartan is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Tartan is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Tartan. If not, see . * * Authors: * Philip Withnall */ /** * GSignalVisitor: * * This is a checker for GObject signal connection calls. For calls to functions * such as g_signal_connect(), it validates that: * • The signal name exists on the given GObject. * • The type of the callback function matches the signal declaration. * It requires the signal name to be a string literal, and will only work if the * GObject parameter (the first parameter to g_signal_connect()) has the most * specific type possible, so it can look up the signals for that GObject * subclass. * * The type of the user_data is not validated (other than requiring it to be a * pointer type, such as gpointer), but could be checked by a separate plugin * for closure types. * * The GObject type is resolved and the signal name is looked up on it. If both * these operations succeed, the type of the callback function is checked * against the signal declaration in the GIR file. A warning is emitted if the * callback function can’t be resolved (i.e. if a variable is passed instead of * a function pointer), or if the function declaration is old-style (without * arguments). * * Formally, given a connection call with types representing variables: * g_signal_connect (D, "S::signal-name", callback, U_1) * and a callback defined as: * R callback (A, …, U_2) * the following type relationships must hold: * U_1 <: U_2 or U_2 = gpointer * D <: A safety for callback invocation * S <: A safety for callback reuse * D <: S safety for signal connection * Additionally, the following relationship can hold to improve efficiency by * eliminating the need for dynamic type checks in the callback. It is not * required for type safety: * S = A efficiency for callback implementation * * In the code, D is referred to as the ‘dynamic’ type, S as the ‘static’ type * and A as the ‘actual’ type. * * If %G_CONNECT_SWAPPED is specified in the g_signal_connect() flags, the same * relationships hold but using the connection call definition: * g_signal_connect (D, "S::signal-name", callback, U_1) * and a callback defined as: * R callback (U_2, …, A) * and a new optional relationship: * A = gpointer */ #include "config.h" #include #include #include #include #include "debug.h" #include "gsignal-checker.h" namespace tartan { /* Information about the GSignal functions we’re interested in. If you want to * add support for a new GSignal function, it may be enough to add a new * element here. */ typedef struct { /* C name of the function */ const char *func_name; /* Zero-based index of the GObject instance parameter. */ unsigned int gobject_param_index; /* Zero-based index of the signal name parameter. */ unsigned int signal_name_param_index; /* Zero-based index of the callback function pointer parameter. */ unsigned int callback_param_index; /* Zero-based index of the flags parameter, or -1 if there is none. */ int flags_param_index; /* Zero-based index of the user_data parameter. */ unsigned int user_data_param_index; } SignalFuncInfo; static const SignalFuncInfo gsignal_connect_funcs[] = { { "g_signal_connect", 0, 1, 2, -1, 3 }, { "g_signal_connect_after", 0, 1, 2, -1, 3 }, { "g_signal_connect_swapped", 0, 1, 2, -1, 3 }, { "g_signal_connect_object", 0, 1, 2, 4, 3 }, { "g_signal_connect_data", 0, 1, 2, 5, 3 }, /* FIXME add support for these: { "g_signal_connect_closure", 0, 1, _ }, { "g_signal_connect_closure_by_id", 0, _, _ }, */ }; static const SignalFuncInfo * _func_is_gsignal_connect (const FunctionDecl& func) { const std::string func_name = func.getNameAsString (); guint i; /* Fast path elimination of irrelevant functions. */ if (func_name[0] != 'g') return NULL; for (i = 0; i < G_N_ELEMENTS (gsignal_connect_funcs); i++) { if (func_name == gsignal_connect_funcs[i].func_name) { return &gsignal_connect_funcs[i]; } } return NULL; } /* If an expression is a reference to a GObject (or subclass, or a GInterface), * return the most specific type information we can for that object (or * interface). This must be freed with g_base_info_unref(). * * If the expression is not a GObject, return NULL. */ static GIObjectInfo* _expr_to_gtype (const Expr *expr, const ASTContext &context, const GirManager &gir_manager) { QualType gobject_type = expr->getType (); while (gobject_type->isPointerType ()) { gobject_type = gobject_type->getPointeeType (); } /* We have the GObject pointee type, so try and resolve it. */ std::string gobject_type_str = gobject_type.getUnqualifiedType ().getAsString (); return gir_manager.find_object_info (gobject_type_str); } /* Look up a named signal in a #GIObjectInfo or #GIInterfaceInfo, * @dynamic_instance_info. * * If no definition for the signal can be found, %NULL will be returned. * * If the signal is defined on a #GObject, the #GIObjectInfo for that object * will be returned in @static_instance_info. In this case, * @static_instance_info is guaranteed to be the same as, or a superclass of, * @dynamic_instance_info. * * If the signal is defined on a #GInterface, the #GIInterfaceInfo for that * interface will be returned in @static_instance_info. In this case, * @dynamic_instance_info could have been the interface, or any class which * implements it. * * Any value returned in @static_instance_info must be freed using * g_base_info_unref(). The returned #GISignalInfo must be freed using * g_base_info_unref() as well. */ static GISignalInfo * _gtype_look_up_signal (GIRegisteredTypeInfo *dynamic_instance_info, GIRegisteredTypeInfo **static_instance_info, const gchar *signal_name) { GISignalInfo *signal_info; gint n_signals; if (GI_IS_OBJECT_INFO (dynamic_instance_info)) { n_signals = g_object_info_get_n_signals (dynamic_instance_info); } else if (GI_IS_INTERFACE_INFO (dynamic_instance_info)) { n_signals = g_interface_info_get_n_signals (dynamic_instance_info); } else { g_assert_not_reached (); } for (gint i = 0; i < n_signals; i++) { if (GI_IS_OBJECT_INFO (dynamic_instance_info)) { signal_info = g_object_info_get_signal (dynamic_instance_info, i); } else if (GI_IS_INTERFACE_INFO (dynamic_instance_info)) { signal_info = g_interface_info_get_signal (dynamic_instance_info, i); } else { g_assert_not_reached (); } if (strcmp (signal_name, g_base_info_get_name (signal_info)) == 0) { /* Found the signal. */ *static_instance_info = g_base_info_ref (dynamic_instance_info); return signal_info; } g_base_info_unref (signal_info); } if (GI_IS_OBJECT_INFO (dynamic_instance_info)) { /* If the object implements any interfaces, try those. */ for (gint i = 0; i < g_object_info_get_n_interfaces (dynamic_instance_info); i++) { GIInterfaceInfo *b; b = g_object_info_get_interface (dynamic_instance_info, i); signal_info = _gtype_look_up_signal (b, static_instance_info, signal_name); g_base_info_unref (b); if (signal_info != NULL) { return signal_info; } } /* If the object has a parent class, try that. */ dynamic_instance_info = g_object_info_get_parent (dynamic_instance_info); if (dynamic_instance_info != NULL) { signal_info = _gtype_look_up_signal (dynamic_instance_info, static_instance_info, signal_name); g_base_info_unref (dynamic_instance_info); return signal_info; } } /* Found nothing. */ *static_instance_info = NULL; return NULL; } /* Look up the #QualType representing the type in @type_info, which must be * a %GI_TYPE_TAG_INTERFACE info. If type lookup fails, a null type is * returned. */ static QualType _type_interface_info_to_type (GITypeInfo *type_info, const ASTContext &context, const GirManager &gir_manager, TypeManager &type_manager) { GIBaseInfo *interface_info; QualType retval; interface_info = g_type_info_get_interface (type_info); assert (interface_info != NULL); switch (g_base_info_get_type (interface_info)) { case GI_INFO_TYPE_ENUM: case GI_INFO_TYPE_FLAGS: { std::string c_type (gir_manager.get_c_name_for_type (interface_info)); retval = type_manager.find_type_by_name (c_type); break; } case GI_INFO_TYPE_CALLBACK: case GI_INFO_TYPE_STRUCT: case GI_INFO_TYPE_BOXED: case GI_INFO_TYPE_OBJECT: case GI_INFO_TYPE_INTERFACE: case GI_INFO_TYPE_UNION: { std::string c_type (gir_manager.get_c_name_for_type (interface_info)); retval = type_manager.find_pointer_type_by_name (c_type); break; } case GI_INFO_TYPE_FUNCTION: case GI_INFO_TYPE_CONSTANT: case GI_INFO_TYPE_VALUE: case GI_INFO_TYPE_SIGNAL: case GI_INFO_TYPE_VFUNC: case GI_INFO_TYPE_PROPERTY: case GI_INFO_TYPE_FIELD: case GI_INFO_TYPE_ARG: case GI_INFO_TYPE_TYPE: /* Never expect these to be argument types. */ case GI_INFO_TYPE_INVALID_0: case GI_INFO_TYPE_UNRESOLVED: case GI_INFO_TYPE_INVALID: /* These are all invalid */ default: WARN ("Warning: Unexpected base info type " << g_base_info_get_type (interface_info) << " for base info " << g_base_info_get_name (interface_info) << "."); } g_base_info_unref (interface_info); if (g_type_info_is_pointer (type_info)) { retval = context.getPointerType (retval); } return retval; } static QualType _type_info_to_type (GITypeInfo *type_info, const ASTContext &context, const GirManager &gir_manager, TypeManager &type_manager); /* Look up the #QualType representing the type in @array_info, which must be * a %GI_TYPE_TAG_ARRAY info. If type lookup fails, a null type is returned. */ static QualType _type_array_info_to_type (GITypeInfo *array_info, const ASTContext &context, const GirManager &gir_manager, TypeManager &type_manager) { switch (g_type_info_get_array_type (array_info)) { case GI_ARRAY_TYPE_C: { /* FIXME: Really not sure if this is correct. */ GITypeInfo *param_type = g_type_info_get_param_type (array_info, 0); QualType element_type = _type_info_to_type (param_type, context, gir_manager, type_manager); g_base_info_unref (param_type); if (element_type.isNull ()) { return QualType (); } /* Handle the array length. */ gint fixed_size = g_type_info_get_array_fixed_size (array_info); /* FIXME: Probably can’t do anything with * g_type_info_get_array_length() because it requires an Expr * for the length, which I don’t think we can retrieve if we’re * examining a callback type (as opposed to a call itself). */ if (fixed_size > -1) { return context.getConstantArrayType (element_type, llvm::APInt (32, fixed_size), ArrayType::ArraySizeModifier::Static, 0); } else { return context.getIncompleteArrayType (element_type, ArrayType::ArraySizeModifier::Static, 0); } } case GI_ARRAY_TYPE_ARRAY: return type_manager.find_pointer_type_by_name ("GArray"); case GI_ARRAY_TYPE_PTR_ARRAY: return type_manager.find_pointer_type_by_name ("GPtrArray"); case GI_ARRAY_TYPE_BYTE_ARRAY: return type_manager.find_pointer_type_by_name ("GByteArray"); default: WARN ("Warning: Unexpected array type " << g_type_info_get_array_type (array_info) << " for base info " << g_base_info_get_name (array_info) << "."); return QualType (); } } /* Look up the #QualType representing the type in @type_info, which can have any * type tag. If type lookup fails, a null type is returned. */ static QualType _type_info_to_type (GITypeInfo *type_info, const ASTContext &context, const GirManager &gir_manager, TypeManager &type_manager) { switch (g_type_info_get_tag (type_info)) { /* Basic types. */ case GI_TYPE_TAG_VOID: return context.VoidTy; case GI_TYPE_TAG_BOOLEAN: return type_manager.find_type_by_name ("gboolean"); case GI_TYPE_TAG_INT8: return context.getIntTypeForBitwidth (8, true); case GI_TYPE_TAG_UINT8: return context.getIntTypeForBitwidth (8, false); case GI_TYPE_TAG_INT16: return context.getIntTypeForBitwidth (16, true); case GI_TYPE_TAG_UINT16: return context.getIntTypeForBitwidth (16, false); case GI_TYPE_TAG_INT32: return context.getIntTypeForBitwidth (32, true); case GI_TYPE_TAG_UINT32: return context.getIntTypeForBitwidth (32, false); case GI_TYPE_TAG_INT64: return context.getIntTypeForBitwidth (64, true); case GI_TYPE_TAG_UINT64: return context.getIntTypeForBitwidth (64, false); case GI_TYPE_TAG_FLOAT: return context.FloatTy; case GI_TYPE_TAG_DOUBLE: return context.DoubleTy; case GI_TYPE_TAG_GTYPE: /* FIXME: The type of GType can differ on different platforms * and under different languages. */ return context.getSizeType (); case GI_TYPE_TAG_UTF8: case GI_TYPE_TAG_FILENAME: return context.getPointerType (context.getConstType (context.CharTy)); case GI_TYPE_TAG_UNICHAR: return context.getIntTypeForBitwidth (32, false); /* Non-basic types */ case GI_TYPE_TAG_ARRAY: return _type_array_info_to_type (type_info, context, gir_manager, type_manager); case GI_TYPE_TAG_INTERFACE: return _type_interface_info_to_type (type_info, context, gir_manager, type_manager); case GI_TYPE_TAG_GLIST: return type_manager.find_pointer_type_by_name ("GList"); case GI_TYPE_TAG_GSLIST: return type_manager.find_pointer_type_by_name ("GSList"); case GI_TYPE_TAG_GHASH: return type_manager.find_pointer_type_by_name ("GHashTable"); case GI_TYPE_TAG_ERROR: return type_manager.find_pointer_type_by_name ("GError"); default: WARN ("Warning: Unexpected base info type " << g_base_info_get_type (type_info) << " for base info " << g_base_info_get_name (type_info) << "."); return QualType (); } } /* Returns true iff * • @a is a GObject, @b is a GObject, and @a is equal to or a subclass of @b; * • @a is a GInterface, @b is a GInterface, and @a is equal to @b; * • @a is a GInterface, @b is a GObject, and at least one of @a’s * prerequisites is equal to or a subtype of @b; * • @a is a GObject, @b is a GInterface, and @a or one of its superclasses * implements @b. */ static bool _is_gtype_subclass (GIBaseInfo *a, GIBaseInfo *b) { GIInfoType a_type, b_type; a_type = g_base_info_get_type (a); b_type = g_base_info_get_type (b); DEBUG ("Checking whether " << g_base_info_get_name (a) << " is a " "subtype of " << g_base_info_get_name (b) << "."); assert (a_type == GI_INFO_TYPE_OBJECT || a_type == GI_INFO_TYPE_INTERFACE); assert (b_type == GI_INFO_TYPE_OBJECT || b_type == GI_INFO_TYPE_INTERFACE); /* The case where @a and @b are equal. */ if (g_base_info_equal (a, b)) { return true; } /* The case where @a implements @b. */ if (a_type == GI_INFO_TYPE_OBJECT && b_type == GI_INFO_TYPE_INTERFACE) { for (gint i = 0; i < g_object_info_get_n_interfaces (a); i++) { GIInterfaceInfo *iface; iface = g_object_info_get_interface (a, i); bool eq = g_base_info_equal (iface, b); g_base_info_unref (iface); if (eq) { return true; } } } /* The case where one of @a’s prerequisites is a subtype of @b. Note * that prerequisites may be classes or interfaces, and all interfaces * have GObject as an implicit prerequisite. */ if (a_type == GI_INFO_TYPE_INTERFACE && b_type == GI_INFO_TYPE_OBJECT) { /* GObject prerequisite. */ if (strcmp (g_base_info_get_namespace (b), "GObject") == 0 && strcmp (g_base_info_get_name (b), "Object") == 0) { return true; } /* Normal prerequisites. */ for (gint i = 0; i < g_interface_info_get_n_prerequisites (a); i++) { GIBaseInfo *prereq; prereq = g_interface_info_get_prerequisite (a, i); bool subtype = _is_gtype_subclass (prereq, b); g_base_info_unref (prereq); if (subtype) { return true; } } } /* The case where @a is a subclass of @b, or a subclass of a class which * implements @b. */ if (a_type == GI_INFO_TYPE_OBJECT) { GIObjectInfo *ap = g_object_info_get_parent (a); if (ap == NULL) { return false; } bool subclass = _is_gtype_subclass (ap, b); g_base_info_unref (ap); return subclass; } return false; } /* A safe calling convention is any convention which is caller-cleanup and where * the callee can access its actual parameters left-to-right without calculating * offsets (e.g. if the actual parameters are pushed on to the stack in * right-to-left order by the caller, leaving the left-most actual parameter the * first one to be popped off by the callee). This allows us to safely pass * actual parameters in excess of the number of formal parameters expected by * the function, without risking corrupting the stack. * * An unsafe calling convention is any other convention. * * Known safe calling conventions: * - x86[1]: * • cdecl: Caller clean-up, parameters pushed right-to-left. * • syscall: Same. * • optlink: Same. * • GCC thiscall: Same. * • vectorcall: Same[13]. * • Microsoft x64: Caller clean-up (of parameters, not stack frame), * parameters pushed right-to-left[10]. * • System V AMD64 ABI: Same[11, §3.2.3]. * • regcall: Presumably the same as cdecl but using more registers[16, 17]. * - PowerPC (32-bit): Caller clean-up (of parameters, not stack frame), * parameters pushed right-to-left[2]. * - PowerPC (64-bit): Same[3]. * - ARM: Caller clean-up, parameters pushed right-to-left[4, §5.3]. * - MIPS: * • O32: Caller clean-up (of parameters, not stack frame), parameters pushed * right-to-left[6]. * • O64: Same[8]. * • N32: Unclear, but variadic arguments are supported[7]. * • N64: Same[7]. * • EABI32: Caller clean-up, parameters pushed right-to-left[5]. * • EABI64: Same[5]. * - SPARC: Callee clean-up, but the caller’s stack pointer value is saved in * its register window, so the callee doesn’t need to know the stack frame * size to restore the old stack pointer; parameters pushed right-to-left[9]. * - PNaCl: Equivalent to cdecl[12]. * - Swift: Lowers to C function calls, which we assume are handled using a * safe calling convention[14]. * - PreserveMost/PreserveAll: Arguments are passed identically to cdecl[15]. * * Known unsafe calling conventions: * - x86[1]: * • pascal: Callee clean-up, parameters pushed left-to-right. * • stdcall: Callee clean-up, parameters pushed right-to-left. * Note: This is used for Windows API calls, so we cannot use Windows API * for callbacks with swapped parameters. * • register/Borland fastcall: Same problems as pascal. * • Microsoft/GCC fastcall: Same problems as stdcall. * • Watcom fastcall: Same. * • safecall: Same. * • Microsoft thiscall: Same. * Note: This is used for C++ non-static member function calls (when using * MSVC++), so we cannot use C++ methods for callbacks with swapped * parameters. * • Microsoft vectorcall: Same. * - aarch64: vectorcall is assumed to be unsafe as it is on x86. * * References: * [1]: http://en.wikipedia.org/wiki/X86_calling_conventions * [2]: https://developer.apple.com/library/mac/documentation/ * DeveloperTools/Conceptual/LowLevelABI/ * 100-32-bit_PowerPC_Function_Calling_Conventions/ * 32bitPowerPC.html * [3]: https://developer.apple.com/library/mac/documentation/ * DeveloperTools/Conceptual/LowLevelABI/ * 110-64-bit_PowerPC_Function_Calling_Conventions/ * 64bitPowerPC.html * [4]: http://infocenter.arm.com/help/topic/com.arm.doc.ihi0042e/ * IHI0042E_aapcs.pdf * [5]: http://www.cygwin.com/ml/binutils/2003-06/msg00436.html * [6]: http://web.archive.org/web/20040930224745/http:// * www.caldera.com/developers/devspecs/mipsabi.pdf * [7]: http://techpubs.sgi.com/library/manuals/2000/007-2816-005/pdf/ * 007-2816-005.pdf * [8]: https://gcc.gnu.org/projects/mipso64-abi.html * [9]: http://math-atlas.sourceforge.net/devel/assembly/abi_sysV_sparc.pdf * [10]: http://msdn.microsoft.com/en-us/library/ms235286.aspx * [11]: http://x86-64.org/documentation/abi.pdf * [12]: https://developer.chrome.com/native-client/reference/ * pnacl-bitcode-abi#calling-conventions * [13]: https://msdn.microsoft.com/en-us/library/dn375768.aspx * [14]: https://github.com/apple/swift/blob/master/docs/CallingConvention.rst * [15]: https://github.com/llvm-mirror/llvm/blob/master/docs/LangRef.rst * #user-content-calling-conventions * [16]: https://clang.llvm.org/docs/AttributeReference.html * #regcall-gnu-regcall-regcall * [17]: https://software.intel.com/en-us/node/693069 * #GUID-F7675A83-FA01-44C9-BD69-D49F891F7566 */ static bool calling_convention_is_safe (CallingConv conv) { switch (conv) { case CC_C: /* cdecl */ case CC_Win64: /* x86-64 */ case CC_X86_64SysV: /* x86-64 */ case CC_AAPCS: /* ARM */ case CC_AAPCS_VFP: /* ARM with VFP registers */ case CC_Swift: /* Swift — lowered to C calling conventions */ case CC_PreserveMost: /* arguments passed identically to cdecl */ case CC_PreserveAll: /* arguments passed identically to cdecl */ case CC_X86RegCall: return true; case CC_X86StdCall: case CC_X86FastCall: case CC_X86ThisCall: case CC_X86Pascal: case CC_X86VectorCall: #ifdef HAVE_LLVM_8_0 case CC_AArch64VectorCall: #endif return false; case CC_IntelOclBicc: /* Intel OpenCL Built-Ins. I can’t find any documentation about * this, so let’s consider it unsafe. */ case CC_SpirFunction: case CC_OpenCLKernel: /* OpenCL SPIR calling conventions. These are ‘defined’ in §3.7 * of * https://www.khronos.org/files/opencl-spir-12-provisional.pdf, * but without enough information to classify them as safe or * unsafe. */ default: return false; } } /* Check the type of the callback in @expr (which is assumed to be a function * pointer or cast of a function pointer), asserting that it matches the type * of @signal_info. * * @dynamic_instance_info is information about the GObject subclass being passed * to g_signal_connect(). @static_instance_info is information about the GObject * subclass which the signal is defined on. @dynamic_instance_info should be a * (non-strict) subclass of @static_instance_info. * * @data_type is the qualified type of the user data parameter passed to * g_signal_connect(). It is checked against the first parameter of the callback * type iff %G_CONNECT_SWAPPED was specified at connection time (iff @is_swapped * is true). * * Returns true if the callback has the correct type. Emits an error and returns * false otherwise. */ static bool _check_signal_callback_type (const Expr *expr, GIBaseInfo *dynamic_instance_info, GIBaseInfo *static_instance_info, const QualType data_type, bool is_swapped, GISignalInfo *signal_info, CompilerInstance &compiler, const ASTContext &context, const GirManager &gir_manager, TypeManager &type_manager) { const FunctionProtoType *callback_type = NULL; SourceRange decl_range; /* for the callback definition */ /* We can’t just use expr->getType() here because we’ll typically get * GCallback as the type, which is not helpful. */ switch ((int) expr->getStmtClass ()) { case Stmt::StmtClass::DeclRefExprClass: { /* A reference to a function. Check the variable is a pointer * and look it up in the GIR namespace. */ const DeclRefExpr *decl_ref_expr = cast (expr); const ValueDecl *value_decl = decl_ref_expr->getDecl (); QualType value_type = value_decl->getType (); if (value_type->isFunctionNoProtoType ()) { /* Warning. */ /* TODO: Emit expected type of signal callback? */ Debug::emit_warning ("Could not check type of handler " "for signal ‘%0::%1’. Callback " "function declaration does not " "contain parameter types.", compiler, #ifdef HAVE_LLVM_8_0 expr->getBeginLoc () #else expr->getLocStart () #endif ) << gir_manager.get_c_name_for_type (static_instance_info) << g_base_info_get_name (signal_info) << decl_range; return false; } else if (!value_type->isFunctionProtoType ()) { /* Error. */ WARN_EXPR (__func__ << "() can’t handle value " "declarations of type ‘" << value_type.getAsString () << "’.", *expr); return false; } callback_type = cast (value_type); decl_range = cast (value_decl)->getCanonicalDecl ()->getSourceRange (); break; } case Stmt::StmtClass::ParenExprClass: { /* A parenthesised expression. */ const ParenExpr *paren_expr = cast (expr); return _check_signal_callback_type (paren_expr->getSubExpr (), dynamic_instance_info, static_instance_info, data_type, is_swapped, signal_info, compiler, context, gir_manager, type_manager); } case Stmt::StmtClass::ImplicitCastExprClass: case Stmt::StmtClass::CStyleCastExprClass: { /* A cast (explicit or C-style). */ const CastExpr *cast_expr = cast (expr); return _check_signal_callback_type (cast_expr->getSubExprAsWritten (), dynamic_instance_info, static_instance_info, data_type, is_swapped, signal_info, compiler, context, gir_manager, type_manager); } case Stmt::StmtClass::NoStmtClass: default: WARN_EXPR (__func__ << "() can’t handle expressions of type " << expr->getStmtClassName (), *expr); return false; } /* Check the function type against the signal info. Add 2 to n_args * because GIR omits the ‘self’ and ‘user_data’ arguments. * * Should we allow the callback type to have fewer parameters than the * signal prototype specifies? This is a complex one. It is undefined * behaviour by the C standard (§6.5.2.2¶6), but is defined behaviour in * most common calling conventions, so we can define ‘safe’ and ‘unsafe’ * calling conventions for passing actual parameters in excess of a * function’s number of formal parameters. * * In the interest of being practical, only emit an error on an * actual–formal parameter count mismatch if the user specifies a * callback with an unsafe calling convention. If the user specifies a * callback with a safe calling convention, emit a portability warning * instead, which should be low-importance and disableable. * * See the documentation for calling_convention_is_safe() for an * analysis. */ GICallableInfo *callable_info = signal_info; guint n_signal_args = g_callable_info_get_n_args (callable_info) + 2; guint n_callback_args = callback_type->getNumParams (); GITypeInfo expected_type_info; QualType actual_type, expected_type; if ((!calling_convention_is_safe (callback_type->getCallConv ()) && n_signal_args != n_callback_args) || n_signal_args < n_callback_args) { /* Error. */ /* TODO: Emit expected type of signal callback? */ Debug::emit_error ("Incorrect number of arguments in signal " "handler for signal ‘%0::%1’. Expected %2 " "but saw %3.", compiler, #ifdef HAVE_LLVM_8_0 expr->getBeginLoc () #else expr->getLocStart () #endif ) << gir_manager.get_c_name_for_type (static_instance_info) << g_base_info_get_name (signal_info) << n_signal_args << n_callback_args << decl_range; return false; } /* Check all arguments */ for (guint i = 0; i < n_callback_args; i++) { const gchar *arg_name; bool type_error; actual_type = callback_type->getParamType (i); if ((i == 0 && !is_swapped) || (i == n_signal_args - 1 && is_swapped)) { /* First argument is always a pointer to the GObject * instance which the signal is defined on; unless the * %G_CONNECT_SWAPPED flag has been passed, in which * case it’s the user_data, which is handled in the * ‘else’ block below. */ std::string c_type (gir_manager.get_c_name_for_type (static_instance_info)); expected_type = type_manager.find_pointer_type_by_name (c_type); arg_name = "self"; QualType atp = actual_type; while (atp->isPointerType ()) { atp = atp->getPointeeType (); } std::string actual_type_str = atp.getUnqualifiedType ().getAsString (); GIBaseInfo *actual_type_info = gir_manager.find_object_info (actual_type_str); if (actual_type_info == NULL && is_swapped) { /* Allow the instance argument to be a gpointer * if things are swapped. */ expected_type = context.getPointerType (context.VoidTy); DEBUG ("Comparing expected ‘" << expected_type.getAsString () << "’ with " "actual ‘" << actual_type.getAsString () << "’."); type_error = !(context.hasSameType (actual_type, expected_type)); } else if (actual_type_info == NULL) { /* Error. */ /* TODO: Emit expected type of signal callback? */ Debug::emit_warning ("Failed to resolve type " "of argument ‘%0’ in " "signal handler for " "signal ‘%1::%2’. Cannot " "find type with name " "‘%3’.", compiler, #ifdef HAVE_LLVM_8_0 expr->getBeginLoc () #else expr->getLocStart () #endif ) << arg_name << c_type << g_base_info_get_name (signal_info) << actual_type_str << decl_range; continue; } if (actual_type_info != NULL) { DEBUG ("Checking expected subclass relationships ‘" << g_base_info_get_name (dynamic_instance_info) << "’ <: ‘" << g_base_info_get_name (actual_type_info) << "’, ‘" << g_base_info_get_name (static_instance_info) << "’ <: ‘" << g_base_info_get_name (actual_type_info) << "’ and ‘" << g_base_info_get_name (dynamic_instance_info) << "’ <: ‘" << g_base_info_get_name (static_instance_info) << "’."); /* See the documentation at the top of the file for an * explanation of the (non-trivial) GObject type * checking for the first parameter. */ type_error = (actual_type_info == NULL || atp.isConstQualified () || !_is_gtype_subclass (dynamic_instance_info, actual_type_info) || !_is_gtype_subclass (static_instance_info, actual_type_info) || !_is_gtype_subclass (dynamic_instance_info, static_instance_info)); /* Remark about callbacks which have a instance * parameter which is not tightly typed. */ bool type_warning; type_warning = (!g_base_info_equal (static_instance_info, actual_type_info) && !type_error); if (type_warning && is_swapped) { Debug::emit_remark ("Type for argument ‘%0’ is " "not specific enough in " "swapped signal handler " "for signal ‘%1::%2’. It " "should be ‘%3’ but is " "currently ‘%4’.", compiler, #ifdef HAVE_LLVM_8_0 expr->getBeginLoc () #else expr->getLocStart () #endif ) << arg_name << gir_manager.get_c_name_for_type (static_instance_info) << g_base_info_get_name (signal_info) << expected_type.getAsString () << actual_type.getAsString () << decl_range; } else if (type_warning) { Debug::emit_remark ("Type for argument ‘%0’ is " "not specific enough in " "signal handler " "for signal ‘%1::%2’. It " "should be ‘%3’ but is " "currently ‘%4’.", compiler, #ifdef HAVE_LLVM_8_0 expr->getBeginLoc () #else expr->getLocStart () #endif ) << arg_name << gir_manager.get_c_name_for_type (static_instance_info) << g_base_info_get_name (signal_info) << expected_type.getAsString () << actual_type.getAsString () << decl_range; } g_base_info_unref (actual_type_info); } } else if ((i == n_signal_args - 1 && !is_swapped) || (i == 0 && is_swapped)) { /* Final argument is always a gpointer user_data. */ expected_type = context.getPointerType (context.VoidTy); arg_name = "user_data"; DEBUG ("Comparing expected ‘" << expected_type.getAsString () << "’ with actual " "‘" << actual_type.getAsString () << "’."); /* Although technically the callback function should * take a gpointer user_data argument, ignore cases * where it takes a more specific *pointer* type, since * it’s a common practice which causes no problems. This * eliminates a huge number of false positives. * * FIXME: In future, we might want to compare that the * @data_type (the type of the @user_data expression * passed to g_signal_connect()) is compatible with * @actual_type (the type of the formal @user_data * parameter of the callback). However, I think that can * be better implemented as a separate checker which * checks that closure parameters type check with the * callbacks they are used with, i.e. so it will also * check things like g_list_find_custom(). */ type_error = !(context.hasSameType (actual_type, expected_type) || actual_type->isPointerType ()); } else { /* All other arguments. */ GIArgInfo arg_info; g_callable_info_load_arg (callable_info, i - 1, &arg_info); g_arg_info_load_type (&arg_info, &expected_type_info); arg_name = g_base_info_get_name (&arg_info); expected_type = _type_info_to_type (&expected_type_info, context, gir_manager, type_manager); if (expected_type.isNull ()) { /* Error. */ /* TODO: Emit expected type of signal callback? */ Debug::emit_warning ("Failed to resolve type " "of argument ‘%0’ in " "signal handler for " "signal ‘%1::%2’. Cannot " "find type with name " "‘%3’.", compiler, #ifdef HAVE_LLVM_8_0 expr->getBeginLoc () #else expr->getLocStart () #endif ) << arg_name << gir_manager.get_c_name_for_type (static_instance_info) << g_base_info_get_name (signal_info) << g_base_info_get_name (&expected_type_info) << decl_range; continue; } DEBUG ("Comparing expected ‘" << expected_type.getAsString () << "’ with actual " "‘" << actual_type.getAsString () << "’."); /* Perform the check. */ type_error = (expected_type.isNull () || !context.hasSameType (actual_type, expected_type)); } /* Return as soon as the first error is encountered, since it’s * likely the user’s used completely the wrong callback type, * so further errors would just be noise. */ if (type_error && is_swapped) { /* Error. */ /* TODO: Emit expected type of signal callback? */ Debug::emit_error ("Incorrect type for argument ‘%0’ " "in swapped signal handler for " "signal ‘%1::%2’. Expected ‘%3’ but " "saw ‘%4’.", compiler, #ifdef HAVE_LLVM_8_0 expr->getBeginLoc () #else expr->getLocStart () #endif ) << arg_name << gir_manager.get_c_name_for_type (static_instance_info) << g_base_info_get_name (signal_info) << expected_type.getAsString () << actual_type.getAsString () << decl_range; return false; } else if (type_error) { /* Error. */ /* TODO: Emit expected type of signal callback? */ Debug::emit_error ("Incorrect type for argument ‘%0’ " "in signal handler for signal " "‘%1::%2’. Expected ‘%3’ but saw " "‘%4’.", compiler, #ifdef HAVE_LLVM_8_0 expr->getBeginLoc () #else expr->getLocStart () #endif ) << arg_name << gir_manager.get_c_name_for_type (static_instance_info) << g_base_info_get_name (signal_info) << expected_type.getAsString () << actual_type.getAsString () << decl_range; return false; } } /* Return type. */ g_callable_info_load_return_type (callable_info, &expected_type_info); actual_type = callback_type->getReturnType (); expected_type = _type_info_to_type (&expected_type_info, context, gir_manager, type_manager); if (expected_type.isNull ()) { /* Error. */ /* TODO: Emit expected type of signal callback? */ Debug::emit_warning ("Failed to resolve return type in signal " "handler for signal ‘%0::%1’. Cannot find " "type with name ‘%2’.", compiler, #ifdef HAVE_LLVM_8_0 expr->getBeginLoc () #else expr->getLocStart () #endif ) << gir_manager.get_c_name_for_type (static_instance_info) << g_base_info_get_name (signal_info) << g_base_info_get_name (&expected_type_info) << decl_range; return false; } if (!context.hasSameType (actual_type, expected_type)) { /* Error. */ /* TODO: Emit expected type of signal callback? */ Debug::emit_error ("Incorrect return type from signal handler " "for signal ‘%0::%1’. Expected ‘%2’ but saw " "‘%3’.", compiler, #ifdef HAVE_LLVM_8_0 expr->getBeginLoc () #else expr->getLocStart () #endif ) << gir_manager.get_c_name_for_type (static_instance_info) << g_base_info_get_name (signal_info) << expected_type.getAsString () << actual_type.getAsString () << decl_range; return false; } return true; } static bool _signal_flags_is_swapped (const Expr *flags_expr, const std::string &signal_name, CompilerInstance &compiler, const ASTContext &context) { switch ((int) flags_expr->getStmtClass ()) { case Stmt::StmtClass::DeclRefExprClass: { /* A reference to an enum, presumably. */ const DeclRefExpr *decl_ref_expr = cast (flags_expr); const ValueDecl *value_decl = decl_ref_expr->getDecl (); const EnumConstantDecl *enum_decl = dyn_cast (value_decl); if (enum_decl == NULL) { /* Error. */ WARN_EXPR (__func__ << "() can’t handle values " "of type ‘" << value_decl->getType ().getAsString () << "’.", *flags_expr); return false; } return (enum_decl->getNameAsString () == "G_CONNECT_SWAPPED"); } case Stmt::StmtClass::IntegerLiteralClass: { const IntegerLiteral *literal_expr = cast (flags_expr); const llvm::APInt i = literal_expr->getValue (); /* FIXME: Ugly as sin. */ return ((i.getLimitedValue ((1 << 8) - 1) & G_CONNECT_SWAPPED) != 0); } case Stmt::StmtClass::BinaryOperatorClass: { /* A binary operation, probably bitwise OR. */ const BinaryOperator *op_expr = cast (flags_expr); if (op_expr->getOpcode () != BO_Or) { /* Error. */ WARN_EXPR (__func__ << "() can’t handle binary " "operators other than bitwise OR.", *flags_expr); return false; } bool lhs_is_swapped = _signal_flags_is_swapped (op_expr->getLHS ()->IgnoreParenImpCasts (), signal_name, compiler, context); bool rhs_is_swapped = _signal_flags_is_swapped (op_expr->getRHS ()->IgnoreParenImpCasts (), signal_name, compiler, context); return lhs_is_swapped || rhs_is_swapped; } case Stmt::StmtClass::ParenExprClass: { /* A parenthesised expression. */ const ParenExpr *paren_expr = cast (flags_expr); return _signal_flags_is_swapped (paren_expr->getSubExpr (), signal_name, compiler, context); } case Stmt::StmtClass::ImplicitCastExprClass: case Stmt::StmtClass::CStyleCastExprClass: { /* A cast (explicit or C-style). */ const CastExpr *cast_expr = cast (flags_expr); return _signal_flags_is_swapped (cast_expr->getSubExprAsWritten (), signal_name, compiler, context); } case Stmt::StmtClass::NoStmtClass: default: WARN_EXPR (__func__ << "() can’t handle expressions of type " << flags_expr->getStmtClassName (), *flags_expr); return false; } } /* Parse the signal name out of a user-provided string. It could be in the * format: * • signal-name * • signal_name * • signal-name::some-detail * We need to return the signal name with hyphens. */ static std::string _parse_signal_name (const std::string &in) { std::string::size_type d = in.find ("::"); std::string signal_name; if (d != std::string::npos) { /* Strip off the detail string. * * FIXME: In future we could validate this. e.g. For the * ‘notify’ signal, validate it against the object’s * properties. */ signal_name = in.substr (0, d); } else { signal_name = in; } /* Normalise the string. */ std::replace (signal_name.begin (), signal_name.end (), '_', '-'); assert (!signal_name.empty ()); return signal_name; } /* Check the type of the function pointer passed to a g_signal_connect() call, * and ensure that its declaration matches the signal definition. * * If the signal name string is not a string literal, or if the concrete type * of the GObject is not known, we can’t check anything. */ static bool _check_gsignal_callback_type (const CallExpr &call, const FunctionDecl &func, const SignalFuncInfo *func_info, CompilerInstance &compiler, const ASTContext &context, const GirManager &gir_manager, TypeManager &type_manager) { const Expr *callback_arg, *gobject_arg, *signal_name_arg; const Expr *user_data_arg; const Expr *flags_arg = NULL; callback_arg = call.getArg (func_info->callback_param_index); gobject_arg = call.getArg (func_info->gobject_param_index); signal_name_arg = call.getArg (func_info->signal_name_param_index); user_data_arg = call.getArg (func_info->user_data_param_index); if (func_info->flags_param_index >= 0) { flags_arg = call.getArg (func_info->flags_param_index); } /* Check if the signal name is a string literal. If not, we can’t check * it. */ const StringLiteral *signal_name_str = dyn_cast (signal_name_arg->IgnoreParenImpCasts ()); if (signal_name_str == NULL) { /* Warning. */ Debug::emit_warning ("Non-string literal passed to signal " "name parameter. This is not an error " "but is highly unusual.", compiler, #ifdef HAVE_LLVM_8_0 signal_name_arg->getBeginLoc () #else signal_name_arg->getLocStart () #endif ); return false; } /* Sort out the signal name, splitting off the detail if necessary. */ StringRef signal_name_str_ref = signal_name_str->getString (); std::string signal_name = _parse_signal_name (signal_name_str_ref.str ()); DEBUG ("Using signal name ‘" << signal_name << "’."); /* Work out whether the instance and data have been swapped for extra * complication funtimes. */ bool is_swapped = false; if (flags_arg != NULL) { is_swapped = _signal_flags_is_swapped (flags_arg->IgnoreParenImpCasts (), signal_name, compiler, context); } /* Try and grab the GObject parameter’s type. This is the type of the * variable passed into g_signal_connect(). The @static_instance_info is * the type of the GObject subclass which defines the signal. */ GIObjectInfo *dynamic_instance_info, *static_instance_info = NULL; dynamic_instance_info = _expr_to_gtype (gobject_arg->IgnoreParenImpCasts (), context, gir_manager); if (dynamic_instance_info == NULL) { /* Emit a remark rather than a warning because the user may not * easily be able to add a GIR file containing the signal * information.. */ Debug::emit_remark ("Could not find GObject subclass for " "expression when connecting to signal " "‘%0’. To improve static analysis, add a " "typecast to the GObject parameter of " "%1() to the specific class defining the " "signal. Ensure a GIR file defining that " "class is loaded.", compiler, #ifdef HAVE_LLVM_8_0 call.getBeginLoc () #else call.getLocStart () #endif ) << signal_name << func_info->func_name << gobject_arg->getSourceRange () << signal_name_arg->getSourceRange (); return false; } DEBUG ("Using GIObjectInfo ‘" << g_base_info_get_name ((GIBaseInfo *) dynamic_instance_info) << "’ from namespace ‘" << g_base_info_get_namespace ((GIBaseInfo *) dynamic_instance_info) << "’."); /* Find the signal in the GObject. */ GISignalInfo *signal_info; signal_info = _gtype_look_up_signal (dynamic_instance_info, &static_instance_info, signal_name.c_str ()); if (signal_info == NULL) { /* Remark on the fact the signal information cannot be found. * We can’t really make this a warning, since the user may not * be able to easily add a GIR file containing the signal * information. */ Debug::emit_remark ("No signal named ‘%0’ in GObject class " "‘%1’. To improve static analysis, add a " "typecast to the GObject parameter of " "%2() to the specific class defining the " "signal. Ensure a GIR file defining that " "class is loaded.", compiler, #ifdef HAVE_LLVM_8_0 call.getBeginLoc () #else call.getLocStart () #endif ) << signal_name << gir_manager.get_c_name_for_type (dynamic_instance_info) << func_info->func_name << gobject_arg->getSourceRange () << signal_name_arg->getSourceRange (); g_base_info_unref (dynamic_instance_info); return false; } DEBUG ("Using GISignalInfo ‘" << g_base_info_get_name ((GIBaseInfo *) signal_info) << "’ from namespace ‘" << g_base_info_get_namespace ((GIBaseInfo *) signal_info) << "’."); /* Check the callback’s type. */ if (!_check_signal_callback_type (callback_arg->IgnoreParenImpCasts (), dynamic_instance_info, static_instance_info, user_data_arg->getType (), is_swapped, signal_info, compiler, context, gir_manager, type_manager)) { /* A diagnostic has already been emitted by * _check_signal_callback_type(). */ g_base_info_unref (signal_info); g_base_info_unref (dynamic_instance_info); g_base_info_unref (static_instance_info); return false; } g_base_info_unref (signal_info); g_base_info_unref (dynamic_instance_info); g_base_info_unref (static_instance_info); return true; } void GSignalConsumer::HandleTranslationUnit (ASTContext& context) { /* Run away if the plugin is disabled. */ if (!this->is_enabled ()) { return; } this->_visitor.TraverseDecl (context.getTranslationUnitDecl ()); } /* Note: Specifically overriding the Traverse* method here to re-implement * recursion to child nodes. */ bool GSignalVisitor::VisitCallExpr (CallExpr* expr) { const SignalFuncInfo *func_info; /* Can only handle direct function calls (i.e. not calling dereferenced * function pointers). */ const FunctionDecl *func = expr->getDirectCallee (); if (func == NULL) return true; /* We’re only interested in functions which connect signals. */ func_info = _func_is_gsignal_connect (*func); if (func_info == NULL) return true; /* Check the callback type. */ const GirManager *gir_manager = this->_gir_manager.get (); _check_gsignal_callback_type (*expr, *func, func_info, this->_compiler, func->getASTContext (), *gir_manager, this->_type_manager); return true; } } /* namespace tartan */