libabigail/include/abg-suppression.h
Dodji Seketeli 3e0eeb9f98 suppression: Fix has_data_member_inserted_between = {offset_of(), offset_of()}
This should fix bug https://sourceware.org/bugzilla/show_bug.cgi?id=28073

There is at least a case where the evaluation of the suppression
specification rule incarnated by the property
has_data_member_inserted_between doesn't work.  This is in the context
of the following suppression specification:

    [suppress_type]
     name = struct_foo
     has_data_member_inserted_between = {offset_of(dm1), offset_of(dm2)}

The evaluation of the rule incarnated by
has_data_member_inserted_between fails in the context of a type change
where the data member "dm1" is removed from the type struct_foo.  In
that case, the evaluation of the suppression should ALWAYS yield to
the suppression specification NOT suppressing the change.  But in some
cases the change is suppressed nonetheless.

This patch fixes that.

The idea of the patch is that if the class has a removed data member
or if its size shrinks then no type change on that class can be
suppressed.  This is because those two kinds of change are
incompatible ABI (or at least API) changes.  So they should be
reported.

The patch also fixes the evaluation of the boundaries of the insertion
range expressed as an "offset_after" expression.

	* doc/manuals/libabigail-concepts.rst: Update the documentation to
	reflect that has_data_member* properties will never suppress any
	type change if the change carries a data member suppression or a
	type size reduction.
	* include/abg-fwd.h (get_last_data_member)
	(get_next_data_member_offset): Declare new functions.
	* include/abg-suppression.h
	(insertion_range::boundary_value_is_end): Declare new static
	member function.
	(type_supression::insertion_range::eval_boundary): Make this
	static function take an uint64_t rather than ssize_t.
	(type_suppression::insertion_range::integer_boundary::{integer_boundary,
	as_integer, operator int}): Make these member functions and
	operator take or return uint64_t rather than int.
	* src/abg-ir.cc (get_last_data_member)
	(get_next_data_member_offset): Define new functions.
	* src/abg-suppression.cc
	(type_suppression::suppresses_diff): Rework logic to better handle
	"has_data_member_inserted_*" properties in the context of class
	diffs.  If the diff object carries data member removal or size
	reduction, the diff object is not suppressed by the current type
	suppression.  Also, the property "has_data_member_inserted_at =
	end", is now represented by an insertion range where the beginning
	and the end of the range are both the max possible value of
	insertion range boundaries; the code is made to recognize that.
	(type_suppression::insertion_range::eval_boundary): Make this
	static function take an uint64_t rather than ssize_t.  If the
	boundary is expressed as a "offset_after" expression, make sure
	the offset of the next data member is considered if it's present.
	(type_suppression::insertion_range::integer_boundary::{integer_boundary,
	as_integer, operator int}): Make these take or return uint64_t
	rather than int.
	(type_suppression::insertion_range::boundary_value_is_end): Define
	new member function.
	(type_suppression::insertion_range::integer_boundary::priv::value_):
	Turn the type of this into uint64_t, from int.
	(type_suppression::insertion_range::integer_boundary::priv::priv):
	The parameter of this is now uint64_t, from int.
	* tests/data/test-diff-suppr/PR28073/PR28073-bitfield-removed.c:
	New test source code.
	* tests/data/test-diff-suppr/PR28073/PR28073-bitfield-removed.o:
	New test binary.
	* tests/data/test-diff-suppr/PR28073/PR28073-bitfield-removed.o.abi:
	New test input.
	* tests/data/test-diff-suppr/PR28073/PR28073-output-{1,2}.txt: New
	test reference output.
	* tests/data/test-diff-suppr/PR28073/PR28073.after.o: New test
	binary.
	* tests/data/test-diff-suppr/PR28073/PR28073.after.o.abi: New test
	input.
	* tests/data/test-diff-suppr/PR28073/PR28073.before.o: New test
	binary.
	* tests/data/test-diff-suppr/PR28073/PR28073.before.o.abi: New
	test input.
	* tests/data/test-diff-suppr/PR28073/PR28073.c: New test source
	code.
	* tests/data/test-diff-suppr/PR28073/bitfield.suppr: New test
	input.
	* tests/data/Makefile.am: Add the new test material to source
	distribution.
	* tests/test-diff-suppr.cc: Add the new test input to this test
	harness.

