A simple plugin for GCC – Part 1
GCC's C and C++ compilers provide several extensions to address several programming needs not covered in the standards. One of these is the warn_unused_result
attribute. This attribute warns us that we are discarding the result of a function. Unfortunately, for C++ it does not always work as expected.
Function calls in C/C++
In contrast to many programming languages, C and C++ allow the programmer to ignore the result of a function. This is useful in those situations where the programmer is only interested in the side-effects of the function call (this includes returning extra information through variables passed by reference).
A typical case is the printf
family of functions. These function returns the number of characters written. This number may be a negative value if some input/output error happens or a positive number but lower than the characters we expected to write if there is not enough space in the output (as it happens with snprintf
/vsnprintf
). Almost no programmer bothers to check the result of these functions because their failure is a sign of a much deeper problem.
</p>
This means that most calls (probably 99% of them) to printf
are just
</p>
rather than
That said, some functions may return values that is essential for the programmer not to discard them. A classical example is malloc
.
GCC has an attribute warn_unused_result
that can be specified for functions whose value cannot be ignored. This is used for diagnostics.
1
2
3
4
5
6
7
__attribute__((warn_unused_result))
int* new_int(void);
void g()
{
new_int();
}
The code above will cause gcc to emit a warning.
For C, this attribute gives predictable results. For instance if we return a structure by value it also works.
1
2
3
4
5
6
7
8
9
10
11
12
typedef struct A
{
int *addr;
} A;
__attribute__((warn_unused_result))
A build_A(void);
void g()
{
build_A();
}
But surprisingly it does not work for C++.
What is going on?
C++ is much, much more complicated than C. And even apparently identical code carries much more semantic load in C++ than its equivalent C code. One of the things that C++ has that C does not are destructors. Consider the following code
1
2
3
4
5
6
7
8
9
10
11
12
struct B
{
int x;
~B();
};
B f();
void g()
{
f();
}
In line 11 of the code above, the function call creates a temporary value that looks like unused. But it is not, since this value is a class type and that class type has a non-trivial destructor (and a user-defined destructor is never trivial), the code must invoke the destructor to destroy this temporary value. So, what at first looks like a discarded value, it happens to be used. The following code tries to represent what actually happens: a temporary is created with the call to f
and then immediately destroyed.
9
10
11
12
void g()
{
B _temp( f() ); _temp.B::~B();
}
If a class does not have a user-defined destructor, the compiler will generate one for the user. Sometimes, that compiler-defined
destructor does not have to do anything. It exists internally in the compiler but it will generate no tangible code in our program. These destructors are called trivial. Trivial destructors happen when all the fields (and base classes) of a class type are of basic type (integer, pointer, reference, array of basic types, etc.) or of class type (or an array of) with a trivial destructor as well. Our class A shown above has a trivial destructor because its unique field is of type pointer to int, so a basic type.
For the cases where the destructor is trivial, the compiler will not emit any other call to the destructor, so the temporary object goes effectively unused. We would want these cases to be diagnosed.
With a motivation already set, we can now move on to implement this in GCC as a plugin.
GCC plugins
Ok, GCC is a rather old compiler (according to Wikipedia, its first release was in 1987) but it has evolved these years to gain new functionalities. One of these functionalities is being extensible via plugins. Plugins let us to extend the compiler without getting ourselves too dirty. GCC codebase, after 28 years, is huge and comes with its own quirks, so writing a plugin is not trivial but, in my opinion, can be very rewarding and revealing on how a real compiler works (for good and bad, of course).
Quick installation
At the moment the plugin interface of GCC follows a model similar to that of Linux modules: API stability is not guaranteed between versions. This means that, more or less our plugins will be tied to specific versions of GCC. This may not be ideal but this is how things are in GCC. For this post we will be using GCC 5.2 (released in June 16th 2015). At the time of writing this post, it is highly unlikely that your distribution provides that compiler version as the system compiler, so we will install it on a directory of our choice. This way we will avoid interferring with the system compiler, a sensitive piece of software that we do not want to break!
First, create a directory where we will put everything, enter it and download GCC 5.2
The next step involves building GCC. First we need to get some software required by GCC itself.
Now, create a build directory sibling to gcc-5.2.0
and make sure you enter it.
Now configure the compiler and build it. In this step we will specify where the compiler will be installed. Make sure that you are in gcc-build
! This step takes several minutes (about 15 minutes or so, depending on your machine) but you only have to do it once.
Now install it
Now check the installation
That's it. You can find much more information about installing here.
Skeleton of our plugin
In order to build a plugin for GCC, GCC installation installs a directory full of C++ headers representing its internal structures. We can now where these headers are installed using GCC itself.
Let's first create a directory where we will put our plugin.
While we can compile writing the commands, it soon becomes tedious, so let's write a Makefile
. We will use our just installed GCC 5.2, so make sure to fill the Makefile varialbe GCCDIR
below with the value of your $GCCDIR
.
This makefile by default will only build the plugin. If we want to test it, we can use make check
. Currently the check
rule compiles an empty file (actually /dev/null
) but this is enough to test it for now.
Now we need to write some code for the file warn_unused.cc
. As a starter, let's make a plugin that does nothing but prove it works.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#include <iostream>
// This is the first gcc header to be included
#include "gcc-plugin.h"
#include "plugin-version.h"
// We must assert that this plugin is GPL compatible
int plugin_is_GPL_compatible;
int plugin_init (struct plugin_name_args *plugin_info,
struct plugin_gcc_version *version)
{
// We check the current gcc loading this plugin against the gcc we used to
// created this plugin
if (!plugin_default_version_check (version, &gcc_version))
{
std::cerr << "This GCC plugin is for version " << GCCPLUGIN_VERSION_MAJOR
<< "." << GCCPLUGIN_VERSION_MINOR << "\n";
return 1;
}
// Let's print all the information given to this plugin!
std::cerr << "Plugin info\n";
std::cerr << "===========\n\n";
std::cerr << "Base name: " << plugin_info->base_name << "\n";
std::cerr << "Full name: " << plugin_info->full_name << "\n";
std::cerr << "Number of arguments of this plugin:" << plugin_info->
argc << "\n";
for (int i = 0; i < plugin_info->argc; i++)
{
std::cerr << "Argument " << i << ": Key: " << plugin_info->argv[i].
key << ". Value: " << plugin_info->argv[i].value << "\n";
}
if (plugin_info->version != NULL)
std::cerr << "Version string of the plugin: " << plugin_info->
version << "\n";
if (plugin_info->help != NULL)
std::cerr << "Help string of the plugin: " << plugin_info->help << "\n";
std::cerr << "\n";
std::cerr << "Version info\n";
std::cerr << "============\n\n";
std::cerr << "Base version: " << version->basever << "\n";
std::cerr << "Date stamp: " << version->datestamp << "\n";
std::cerr << "Dev phase: " << version->devphase << "\n";
std::cerr << "Revision: " << version->devphase << "\n";
std::cerr << "Configuration arguments: " << version->
configuration_arguments << "\n";
std::cerr << "\n";
std::cerr << "Plugin successfully initialized\n";
return 0;
}
Now we can build the plugin.
And test if it works
It works. Great!
How it works
Plugins are implemented using dynamic libraries. We use the flag -fplugin=file.so
, where file.so
is the dynamic library that implements our plugin. GCC loads the dynamic library and invokes the function plugin_init
. This function is used to initialize the plugin and to register itself inside the compiler. In its current state, our plugin does nothing but verify the version of GCC compatible with this plugin and show some information passed by GCC during loading.
Our plugin license must be compatible with GCC
Since plugins are somehow integrated with the code of GCC we have to assert in our code that the license of the plugin is GPL compatible by declaring a global variable plugin_is_GPL_compatible
.
Next steps
Now that we have the basic infrastructure to build plugins we can continue developing our plugin. But this post is long enough so let's postpone this until the next time.
You can find the supporting code for this post here. Make sure you fix the GCCDIR
variable in the Makefile
.