ABI artifacts
An ABI artifact is a relevant part of the ABI of a shared library
or program. Examples of ABI artifacts are exported types,
variables, functions, or ELF symbols exported by a shared
library.
The set of ABI artifact for a binary is called an ABI Corpus.
Harmful changes
A change in the diff report is considered harmful if it might
cause ABI compatibility issues. That is, it might prevent an
application dynamically linked against a given version of a
library to keep working with the changed subsequent versions of
the same library.
Harmless changes
A change in the diff report is considered harmless if it will not
cause any ABI compatibility issue. That is, it will not prevent
an application dynamically linked against given version of a
library to keep working with the changed subsequent versions of
the same library.
By default, abidiff
filters harmless changes from the diff
report.
Suppression specifications
Definition
A suppression specification file is a way for a user to instruct
abidiff, abipkgdiff or any other relevant libabigail tool to
avoid emitting reports for changes involving certain ABI
artifacts.
It contains directives (or specifications) that describe the set
of ABI artifacts to avoid emitting change reports about.
Introductory examples
Its syntax is based on a simplified and customized form of Ini
File Syntax. For instance, to specify that change reports on a
type named FooPrivateType should be suppressed, one could write
this suppression specification:
[suppress_type]
name = FooPrivateType
If we want to ensure that only change reports about structures
named FooPrivateType should be suppressed, we could write:
[suppress_type]
type_kind = struct
name = FooPrivateType
But we could also want to suppress change reports avoid typedefs
named FooPrivateType. In that case we would write:
[suppress_type]
type_kind = typedef
name = FooPrivateType
Or, we could want to suppress change reports about all struct
which names end with the string "PrivateType":
[suppress_type]
type_kind = struct
name_regexp = ^.*PrivateType
Let's now look at the generic syntax of suppression specification
files.
Syntax
Properties
More generally, the format of suppression lists is organized
around the concept of property. Every property has a name and a
value, delimited by the =
sign. E.g:
name = value
Leading and trailing white spaces are ignored around property
names and values.
Regular expressions
The value of some properties might be a regular expression. In
that case, they must comply with the syntax of extended POSIX
regular expressions. Note that Libabigail uses the regular
expression engine of the GNU C Library.
Escaping a character in a regular expression
When trying to match a string that contains a *
character, like
in the pointer type int*
, one must be careful to notice that the
character *
is a special character in the extended POSIX regular
expression syntax. And that character must be escaped for the
regular expression engine. Thus the regular expression that
would match the string int*
in a suppression file should be
int\\*
Wait; but then why the two \
characters? Well, because the \
character is a special character in the Ini File Syntax used for
specifying suppressions. So it must be escaped as well, so that
the Ini File parser leaves a \
character intact in the data
stream that is handed to the regular expression engine. Hence
the \\
targeted at the Ini File parser.
So, in short, to escape a character in a regular expression,
always prefix the character with the \\
sequence.
Modus operandi
Suppression specifications can be applied at two different points
of the processing pipeline of libabigail.
In the default operating mode called "late suppression mode",
suppression specifications are applied to the result of comparing
the in-memory internal representations of two ABIs. In this
mode, if an ABI artifact matches a suppression specification, its
changes are not mentioned in the ABI change report. The internal
representation of the "suppressed" changed ABI artifact is still
present in memory; it is just not mentioned in the ABI change
report. The change report can still mention statistics about the
number of changed ABI artifacts that were suppressed.
There is another operating mode called the "early suppression
mode" where suppression specifications are applied during the
construction of the in-memory internal representation of a given
ABI. In that mode, if an ABI artifact matches a suppression
specification, no in-memory internal representation is built for
it. As a result, no change about the matched ABI artifact is
going to be mentioned in the ABI change report and no statistic
about the number of suppressed ABI changes is available. Also,
please note that because suppressed ABI artifacts are removed
from the in-memory internal representation in this mode, the
amount memory used by the internal representation is potentially
smaller than the memory consumption in the late suppression mode.
Sections
Properties are then grouped into arbitrarily named sections that
shall not be nested. The name of the section is on a line by
itself and is surrounded by square brackets, i.e:
[section_name]
property1_name = property1_value
property2_name = property2_value
A section might or might not have properties. Sections that
expect to have properties and which are found nonetheless empty
are just ignored. Properties that are not recognized by the
reader are ignored as well.
Section names
Each different section can be thought of as being a directive to
suppress ABI change reports for a particular kind of ABI
artifact.
[suppress_file]
This directive prevents a given tool from loading a file (binary
or abixml file) if its file name or other properties match
certain properties. Thus, if the tool is meant to compare the
ABIs of two files, and if the directive prevents it from loading
either one of the files, then no comparison is performed.
Note that for the [suppress_file]
directive to work, at least one
of the following properties must be provided:
file_name_regexp
, file_name_not_regexp
, soname_regexp
,
soname_not_regexp
.
If none of the above properties are provided, then the
[suppress_file]
directive is simply ignored.
The potential properties of this sections are listed below:
• file_name_regexp
Usage:
file_name_regexp =
<regular-expression>
Prevents the system from loading the file which name matches
the regular expression specified as value of this property.
• file_name_not_regexp
Usage:
file_name_not_regexp =
<regular-expression>
Prevents the system from loading the file which name does not
match the regular expression specified as value of this
property.
• soname_regexp
Usage:
soname_regexp =
<regular-expression>
Prevents the system from loading the file which contains a
SONAME property that matches the regular expression of this
property. Note that this property also works on an abixml file
if it contains a SONAME property.
• soname_not_regexp
Usage:
soname_not_regexp =
<regular-expression>
Prevents the system from loading the file which contains a
SONAME property that does NOT match the regular expression of
this property. Note that this property also works on an abixml
file if it contains a SONAME property.
• label
Usage:
label =
<some-value>
Define a label for the section. A label is just an
informative string that might be used by the tool to refer to
a type suppression in error messages.
[suppress_type]
This directive suppresses report messages about a type change.
Note that for the [suppress_type]
directive to work, at least one
of the following properties must be provided:
file_name_regexp
, file_name_not_regexp
, soname_regexp
,
soname_not_regexp
, name
, name_regexp
, name_not_regexp
,
type_kind
, source_location_not_in
, source_location_not_regexp
.
If none of the above properties are provided, then the
[suppress_type]
directive is simply ignored.
The potential properties of this sections are listed below:
• file_name_regexp
Usage:
file_name_regexp =
<regular-expression>
Suppresses change reports about ABI artifacts that are defined
in a binary file which name matches the regular expression
specified as value of this property.
• file_name_not_regexp
Usage:
file_name_not_regexp =
<regular-expression>
Suppresses change reports about ABI artifacts that are defined
in a binary file which name does not match the regular
expression specified as value of this property.
• soname_regexp
Usage:
soname_regexp =
<regular-expression>
Suppresses change reports about ABI artifacts that are defined
in a shared library which SONAME property matches the regular
expression specified as value of this property.
• soname_not_regexp
Usage:
soname_not_regexp =
<regular-expression>
Suppresses change reports about ABI artifacts that are defined
in a shared library which SONAME property does not match the
regular expression specified as value of this property.
• name_regexp
Usage:
name_regexp =
<regular-expression>
Suppresses change reports involving types whose name matches
the regular expression specified as value of this property.
• name_not_regexp
Usage:
name_not_regexp =
<regular-expression>
Suppresses change reports involving types whose name does NOT
match the regular expression specified as value of this
property. Said otherwise, this property specifies which types
to keep, rather than types to suppress from reports.
• name
Usage:
name =
<a-value>
Suppresses change reports involving types whose name equals
the value of this property.
• type_kind
Usage:
type_kind = class | struct | union | enum |
array
| typedef
| builtin
Suppresses change reports involving a certain kind of type.
The kind of type to suppress change reports for is specified
by the possible values listed above:
•
class: suppress change reports for class types. Note
that
even if class types don't exist for C, this value
still triggers the suppression of change reports
for struct types, in C. In C++ however, it
should do what it suggests.
•
struct: suppress change reports for struct types in C or
C++.
Note that the value class
above is a super-set of
this one.
• union
: suppress change reports for union types.
• enum
: suppress change reports for enum types.
• array
: suppress change reports for array types.
• typedef
: suppress change reports for typedef types.
• builtin
: suppress change reports for built-in (or
native) types. Example of built-in types are char, int,
unsigned int, etc.
• source_location_not_in
Usage:
source_location_not_in =
<list-of-file-paths
>
Suppresses change reports involving a type which is defined in
a file which path is NOT listed in the value
list-of-file-paths
. Note that the value is a comma-separated
list of file paths e.g, this property
source_location_not_in = libabigail/abg-ir.h, libabigail/abg-dwarf-reader.h
suppresses change reports about all the types that are NOT
defined in header files whose path end up with the strings
libabigail/abg-ir.h or libabigail/abg-dwarf-reader.h.
• source_location_not_regexp
Usage:
source_location_not_regexp =
<regular-expression>
Suppresses change reports involving a type which is defined in
a file which path does NOT match the regular expression
provided as value of the property. E.g, this property
source_location_not_regexp = libabigail/abg-.*\\.h
suppresses change reports involving all the types that are NOT
defined in header files whose path match the regular
expression provided a value of the property.
• has_data_member_inserted_at
Usage:
has_data_member_inserted_at =
<offset-in-bit
>
Suppresses change reports involving a type which has at least
one data member inserted at an offset specified by the
property value offset-in-bit
. The value offset-in-bit
is
either:
• an integer value, expressed in bits, which denotes
the offset of the insertion point of the data member,
starting from the beginning of the relevant structure
or class.
• the keyword end
which is a named constant which value
equals the offset of the end of the of the structure
or class.
• the function call expression
offset_of(data-member-name)
where data-member-name is
the name of a given data member of the relevant
structure or class. The value of this function call
expression is an integer that represents the offset
of the data member denoted by data-member-name
.
• the function call expression
offset_after(data-member-name)
where data-member-name
is the name of a given data member of the relevant
structure or class. The value of this function call
expression is an integer that represents the offset
of the point that comes right after the region
occupied by the data member denoted by
data-member-name
.
• has_data_member_inserted_between
Usage:
has_data_member_inserted_between =
{<range-begin
>,
<range-end
>}
Suppresses change reports involving a type which has at least
one data mber inserted at an offset that is comprised in the
range between range-begin`` and range-end
. Please note that
each of the values range-begin
and range-end
can be of the
same form as the has_data_member_inserted_at property above.
Usage examples of this properties are:
has_data_member_inserted_between = {8, 64}
or:
has_data_member_inserted_between = {16, end}
or:
has_data_member_inserted_between = {offset_after(member1), end}
• has_data_members_inserted_between
Usage:
has_data_members_inserted_between =
{<sequence-of-ranges>}
Suppresses change reports involving a type which has multiple
data member inserted in various offset ranges. A usage
example of this property is, for instance:
has_data_members_inserted_between = {{8, 31}, {72, 95}}
This usage example suppresses change reports involving a type
which has data members inserted in bit offset ranges [8 31]
and [72 95]. The length of the sequence of ranges or this
has_data_members_inserted_between
is not bounded; it can be as
long as the system can cope with. The values of the
boundaries of the ranges are of the same kind as for the
has_data_member_inserted_at property above.
Another usage example of this property is thus:
has_data_members_inserted_between =
{
{offset_after(member0), offset_of(member1)},
{72, end}
}
• accessed_through
Usage:
accessed_through =
<some-predefined-values>
Suppress change reports involving a type which is referred to
either directly or through a pointer or a reference. The
potential values of this property are the predefined keywords
below:
• direct
So if the [suppress_type]
contains the property
description:
accessed_through = direct
then changes about a type that is referred-to directly
(i.e, not through a pointer or a reference) are going to
be suppressed.
• pointer
If the accessed_through
property is set to the value
pointer
then changes about a type that is referred-to
through a pointer are going to be suppressed.
• reference
If the accessed_through
property is set to the value
reference
then changes about a type that is referred-to
through a reference are going to be suppressed.
• reference-or-pointer
If the accessed_through
property is set to the value
reference-or-pointer
then changes about a type that is
referred-to through either a reference or a pointer are
going to be suppressed.
For an extensive example of how to use this property, please
check out the example below about suppressing change reports
about types accessed either directly or through pointers.
• drop
Usage:
drop =
yes | no
If a type is matched by a suppression specification which
contains the "drop" property set to "yes" (or to "true") then
the type is not even going to be represented in the internal
representation of the ABI being analyzed. This property makes
its enclosing suppression specification to be applied in the
early suppression specification mode. The net effect is that
it potentially reduces the memory used to represent the ABI
being analyzed.
Please note that for this property to be effective, the
enclosing suppression specification must have at least one of
the following properties specified: name_regexp
, name
,
name_regexp
, source_location_not_in
or
source_location_not_regexp
.
• label
Usage:
label =
<some-value>
Define a label for the section. A label is just an
informative string that might be used by a tool to refer to a
type suppression in error messages.
• changed_enumerators
Usage:
changed_enumerators =
<list-of-enumerators>
Suppresses change reports involving changes in the value of
enumerators of a given enum type. This property is applied if
the type_kind
property is set to the value enum
, at least. The
value of the changed_enumerators
is a comma-separated list of
the enumerators that the user expects to change. For instance:
changed_enumerators = LAST_ENUMERATORS0, LAST_ENUMERATOR1
[suppress_function]
This directive suppresses report messages about changes on a set
of functions.
Note that for the [suppress_function]
directive to work, at least
one of the following properties must be provided:
label
, file_name_regexp
, file_name_not_regexp
, soname_regexp
,
soname_not_regexp
, name
, name_regexp
, name_not_regexp
,
parameter
, return_type_name
, return_type_regexp
, symbol_name
,
symbol_name_regexp
, symbol_name_not_regexp
, symbol_version
,
symbol_version_regexp
.
If none of the above properties are provided, then the
[suppress_function]
directive is simply ignored.
The potential properties of this sections are:
• label
Usage:
label =
<some-value>
This property is the same as the label property defined above.
• file_name_regexp
Usage:
file_name_regexp =
<regular-expression>
Suppresses change reports about ABI artifacts that are defined
in a binary file which name matches the regular expression
specified as value of this property.
• file_name_not_regexp
Usage:
file_name_not_regexp =
<regular-expression>
Suppresses change reports about ABI artifacts that are defined
in a binary file which name does not match the regular
expression specified as value of this property.
• soname_regexp
Usage:
soname_regexp =
<regular-expression>
Suppresses change reports about ABI artifacts that are defined
in a shared library which SONAME property matches the regular
expression specified as value of this property.
• soname_not_regexp
Usage:
soname_not_regexp =
<regular-expression>
Suppresses change reports about ABI artifacts that are defined
in a shared library which SONAME property does not match the
regular expression specified as value of this property.
• name
Usage:
name =
<some-value>
Suppresses change reports involving functions whose name
equals the value of this property.
• name_regexp
Usage:
name_regexp =
<regular-expression>
Suppresses change reports involving functions whose name
matches the regular expression specified as value of this
property.
Let's consider the case of functions that have several symbol
names. This happens when the underlying symbol for the
function has aliases. Each symbol name is actually one alias
name.
In this case, if the regular expression matches the name of at
least one of the aliases names, then it must match the names
of all of the aliases of the function for the directive to
actually suppress the diff reports for said function.
• name_not_regexp
Usage:
name_not_regexp =
<regular-expression>
Suppresses change reports involving functions whose names
don't match the regular expression specified as value of this
property.
The rules for functions that have several symbol names are the
same rules as for the name_regexp
property above.
• change_kind
Usage:
change_kind =
<predefined-possible-values>
Specifies the kind of changes this suppression specification
should apply to. The possible values of this property as well
as their meaning are listed below:
• function-subtype-change
This suppression specification applies to functions that
which have at least one sub-type that has changed.
• added-function
This suppression specification applies to functions that
have been added to the binary.
• deleted-function
This suppression specification applies to functions that
have been removed from the binary.
• all
This suppression specification applies to functions that
have all of the changes above. Note that not providing
the change_kind
property at all is equivalent to setting
it to the value all
.
• parameter
Usage:
parameter =
<function-parameter-specification>
Suppresses change reports involving functions whose parameters
match the parameter specification indicated as value of this
property.
The format of the function parameter specification is:
' <parameter-index> <space> <type-name-or-regular-expression>
That is, an apostrophe followed by a number that is the index
of the parameter, followed by one of several spaces, followed
by either the name of the type of the parameter, or a regular
expression describing a family of parameter type names.
If the parameter type name is designated by a regular
expression, then said regular expression must be enclosed
between two slashes; like /some-regular-expression/
.
The index of the first parameter of the function is zero.
Note that for member functions (methods of classes), the this
is the first parameter that comes after the implicit "this"
pointer parameter.
Examples of function parameter specifications are:
'0 int
Which means, the parameter at index 0, whose type name is int
.
'4 unsigned char*
Which means, the parameter at index 4, whose type name is
unsigned char*
.
'2 /^foo.*&/
Which means, the parameter at index 2, whose type name starts
with the string "foo" and ends with an '&'. In other words,
this is the third parameter and it's a reference on a type
that starts with the string "foo".
• return_type_name
Usage:
return_type_name =
<some-value>
Suppresses change reports involving functions whose return
type name equals the value of this property.
• return_type_regexp
Usage:
return_type_regexp =
<regular-expression>
Suppresses change reports involving functions whose return
type name matches the regular expression specified as value of
this property.
• symbol_name
Usage:
symbol_name =
<some-value>
Suppresses change reports involving functions whose symbol
name equals the value of this property.
• symbol_name_regexp
Usage:
symbol_name_regexp =
<regular-expression>
Suppresses change reports involving functions whose symbol
name matches the regular expression specified as value of this
property.
Let's consider the case of functions that have several symbol
names. This happens when the underlying symbol for the
function has aliases. Each symbol name is actually one alias
name.
In this case, the regular expression must match the names of
all of the aliases of the function for the directive to
actually suppress the diff reports for said function.
• symbol_name_not_regexp
Usage:
symbol_name_not_regexp =
<regular-expression>
Suppresses change reports involving functions whose symbol
name does not match the regular expression specified as value
of this property.
• symbol_version
Usage:
symbol_version =
<some-value>
Suppresses change reports involving functions whose symbol
version equals the value of this property.
• symbol_version_regexp
Usage:
symbol_version_regexp =
<regular-expression>
Suppresses change reports involving functions whose symbol
version matches the regular expression specified as value of
this property.
• drop
Usage:
drop =
yes | no
If a function is matched by a suppression specification which
contains the "drop" property set to "yes" (or to "true") then
the function is not even going to be represented in the
internal representation of the ABI being analyzed. This
property makes its enclosing suppression specification to be
applied in the early suppression specification mode. The net
effect is that it potentially reduces the memory used to
represent the ABI being analyzed.
Please note that for this property to be effective, the
enclosing suppression specification must have at least one of
the following properties specified: name_regexp
, name
,
name_regexp
, source_location_not_in
or
source_location_not_regexp
.
[suppress_variable]
This directive suppresses report messages about changes on a set
of variables.
Note that for the [suppress_variable]
directive to work, at least
one of the following properties must be provided:
label
, file_name_regexp
, file_name_not_regexp
, soname_regexp
,
soname_not_regexp
, name
, name_regexp
, name_not_regexp
,
symbol_name
, symbol_name_regexp
, symbol_name_not_regexp
,
symbol_version
, symbol_version_regexp
, type_name
,
type_name_regexp
.
If none of the above properties are provided, then the
[suppress_variable]
directive is simply ignored.
The potential properties of this sections are:
• label
Usage:
label =
<some-value>
This property is the same as the label property defined above.
• file_name_regexp
Usage:
file_name_regexp =
<regular-expression>
Suppresses change reports about ABI artifacts that are defined
in a binary file which name matches the regular expression
specified as value of this property.
• file_name_not_regexp
Usage:
file_name_not_regexp =
<regular-expression>
Suppresses change reports about ABI artifacts that are defined
in a binary file which name does not match the regular
expression specified as value of this property.
• soname_regexp
Usage:
soname_regexp =
<regular-expression>
Suppresses change reports about ABI artifacts that are defined
in a shared library which SONAME property matches the regular
expression specified as value of this property.
• soname_not_regexp
Usage:
soname_not_regexp =
<regular-expression>
Suppresses change reports about ABI artifacts that are defined
in a shared library which SONAME property does not match the
regular expression specified as value of this property.
• name
Usage:
name =
<some-value>
Suppresses change reports involving variables whose name
equals the value of this property.
• name_regexp
Usage:
name_regexp =
<regular-expression>
Suppresses change reports involving variables whose name
matches the regular expression specified as value of this
property.
• change_kind
Usage:
change_kind =
<predefined-possible-values>
Specifies the kind of changes this suppression specification
should apply to. The possible values of this property as well
as their meaning are the same as when it's used in the
[suppress_function] section.
• symbol_name
Usage:
symbol_name =
<some-value>
Suppresses change reports involving variables whose symbol
name equals the value of this property.
• symbol_name_regexp
Usage:
symbol_name_regexp =
<regular-expression>
Suppresses change reports involving variables whose symbol
name matches the regular expression specified as value of this
property.
• symbol_name_not_regexp
Usage:
symbol_name_not_regexp =
<regular-expression>
Suppresses change reports involving variables whose symbol
name does not match the regular expression specified as value
of this property.
• symbol_version
Usage:
symbol_version =
<some-value>
Suppresses change reports involving variables whose symbol
version equals the value of this property.
• symbol_version_regexp
Usage:
symbol_version_regexp =
<regular-expression>
Suppresses change reports involving variables whose symbol
version matches the regular expression specified as value of
this property.
• type_name
Usage:
type_name =
<some-value>
Suppresses change reports involving variables whose type name
equals the value of this property.
• type_name_regexp
Usage:
type_name_regexp =
<regular-expression>
Suppresses change reports involving variables whose type name
matches the regular expression specified as value of this
property.
Comments
;
or #
ASCII character at the beginning of a line indicates a
comment. Comment lines are ignored.
Code examples
1. Suppressing change reports about types.
Suppose we have a library named libtest1-v0.so
which contains
this very useful code:
$ cat -n test1-v0.cc
1 // A forward declaration for a type considered to be opaque to
2 // function foo() below.
3 struct opaque_type;
4
5 // This function cannot touch any member of opaque_type. Hence,
6 // changes to members of opaque_type should not impact foo, as far as
7 // ABI is concerned.
8 void
9 foo(opaque_type*)
10 {
11 }
12
13 struct opaque_type
14 {
15 int member0;
16 char member1;
17 };
$
Let's change the layout of struct opaque_type by inserting a data
member around line 15, leading to a new version of the library,
that we shall name libtest1-v1.so
:
$ cat -n test1-v1.cc
1 // A forward declaration for a type considered to be opaque to
2 // function foo() below.
3 struct opaque_type;
4
5 // This function cannot touch any member of opaque_type; Hence,
6 // changes to members of opaque_type should not impact foo, as far as
7 // ABI is concerned.
8 void
9 foo(opaque_type*)
10 {
11 }
12
13 struct opaque_type
14 {
15 char added_member; // <-- a new member got added here now.
16 int member0;
17 char member1;
18 };
$
Let's compile both examples. We shall not forget to compile them
with debug information generation turned on:
$ g++ -shared -g -Wall -o libtest1-v0.so test1-v0.cc
$ g++ -shared -g -Wall -o libtest1-v1.so test1-v1.cc
Let's ask abidiff which ABI differences it sees between
libtest1-v0.so
and libtest1-v1.so
:
$ abidiff libtest1-v0.so libtest1-v1.so
Functions changes summary: 0 Removed, 1 Changed, 0 Added function
Variables changes summary: 0 Removed, 0 Changed, 0 Added variable
1 function with some indirect sub-type change:
[C]'function void foo(opaque_type*)' has some indirect sub-type changes:
parameter 0 of type 'opaque_type*' has sub-type changes:
in pointed to type 'struct opaque_type':
size changed from 64 to 96 bits
1 data member insertion:
'char opaque_type::added_member', at offset 0 (in bits)
2 data member changes:
'int opaque_type::member0' offset changed from 0 to 32
'char opaque_type::member1' offset changed from 32 to 64
So abidiff
reports that the opaque_type's layout has changed in a
significant way, as far as ABI implications are concerned, in
theory. After all, a sub-type (struct opaque_type
) of an
exported function (foo()
) has seen its layout change. This might
have non negligible ABI implications. But in practice here, the
programmer of the litest1-v1.so library knows that the "soft"
contract between the function foo()
and the type struct
opaque_type
is to stay away from the data members of the type.
So layout changes of struct opaque_type
should not impact foo()
.
Now to teach abidiff
about this soft contract and have it avoid
emitting what amounts to false positives in this case, we write
the suppression specification file below:
$ cat test1.suppr
[suppress_type]
type_kind = struct
name = opaque_type
Translated in plain English, this suppression specification would
read: "Do not emit change reports about a struct which name is
opaque_type".
Let's now invoke abidiff
on the two versions of the library
again, but this time with the suppression specification:
$ abidiff --suppressions test1.suppr libtest1-v0.so libtest1-v1.so
Functions changes summary: 0 Removed, 0 Changed (1 filtered out), 0 Added function
Variables changes summary: 0 Removed, 0 Changed, 0 Added variable
As you can see, abidiff
does not report the change anymore; it
tells us that it was filtered out instead.
Suppressing change reports about types with data member
insertions
Suppose the first version of a library named libtest3-v0.so
has
this source code:
/* Compile this with:
gcc -g -Wall -shared -o libtest3-v0.so test3-v0.c
*/
struct S
{
char member0;
int member1; /*
between member1 and member2, there is some padding,
at least on some popular platforms. On
these platforms, adding a small enough data
member into that padding shouldn't change
the offset of member1. Right?
*/
};
int
foo(struct S* s)
{
return s->member0 + s->member1;
}
Now, suppose the second version of the library named
libtest3-v1.so
has this source code in which a data member has
been added in the padding space of struct S and another data
member has been added at its end:
/* Compile this with:
gcc -g -Wall -shared -o libtest3-v1.so test3-v1.c
*/
struct S
{
char member0;
char inserted1; /* <---- A data member has been added here... */
int member1;
char inserted2; /* <---- ... and another one has been added here. */
};
int
foo(struct S* s)
{
return s->member0 + s->member1;
}
In libtest3-v1.so, adding char data members S::inserted1
and
S::inserted2
can be considered harmless (from an ABI
compatibility perspective), at least on the x86 platform, because
that doesn't change the offsets of the data members S::member0
and S::member1. But then running abidiff
on these two versions
of library yields:
$ abidiff libtest3-v0.so libtest3-v1.so
Functions changes summary: 0 Removed, 1 Changed, 0 Added function
Variables changes summary: 0 Removed, 0 Changed, 0 Added variable
1 function with some indirect sub-type change:
[C]'function int foo(S*)' has some indirect sub-type changes:
parameter 0 of type 'S*' has sub-type changes:
in pointed to type 'struct S':
type size changed from 64 to 96 bits
2 data member insertions:
'char S::inserted1', at offset 8 (in bits)
'char S::inserted2', at offset 64 (in bits)
$
That is, abidiff
shows us the two changes, even though we (the
developers of that very involved library) know that these changes
are harmless in this particular context.
Luckily, we can devise a suppression specification that
essentially tells abidiff to filter out change reports about
adding a data member between S::member0
and S::member1
, and
adding a data member at the end of struct S. We have written
such a suppression specification in a file called test3-1.suppr
and it unsurprisingly looks like:
[suppress_type]
name = S
has_data_member_inserted_between = {offset_after(member0), offset_of(member1)}
has_data_member_inserted_at = end
Now running abidiff
with this suppression specification yields:
$ ../build/tools/abidiff --suppressions test3-1.suppr libtest3-v0.so libtest3-v1.so
Functions changes summary: 0 Removed, 0 Changed (1 filtered out), 0 Added function
Variables changes summary: 0 Removed, 0 Changed, 0 Added variable
$
Hooora! \o/ (I guess)
Suppressing change reports about types accessed either directly
or through pointers
Suppose we have a first version of an object file which source
code is the file widget-v0.cc below:
// Compile with: g++ -g -c widget-v0.cc
struct widget
{
int x;
int y;
widget()
:x(), y()
{}
};
void
fun0(widget*)
{
// .. do stuff here.
}
void
fun1(widget&)
{
// .. do stuff here ..
}
void
fun2(widget w)
{
// ... do other stuff here ...
}
Now suppose in the second version of that file, named
widget-v1.cc, we have added some data members at the end of the
type struct widget
; here is what the content of that file would
look like:
// Compile with: g++ -g -c widget-v1.cc
struct widget
{
int x;
int y;
int w; // We have added these two new data members here ..
int h; // ... and here.
widget()
: x(), y(), w(), h()
{}
};
void
fun0(widget*)
{
// .. do stuff here.
}
void
fun1(widget&)
{
// .. do stuff here ..
}
void
fun2(widget w)
{
// ... do other stuff here ...
}
When we invoke abidiff
on the object files resulting from the
compilation of the two file above, here is what we get:
$ abidiff widget-v0.o widget-v1.o
Functions changes summary: 0 Removed, 2 Changed (1 filtered out), 0 Added functions
Variables changes summary: 0 Removed, 0 Changed, 0 Added variable
2 functions with some indirect sub-type change:
[C]'function void fun0(widget*)' has some indirect sub-type changes:
parameter 1 of type 'widget*' has sub-type changes:
in pointed to type 'struct widget':
type size changed from 64 to 128 bits
2 data member insertions:
'int widget::w', at offset 64 (in bits)
'int widget::h', at offset 96 (in bits)
[C]'function void fun2(widget)' has some indirect sub-type changes:
parameter 1 of type 'struct widget' has sub-type changes:
details were reported earlier
$
I guess a little bit of explaining is due here. abidiff
detects
that two data member got added at the end of struct widget
. it
also tells us that the type change impacts the exported function
fun0()
which uses the type struct widget
through a pointer, in
its signature.
Careful readers will notice that the change to struct widget
also
impacts the exported function fun1()
, that uses type struct
widget
through a reference. But then abidiff
doesn't tell us
about the impact on that function fun1()
because it has evaluated
that change as being redundant
with the change it reported on
fun0()
. It has thus filtered it out, to avoid cluttering the
output with noise.
Redundancy detection and filtering is fine and helpful to avoid
burying the important information in a sea of noise. However, it
must be treated with care, by fear of mistakenly filtering out
relevant and important information.
That is why abidiff
tells us about the impact that the change to
struct widget
has on function fun2()
. In this case, that
function uses the type struct widget directly
(in its signature).
It does not use it via a pointer or a reference. In this case,
the direct use of this type causes fun2()
to be exposed to a
potentially harmful ABI change. Hence, the report about fun2()
is not filtered out, even though it's about that same change on
struct widget
.
To go further in suppressing reports about changes that are
harmless and keeping only those that we know are harmful, we
would like to go tell abidiff to suppress reports about this
particular struct widget
change when it impacts uses of struct
widget
through a pointer or reference. In other words, suppress
the change reports about fun0() and fun1()
. We would then write
this suppression specification, in file widget.suppr
:
[suppress_type]
name = widget
type_kind = struct
has_data_member_inserted_at = end
accessed_through = reference-or-pointer
# So this suppression specification says to suppress reports about
# the type 'struct widget', if this type was added some data member
# at its end, and if the change impacts uses of the type through a
# reference or a pointer.
Invoking abidiff
on widget-v0.o
and widget-v1.o
with this
suppression specification yields:
$ abidiff --suppressions widget.suppr widget-v0.o widget-v1.o
Functions changes summary: 0 Removed, 1 Changed (2 filtered out), 0 Added function
Variables changes summary: 0 Removed, 0 Changed, 0 Added variable
1 function with some indirect sub-type change:
[C]'function void fun2(widget)' has some indirect sub-type changes:
parameter 1 of type 'struct widget' has sub-type changes:
type size changed from 64 to 128 bits
2 data member insertions:
'int widget::w', at offset 64 (in bits)
'int widget::h', at offset 96 (in bits)
$
As expected, I guess.
Suppressing change reports about functions.
Suppose we have a first version a library named libtest2-v0.so
whose source code is:
$ cat -n test2-v0.cc
1 struct S1
2 {
3 int m0;
4
5 S1()
6 : m0()
7 {}
8 };
9
10 struct S2
11 {
12 int m0;
13
14 S2()
15 : m0()
16 {}
17 };
18
19 struct S3
20 {
21 int m0;
22
23 S3()
24 : m0()
25 {}
26 };
27
28 int
29 func(S1&)
30 {
31 // suppose the code does something with the argument.
32 return 0;
33
34 }
35
36 char
37 func(S2*)
38 {
39 // suppose the code does something with the argument.
40 return 0;
41 }
42
43 unsigned
44 func(S3)
45 {
46 // suppose the code does something with the argument.
47 return 0;
48 }
$
And then we come up with a second version libtest2-v1.so
of that
library; the source code is modified by making the structures S1
,
S2
, S3
inherit another struct:
$ cat -n test2-v1.cc
1 struct base_type
2 {
3 int m_inserted;
4 };
5
6 struct S1 : public base_type // <--- S1 now has base_type as its base
7 // type.
8 {
9 int m0;
10
11 S1()
12 : m0()
13 {}
14 };
15
16 struct S2 : public base_type // <--- S2 now has base_type as its base
17 // type.
18 {
19 int m0;
20
21 S2()
22 : m0()
23 {}
24 };
25
26 struct S3 : public base_type // <--- S3 now has base_type as its base
27 // type.
28 {
29 int m0;
30
31 S3()
32 : m0()
33 {}
34 };
35
36 int
37 func(S1&)
38 {
39 // suppose the code does something with the argument.
40 return 0;
41
42 }
43
44 char
45 func(S2*)
46 {
47 // suppose the code does something with the argument.
48 return 0;
49 }
50
51 unsigned
52 func(S3)
53 {
54 // suppose the code does something with the argument.
55 return 0;
56 }
$
Now let's build the two libraries:
g++ -Wall -g -shared -o libtest2-v0.so test2-v0.cc
g++ -Wall -g -shared -o libtest2-v0.so test2-v0.cc
Let's look at the output of abidiff
:
$ abidiff libtest2-v0.so libtest2-v1.so
Functions changes summary: 0 Removed, 3 Changed, 0 Added functions
Variables changes summary: 0 Removed, 0 Changed, 0 Added variable
3 functions with some indirect sub-type change:
[C]'function unsigned int func(S3)' has some indirect sub-type changes:
parameter 0 of type 'struct S3' has sub-type changes:
size changed from 32 to 64 bits
1 base class insertion:
struct base_type
1 data member change:
'int S3::m0' offset changed from 0 to 32
[C]'function char func(S2*)' has some indirect sub-type changes:
parameter 0 of type 'S2*' has sub-type changes:
in pointed to type 'struct S2':
size changed from 32 to 64 bits
1 base class insertion:
struct base_type
1 data member change:
'int S2::m0' offset changed from 0 to 32
[C]'function int func(S1&)' has some indirect sub-type changes:
parameter 0 of type 'S1&' has sub-type changes:
in referenced type 'struct S1':
size changed from 32 to 64 bits
1 base class insertion:
struct base_type
1 data member change:
'int S1::m0' offset changed from 0 to 32
$
Let's tell abidiff
to avoid showing us the differences on the
overloads of func
that takes either a pointer or a reference.
For that, we author this simple suppression specification:
$ cat -n libtest2.suppr
1 [suppress_function]
2 name = func
3 parameter = '0 S1&
4
5 [suppress_function]
6 name = func
7 parameter = '0 S2*
$
And then let's invoke abidiff
with the suppression specification:
$ ../build/tools/abidiff --suppressions libtest2.suppr libtest2-v0.so libtest2-v1.so
Functions changes summary: 0 Removed, 1 Changed (2 filtered out), 0 Added function
Variables changes summary: 0 Removed, 0 Changed, 0 Added variable
1 function with some indirect sub-type change:
[C]'function unsigned int func(S3)' has some indirect sub-type changes:
parameter 0 of type 'struct S3' has sub-type changes:
size changed from 32 to 64 bits
1 base class insertion:
struct base_type
1 data member change:
'int S3::m0' offset changed from 0 to 32
The suppression specification could be reduced using regular
expressions:
$ cat -n libtest2-1.suppr
1 [suppress_function]
2 name = func
3 parameter = '0 /^S.(&|\\*)/
$
$ ../build/tools/abidiff --suppressions libtest2-1.suppr libtest2-v0.so libtest2-v1.so
Functions changes summary: 0 Removed, 1 Changed (2 filtered out), 0 Added function
Variables changes summary: 0 Removed, 0 Changed, 0 Added variable
1 function with some indirect sub-type change:
[C]'function unsigned int func(S3)' has some indirect sub-type changes:
parameter 0 of type 'struct S3' has sub-type changes:
size changed from 32 to 64 bits
1 base class insertion:
struct base_type
1 data member change:
'int S3::m0' offset changed from 0 to 32
$