Signed-off-by: Dodji Seketeli <dodji@redhat.com>
2021-12-06 14:39:32 +01:00

829 lines
19 KiB
C++

// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
// -*- Mode: C++ -*-
//
// Copyright (C) 2016-2020 Red Hat, Inc.
//
// Author: Dodji Seketeli
#ifndef __ABG_SUPPRESSION_H__
#define __ABG_SUPPRESSION_H__
#include <unordered_set>
#include "abg-ini.h"
#include "abg-comparison.h"
namespace abigail
{
/// @brief an engine to suppress the parts of the result of comparing
/// two sets of ABI artifacts.
///
/// The user specifies the kind of changes between ABI artefact she
/// wants to see suppressed. That suppression specification is done
/// in an INI format.
///
/// That INI file is parsed and represented internally using the types
/// that are defined in this namespace.
namespace suppr
{
using namespace abigail::comparison;
using std::unordered_set;
/// Base type of the suppression specifications types.
///
/// This abstracts a suppression specification. It's a way to specify
/// how to drop reports about a particular diff node on the floor, if
/// it matches the supppression specification.
class suppression_base
{
public:
class priv; // declare publicly to allow subclasses to reuse the priv
private:
// Forbid default constructor
suppression_base();
public:
std::unique_ptr<priv> priv_;
suppression_base(const string& label);
suppression_base(const string& label,
const string& file_name_regex_str,
const string& file_name_not_regex_str);
bool
get_drops_artifact_from_ir() const;
void
set_drops_artifact_from_ir(bool);
bool
get_is_artificial() const;
void
set_is_artificial(bool);
const string
get_label() const;
void
set_label(const string&);
void
set_file_name_regex_str(const string& regexp);
const string&
get_file_name_regex_str() const;
void
set_file_name_not_regex_str(const string& regexp);
const string&
get_file_name_not_regex_str() const;
bool
has_file_name_related_property() const;
void
set_soname_regex_str(const string& regexp);
const string&
get_soname_regex_str() const;
void
set_soname_not_regex_str(const string& regexp);
const string&
get_soname_not_regex_str() const;
bool
has_soname_related_property() const;
virtual bool
suppresses_diff(const diff*) const = 0;
virtual ~suppression_base();
friend bool
suppression_matches_soname(const string& soname,
const suppression_base& suppr);
friend bool
suppression_matches_soname_or_filename(const string& soname,
const string& filename,
const suppression_base& suppr);
}; // end class suppression_base
void
read_suppressions(std::istream& input,
suppressions_type& suppressions);
void
read_suppressions(const string& file_path,
suppressions_type& suppressions);
class type_suppression;
/// Convenience typedef for a shared pointer to type_suppression.
typedef shared_ptr<type_suppression> type_suppression_sptr;
/// Convenience typedef for vector of @ref type_suppression_sptr.
typedef vector<type_suppression_sptr> type_suppressions_type;
/// Abstraction of a type suppression specification.
///
/// Specifies under which condition reports about a type diff node
/// should be dropped on the floor.
class type_suppression : public suppression_base
{
class priv;
// Forbid this;
type_suppression();
public:
std::unique_ptr<priv> priv_;
/// The kind of the type the current type suppression is supposed to
/// be about.
enum type_kind
{
UNKNOWN_TYPE_KIND,
CLASS_TYPE_KIND,
STRUCT_TYPE_KIND,
UNION_TYPE_KIND,
ENUM_TYPE_KIND,
ARRAY_TYPE_KIND,
TYPEDEF_TYPE_KIND,
BUILTIN_TYPE_KIND
}; // end enum type_kind
/// The different ways through which the type diff has been reached.
enum reach_kind
{
/// The type diff has been reached (from a function or variable
/// change) directly.
DIRECT_REACH_KIND = 0,
/// The type diff has been reached (from a function or variable
/// change) through a pointer.
POINTER_REACH_KIND,
/// The type diff has been reached (from a function or variable
/// change) through a reference; you know, like a c++ reference..
REFERENCE_REACH_KIND,
/// The type diff has been reached (from a function or variable
/// change) through either a reference or a pointer.
REFERENCE_OR_POINTER_REACH_KIND
}; // end enum reach_kind
class insertion_range;
/// A convenience typedef for a shared pointer to @ref
/// insertion_range.
typedef shared_ptr<insertion_range> insertion_range_sptr;
/// A convenience typedef for a vector of @ref insertion_range_sptr.
typedef vector<insertion_range_sptr> insertion_ranges;
type_suppression(const string& label,
const string& type_name_regexp,
const string& type_name);
virtual ~type_suppression();
void
set_type_name_regex_str(const string& name_regex_str);
const string&
get_type_name_regex_str() const;
void
set_type_name_not_regex_str(const string& name_regex_str);
const string&
get_type_name_not_regex_str() const;
void
set_type_name(const string& name);
const string&
get_type_name() const;
bool
get_consider_type_kind() const;
void
set_consider_type_kind(bool f);
void
set_type_kind(type_kind k);
type_kind
get_type_kind() const;
bool
get_consider_reach_kind() const;
void
set_consider_reach_kind(bool f);
reach_kind
get_reach_kind() const;
void
set_reach_kind(reach_kind k);
void
set_data_member_insertion_ranges(const insertion_ranges& r);
const insertion_ranges&
get_data_member_insertion_ranges() const;
insertion_ranges&
get_data_member_insertion_ranges();
const unordered_set<string>&
get_source_locations_to_keep() const;
unordered_set<string>&
get_source_locations_to_keep();
void
set_source_locations_to_keep(const unordered_set<string>&);
const string&
get_source_location_to_keep_regex_str() const;
void
set_source_location_to_keep_regex_str(const string&);
const vector<string>&
get_changed_enumerator_names() const;
void
set_changed_enumerator_names(const vector<string>&);
virtual bool
suppresses_diff(const diff* diff) const;
bool
suppresses_type(const type_base_sptr& type,
const diff_context_sptr& ctxt) const;
bool
suppresses_type(const type_base_sptr& type) const;
bool
suppresses_type(const type_base_sptr& type,
const scope_decl* type_scope) const;
}; // end type_suppression
type_suppression_sptr
is_type_suppression(const suppression_sptr);
/// The abstraction of a range of offsets in which a member of a type
/// might get inserted.
class type_suppression::insertion_range
{
struct priv;
std::unique_ptr<priv> priv_;
public:
class boundary;
class integer_boundary;
class fn_call_expr_boundary;
/// Convenience typedef for a shared_ptr to @ref boundary
typedef shared_ptr<boundary> boundary_sptr;
/// Convenience typedef for a shared_ptr to a @ref integer_boundary
typedef shared_ptr<integer_boundary> integer_boundary_sptr;
/// Convenience typedef for a shared_ptr to a @ref
/// fn_call_expr_boundary
typedef shared_ptr<fn_call_expr_boundary> fn_call_expr_boundary_sptr;
insertion_range();
insertion_range(boundary_sptr begin, boundary_sptr end);
boundary_sptr
begin() const;
boundary_sptr
end() const;
static insertion_range::integer_boundary_sptr
create_integer_boundary(int value);
static insertion_range::fn_call_expr_boundary_sptr
create_fn_call_expr_boundary(ini::function_call_expr_sptr);
static insertion_range::fn_call_expr_boundary_sptr
create_fn_call_expr_boundary(const string&);
static bool
eval_boundary(boundary_sptr boundary,
class_decl_sptr context,
uint64_t& value);
static bool
boundary_value_is_end(uint64_t value);
}; // end class insertion_range
type_suppression::insertion_range::integer_boundary_sptr
is_integer_boundary(type_suppression::insertion_range::boundary_sptr);
type_suppression::insertion_range::fn_call_expr_boundary_sptr
is_fn_call_expr_boundary(type_suppression::insertion_range::boundary_sptr);
/// The abstraction of the boundary of an @ref insertion_range, in the
/// context of a @ref type_suppression
class type_suppression::insertion_range::boundary
{
struct priv;
std::unique_ptr<priv> priv_;
public:
boundary();
virtual ~boundary();
};// end class type_suppression::insertion_range::boundary
/// An @ref insertion_range boundary that is expressed as an integer
/// value. That integer value is usually a bit offset.
class type_suppression::insertion_range::integer_boundary
: public type_suppression::insertion_range::boundary
{
struct priv;
std::unique_ptr<priv> priv_;
integer_boundary();
public:
integer_boundary(uint64_t value);
uint64_t as_integer() const;
operator uint64_t () const;
~integer_boundary();
}; //end class type_suppression::insertion_range::integer_boundary
/// An @ref insertion_range boundary that is expressed as function
/// call expression. The (integer) value of that expression is
/// usually a bit offset.
class type_suppression::insertion_range::fn_call_expr_boundary
: public type_suppression::insertion_range::boundary
{
struct priv;
std::unique_ptr<priv> priv_;
fn_call_expr_boundary();
public:
fn_call_expr_boundary(ini::function_call_expr_sptr expr);
ini::function_call_expr_sptr as_function_call_expr() const;
operator ini::function_call_expr_sptr () const;
~fn_call_expr_boundary();
}; //end class type_suppression::insertion_range::fn_call_expr_boundary
class function_suppression;
/// Convenience typedef for a shared pointer to function_suppression.
typedef shared_ptr<function_suppression> function_suppression_sptr;
/// Convenience typedef for a vector of @ref function_suppression_sptr.
typedef vector<function_suppression_sptr> function_suppressions_type;
/// Abstraction of a function suppression specification.
///
/// Specifies under which condition reports about a @ref
/// function_decl_diff diff node should be dropped on the floor for
/// the purpose of reporting.
class function_suppression : public suppression_base
{
struct priv;
public:
std::unique_ptr<priv> priv_;
class parameter_spec;
/// Convenience typedef for shared_ptr of @ref parameter_spec.
typedef shared_ptr<parameter_spec> parameter_spec_sptr;
/// Convenience typedef for vector of @ref parameter_spec_sptr.
typedef vector<parameter_spec_sptr> parameter_specs_type;
/// The kind of change the current function suppression should apply
/// to.
enum change_kind
{
UNDEFINED_CHANGE_KIND,
/// A change in a sub-type of the function.
FUNCTION_SUBTYPE_CHANGE_KIND = 1,
/// The function was added to the second subject of the diff.
ADDED_FUNCTION_CHANGE_KIND = 1 << 1,
/// The function was deleted from the second subject of the diff.
DELETED_FUNCTION_CHANGE_KIND = 1 << 2,
/// This represents all the changes possibly described by this
/// enum. It's a logical 'OR' of all the change enumerators
/// above.
ALL_CHANGE_KIND = (FUNCTION_SUBTYPE_CHANGE_KIND
| ADDED_FUNCTION_CHANGE_KIND
| DELETED_FUNCTION_CHANGE_KIND)
};
function_suppression();
function_suppression(const string& label,
const string& name,
const string& name_regex,
const string& return_type_name,
const string& return_type_regex,
parameter_specs_type& parm_specs,
const string& symbol_name,
const string& symbol_name_regex,
const string& symbol_version,
const string& symbol_version_regex_str);
virtual ~function_suppression();
static change_kind
parse_change_kind(const string&);
change_kind
get_change_kind() const;
void
set_change_kind(change_kind k);
const string&
get_name() const;
void
set_name(const string&);
const string&
get_name_regex_str() const;
void
set_name_regex_str(const string&);
const string&
get_name_not_regex_str() const;
void
set_name_not_regex_str(const string&);
const string&
get_return_type_name() const;
void
set_return_type_name(const string&);
const string&
get_return_type_regex_str() const;
void
set_return_type_regex_str(const string& r);
const parameter_specs_type&
get_parameter_specs() const;
void
set_parameter_specs(parameter_specs_type&);
void
append_parameter_specs(const parameter_spec_sptr);
const string&
get_symbol_name() const;
void
set_symbol_name(const string& n);
const string&
get_symbol_name_regex_str() const;
void
set_symbol_name_regex_str(const string&);
const string&
get_symbol_name_not_regex_str() const;
void
set_symbol_name_not_regex_str(const string&);
const string&
get_symbol_version() const;
void
set_symbol_version(const string&);
const string&
get_symbol_version_regex_str() const;
void
set_symbol_version_regex_str(const string&);
bool
get_allow_other_aliases() const;
void
set_allow_other_aliases(bool f);
virtual bool
suppresses_diff(const diff* diff) const;
bool
suppresses_function(const function_decl* fn,
change_kind k,
const diff_context_sptr ctxt) const;
bool
suppresses_function(const function_decl_sptr fn,
change_kind k,
const diff_context_sptr ctxt) const;
bool
suppresses_function_symbol(const elf_symbol* sym,
change_kind k,
const diff_context_sptr ctxt);
bool
suppresses_function_symbol(const elf_symbol_sptr sym,
change_kind k,
const diff_context_sptr ctxt);
}; // end class function_suppression.
function_suppression_sptr
is_function_suppression(const suppression_sptr);
function_suppression::change_kind
operator&(function_suppression::change_kind l,
function_suppression::change_kind r);
function_suppression::change_kind
operator|(function_suppression::change_kind l,
function_suppression::change_kind r);
/// Abstraction of the specification of a function parameter in a
/// function suppression specification.
class function_suppression::parameter_spec
{
friend class function_suppression;
class priv;
std::unique_ptr<priv> priv_;
// Forbid this.
parameter_spec();
public:
parameter_spec(size_t index,
const string& type_name,
const string& type_name_regex);
size_t
get_index() const;
void
set_index(size_t);
const string&
get_parameter_type_name() const;
void
set_parameter_type_name(const string&);
const string&
get_parameter_type_name_regex_str() const;
void
set_parameter_type_name_regex_str(const string&);
};// end class function_suppression::parameter_spec
class variable_suppression;
/// A convenience typedef for a shared pointer to @ref
/// variable_suppression.
typedef shared_ptr<variable_suppression> variable_suppression_sptr;
/// A convenience typedef for a vector of @ref
/// variable_suppression_sptr.
typedef vector<variable_suppression_sptr> variable_suppressions_type;
/// The abstraction of a variable suppression specification.
///
/// It specifies under which condition reports about a @ref var_diff
/// diff node should be dropped on the floor for the purpose of
/// reporting.
class variable_suppression : public suppression_base
{
public:
/// The kind of change the current variable suppression should apply
/// to.
enum change_kind
{
UNDEFINED_CHANGE_KIND,
/// A change in a sub-type of the variable.
VARIABLE_SUBTYPE_CHANGE_KIND = 1,
/// The variable was added to the second second subject of the
/// diff.
ADDED_VARIABLE_CHANGE_KIND = 1 << 1,
/// The variable was deleted from the second subject of the diff.
DELETED_VARIABLE_CHANGE_KIND = 1 << 2,
/// This represents all the changes possibly described by this
/// enum. It's a logical 'OR' of all the change enumerators
/// above.
ALL_CHANGE_KIND = (VARIABLE_SUBTYPE_CHANGE_KIND
| ADDED_VARIABLE_CHANGE_KIND
| DELETED_VARIABLE_CHANGE_KIND)
};
private:
struct priv;
public:
std::unique_ptr<priv> priv_;
variable_suppression(const string& label = "",
const string& name = "",
const string& name_regex_str = "",
const string& symbol_name = "",
const string& symbol_name_regex_str = "",
const string& symbol_version = "",
const string& symbol_version_regex_str = "",
const string& type_name = "",
const string& type_name_regex_str = "");
virtual ~variable_suppression();
static change_kind
parse_change_kind(const string&);
change_kind
get_change_kind() const;
void
set_change_kind(change_kind k);
const string&
get_name() const;
void
set_name(const string&);
const string&
get_name_regex_str() const;
void
set_name_regex_str(const string&);
const string&
get_name_not_regex_str() const;
void
set_name_not_regex_str(const string&);
const string&
get_symbol_name() const;
void
set_symbol_name(const string&);
const string&
get_symbol_name_regex_str() const;
void
set_symbol_name_regex_str(const string&);
const string&
get_symbol_name_not_regex_str() const;
void
set_symbol_name_not_regex_str(const string&);
const string&
get_symbol_version() const;
void
set_symbol_version(const string&);
const string&
get_symbol_version_regex_str() const;
void
set_symbol_version_regex_str(const string&);
const string&
get_type_name() const;
void
set_type_name(const string&);
const string&
get_type_name_regex_str() const;
void
set_type_name_regex_str(const string&);
bool
suppresses_diff(const diff* d) const;
bool
suppresses_variable(const var_decl* var,
change_kind k,
const diff_context_sptr cxt) const;
bool
suppresses_variable(const var_decl_sptr var,
change_kind k,
const diff_context_sptr cxt) const;
bool
suppresses_variable_symbol(const elf_symbol* sym,
change_kind k,
const diff_context_sptr cxt) const;
bool
suppresses_variable_symbol(const elf_symbol_sptr fn,
change_kind k,
const diff_context_sptr cxt) const;
}; // end class variable_suppression
variable_suppression_sptr
is_variable_suppression(const suppression_sptr);
variable_suppression::change_kind
operator&(variable_suppression::change_kind l,
variable_suppression::change_kind r);
variable_suppression::change_kind
operator|(variable_suppression::change_kind l,
variable_suppression::change_kind r);
class file_suppression;
/// A convenience typedef for a shared_ptr to @ref file_suppression
typedef shared_ptr<file_suppression> file_suppression_sptr;
/// Abstraction of a suppression specification to avoid loading a
/// file.
///
/// This can be used by a tool that loads (binary) files, to know
/// which file it has to avoid loading.
class file_suppression: public suppression_base
{
std::unique_ptr<priv> priv_;
// Forbid this
file_suppression();
public:
file_suppression(const string& label,
const string& file_name_regex,
const string& file_name_not_regex);
virtual bool
suppresses_diff(const diff* diff) const;
bool
suppresses_file(const string& file_path);
virtual ~file_suppression();
}; // end file_suppression
file_suppression_sptr
is_file_suppression(const suppression_sptr);
file_suppression_sptr
file_is_suppressed(const string& file_path,
const suppressions_type& suppressions);
bool
suppression_matches_soname(const string& soname,
const suppression_base& suppr);
bool
suppression_matches_soname_or_filename(const string& soname,
const string& filename,
const suppression_base& suppr);
const char*
get_private_types_suppr_spec_label();
bool
is_private_type_suppr_spec(const type_suppression&);
bool
is_private_type_suppr_spec(const suppression_sptr& s);
} // end namespace suppr
} // end namespace abigail
#endif //__ABG_SUPPRESSION_H__