mirror of
git://sourceware.org/git/libabigail.git
synced 2024-12-15 14:34:38 +00:00
acfa51d52d
Categorizing a diff graph with a thousands of function diff nodes takes a lot of time. At that point, categorizing each node has harmful/harmless is showing up in the profile, particularly because has_var_type_cv_qual_change and to a lesser extend class_diff_has_harmless_odr_violation_change perform some structural comparison still, oops. This patch avoids doing that. On my machine, the categorizing of each node goes from around 130ms to 92 ms. * src/abg-comp-filter.cc (class_diff_has_harmless_odr_violation_change) (has_var_type_cv_qual_change): Avoid doing structural comparison here. Signed-off-by: Dodji Seketeli <dodji@redhat.com>
1934 lines
58 KiB
C++
1934 lines
58 KiB
C++
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
// -*- Mode: C++ -*-
|
|
//
|
|
// Copyright (C) 2013-2023 Red Hat, Inc.
|
|
//
|
|
// Author: Dodji Seketeli
|
|
|
|
/// @file
|
|
///
|
|
/// This file contains definitions of diff objects filtering
|
|
/// facilities.
|
|
|
|
#include "abg-internal.h"
|
|
#include <memory>
|
|
// <headers defining libabigail's API go under here>
|
|
ABG_BEGIN_EXPORT_DECLARATIONS
|
|
|
|
#include "abg-comp-filter.h"
|
|
#include "abg-tools-utils.h"
|
|
|
|
ABG_END_EXPORT_DECLARATIONS
|
|
// </headers defining libabigail's API>
|
|
|
|
namespace abigail
|
|
{
|
|
namespace comparison
|
|
{
|
|
namespace filtering
|
|
{
|
|
|
|
using std::dynamic_pointer_cast;
|
|
|
|
/// Walk the diff sub-trees of a a @ref corpus_diff and apply a filter
|
|
/// to the nodes visted. The filter categorizes each node, assigning
|
|
/// it into one or several categories.
|
|
///
|
|
/// @param filter the filter to apply to the diff nodes
|
|
///
|
|
/// @param d the corpus diff to apply the filter to.
|
|
void
|
|
apply_filter(filter_base& filter, corpus_diff_sptr d)
|
|
{
|
|
bool s = d->context()->visiting_a_node_twice_is_forbidden();
|
|
d->context()->forbid_visiting_a_node_twice(true);
|
|
d->traverse(filter);
|
|
d->context()->forbid_visiting_a_node_twice(s);
|
|
}
|
|
|
|
/// Walk a diff sub-tree and apply a filter to the nodes visted. The
|
|
/// filter categorizes each node, assigning it into one or several
|
|
/// categories.
|
|
///
|
|
/// Note that this function makes sure to avoid visiting a node (or
|
|
/// any other node equivalent to it) more than once. This helps avoid
|
|
/// infinite loops for diff trees that involve type changes that
|
|
/// reference themselves.
|
|
///
|
|
/// @param filter the filter to apply to the nodes of the sub-tree.
|
|
///
|
|
/// @param d the diff sub-tree to walk and apply the filter to.
|
|
void
|
|
apply_filter(filter_base& filter, diff_sptr d)
|
|
{
|
|
bool s = d->context()->visiting_a_node_twice_is_forbidden();
|
|
d->context()->forbid_visiting_a_node_twice(true);
|
|
d->context()->forget_visited_diffs();
|
|
d->traverse(filter);
|
|
d->context()->forbid_visiting_a_node_twice(s);
|
|
}
|
|
|
|
/// Walk a diff sub-tree and apply a filter to the nodes visted. The
|
|
/// filter categorizes each node, assigning it into one or several
|
|
/// categories.
|
|
///
|
|
/// Note that this function makes sure to avoid visiting a node (or
|
|
/// any other node equivalent to it) more than once. This helps avoid
|
|
/// infinite loops for diff trees that involve type changes that
|
|
/// reference themselves.
|
|
///
|
|
/// @param filter the filter to apply to the nodes of the sub-tree.
|
|
///
|
|
/// @param d the diff sub-tree to walk and apply the filter to.
|
|
void
|
|
apply_filter(filter_base_sptr filter, diff_sptr d)
|
|
{apply_filter(*filter, d);}
|
|
|
|
/// Test if there is a class that is declaration-only among the two
|
|
/// classes in parameter.
|
|
///
|
|
/// @param class1 the first class to consider.
|
|
///
|
|
/// @param class2 the second class to consider.
|
|
///
|
|
/// @return true if either classes are declaration-only, false
|
|
/// otherwise.
|
|
static bool
|
|
there_is_a_decl_only_class(const class_decl_sptr& class1,
|
|
const class_decl_sptr& class2)
|
|
{
|
|
if ((class1 && class1->get_is_declaration_only())
|
|
|| (class2 && class2->get_is_declaration_only()))
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
/// Test if there is a enum that is declaration-only among the two
|
|
/// enums in parameter.
|
|
///
|
|
/// @param enum1 the first enum to consider.
|
|
///
|
|
/// @param enum2 the second enum to consider.
|
|
///
|
|
/// @return true if either enums are declaration-only, false
|
|
/// otherwise.
|
|
static bool
|
|
there_is_a_decl_only_enum(const enum_type_decl_sptr& enum1,
|
|
const enum_type_decl_sptr& enum2)
|
|
{
|
|
if ((enum1 && enum1->get_is_declaration_only())
|
|
|| (enum2 && enum2->get_is_declaration_only()))
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
/// Test if the diff involves a declaration-only class.
|
|
///
|
|
/// @param diff the class diff to consider.
|
|
///
|
|
/// @return true iff the diff involves a declaration-only class.
|
|
static bool
|
|
diff_involves_decl_only_class(const class_diff* diff)
|
|
{
|
|
if (diff && there_is_a_decl_only_class(diff->first_class_decl(),
|
|
diff->second_class_decl()))
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
/// Tests if the size of a given type changed.
|
|
///
|
|
/// @param f the first version of the type to consider.
|
|
///
|
|
/// @param s the second version of the type to consider.
|
|
///
|
|
/// @return true if the type size changed, false otherwise.
|
|
static bool
|
|
type_size_changed(const type_base_sptr f, const type_base_sptr s)
|
|
{
|
|
if (!f || !s
|
|
|| f->get_size_in_bits() == 0
|
|
|| s->get_size_in_bits() == 0
|
|
|| there_is_a_decl_only_class(is_compatible_with_class_type(f),
|
|
is_compatible_with_class_type(s))
|
|
|| there_is_a_decl_only_enum(is_compatible_with_enum_type(f),
|
|
is_compatible_with_enum_type(s)))
|
|
return false;
|
|
|
|
return f->get_size_in_bits() != s->get_size_in_bits();
|
|
}
|
|
|
|
/// Tests if the size of a given type changed.
|
|
///
|
|
/// @param f the declaration of the first version of the type to
|
|
/// consider.
|
|
///
|
|
/// @param s the declaration of the second version of the type to
|
|
/// consider.
|
|
///
|
|
/// @return true if the type size changed, false otherwise.
|
|
static bool
|
|
type_size_changed(const decl_base_sptr f, const decl_base_sptr s)
|
|
{return type_size_changed(is_type(f), is_type(s));}
|
|
|
|
/// Test if a given type diff node carries a type size change.
|
|
///
|
|
/// @param diff the diff tree node to test.
|
|
///
|
|
/// @return true if @p diff carries a type size change.
|
|
static bool
|
|
has_type_size_change(const diff* diff)
|
|
{
|
|
if (!diff)
|
|
return false;
|
|
|
|
if (const fn_parm_diff* fn_parm_d = is_fn_parm_diff(diff))
|
|
diff = fn_parm_d->type_diff().get();
|
|
|
|
type_base_sptr f = is_type(diff->first_subject()),
|
|
s = is_type(diff->second_subject());
|
|
|
|
if (!f || !s)
|
|
return false;
|
|
|
|
return type_size_changed(f, s);
|
|
}
|
|
/// Tests if the access specifiers for a member declaration changed.
|
|
///
|
|
/// @param f the declaration for the first version of the member
|
|
/// declaration to consider.
|
|
///
|
|
/// @param s the declaration for the second version of the member
|
|
/// delcaration to consider.
|
|
///
|
|
/// @return true iff the access specifier changed.
|
|
static bool
|
|
access_changed(const decl_base_sptr& f, const decl_base_sptr& s)
|
|
{
|
|
if (!is_member_decl(f)
|
|
|| !is_member_decl(s))
|
|
return false;
|
|
|
|
access_specifier fa = get_member_access_specifier(f),
|
|
sa = get_member_access_specifier(s);
|
|
|
|
if (sa != fa)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
/// Test if there was a function or variable CRC change.
|
|
///
|
|
/// @param f the first function or variable to consider.
|
|
///
|
|
/// @param s the second function or variable to consider.
|
|
///
|
|
/// @return true if the test is positive, false otherwise.
|
|
template <typename function_or_var_decl_sptr>
|
|
static bool
|
|
crc_changed(const function_or_var_decl_sptr& f,
|
|
const function_or_var_decl_sptr& s)
|
|
{
|
|
const auto& symbol_f = f->get_symbol();
|
|
const auto& symbol_s = s->get_symbol();
|
|
if (!symbol_f || !symbol_s)
|
|
return false;
|
|
return symbol_f->get_crc() != symbol_s->get_crc();
|
|
}
|
|
|
|
/// Test if the current diff tree node carries a CRC change in either a
|
|
/// function or a variable.
|
|
///
|
|
/// @param diff the diff tree node to consider.
|
|
///
|
|
/// @return true if the test is positive, false otherwise.
|
|
static bool
|
|
crc_changed(const diff* diff)
|
|
{
|
|
if (const function_decl_diff* d =
|
|
dynamic_cast<const function_decl_diff*>(diff))
|
|
return crc_changed(d->first_function_decl(), d->second_function_decl());
|
|
if (const var_diff* d = dynamic_cast<const var_diff*>(diff))
|
|
return crc_changed(d->first_var(), d->second_var());
|
|
return false;
|
|
}
|
|
|
|
/// Test if there was a function or variable namespace change.
|
|
///
|
|
/// @param f the first function or variable to consider.
|
|
///
|
|
/// @param s the second function or variable to consider.
|
|
///
|
|
/// @return true if the test is positive, false otherwise.
|
|
template <typename function_or_var_decl_sptr>
|
|
static bool
|
|
namespace_changed(const function_or_var_decl_sptr& f,
|
|
const function_or_var_decl_sptr& s)
|
|
{
|
|
const auto& symbol_f = f->get_symbol();
|
|
const auto& symbol_s = s->get_symbol();
|
|
if (!symbol_f || !symbol_s)
|
|
return false;
|
|
return symbol_f->get_namespace() != symbol_s->get_namespace();
|
|
}
|
|
|
|
/// Test if the current diff tree node carries a namespace change in
|
|
/// either a function or a variable.
|
|
///
|
|
/// @param diff the diff tree node to consider.
|
|
///
|
|
/// @return true if the test is positive, false otherwise.
|
|
static bool
|
|
namespace_changed(const diff* diff)
|
|
{
|
|
if (const function_decl_diff* d =
|
|
dynamic_cast<const function_decl_diff*>(diff))
|
|
return namespace_changed(d->first_function_decl(),
|
|
d->second_function_decl());
|
|
if (const var_diff* d = dynamic_cast<const var_diff*>(diff))
|
|
return namespace_changed(d->first_var(), d->second_var());
|
|
return false;
|
|
}
|
|
|
|
/// Test if there was a function name change, but there there was no
|
|
/// change in name of the underlying symbol. IOW, if the name of a
|
|
/// function changed, but the symbol of the new function is equal to
|
|
/// the symbol of the old one, or is equal to an alians of the symbol
|
|
/// of the old function.
|
|
///
|
|
/// @param f the first function to consider.
|
|
///
|
|
/// @param s the second function to consider.
|
|
///
|
|
/// @return true if the test is positive, false otherwise.
|
|
static bool
|
|
function_name_changed_but_not_symbol(const function_decl_sptr& f,
|
|
const function_decl_sptr& s)
|
|
{
|
|
if (!f || !s)
|
|
return false;
|
|
string fn = f->get_qualified_name(),
|
|
sn = s->get_qualified_name();
|
|
|
|
if (fn != sn)
|
|
{
|
|
elf_symbol_sptr fs = f->get_symbol(), ss = s->get_symbol();
|
|
if (fs == ss)
|
|
return true;
|
|
if (!!fs != !!ss)
|
|
return false;
|
|
for (elf_symbol_sptr s = fs->get_next_alias();
|
|
s && !s->is_main_symbol();
|
|
s = s->get_next_alias())
|
|
if (*s == *ss)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/// Test if the current diff tree node carries a function name change,
|
|
/// in which there there was no change in the name of the underlying
|
|
/// symbol. IOW, if the name of a function changed, but the symbol of
|
|
/// the new function is equal to the symbol of the old one, or is
|
|
/// equal to an alians of the symbol of the old function.
|
|
///
|
|
/// @param diff the diff tree node to consider.
|
|
///
|
|
/// @return true if the test is positive, false otherwise.
|
|
static bool
|
|
function_name_changed_but_not_symbol(const diff* diff)
|
|
{
|
|
if (const function_decl_diff* d =
|
|
dynamic_cast<const function_decl_diff*>(diff))
|
|
return function_name_changed_but_not_symbol(d->first_function_decl(),
|
|
d->second_function_decl());
|
|
return false;
|
|
}
|
|
|
|
/// Tests if the offset of a given data member changed.
|
|
///
|
|
/// @param f the declaration for the first version of the data member to
|
|
/// consider.
|
|
///
|
|
/// @param s the declaration for the second version of the data member
|
|
/// to consider.
|
|
///
|
|
/// @return true iff the offset of the data member changed.
|
|
static bool
|
|
data_member_offset_changed(decl_base_sptr f, decl_base_sptr s)
|
|
{
|
|
if (!is_member_decl(f)
|
|
|| !is_member_decl(s))
|
|
return false;
|
|
|
|
var_decl_sptr v0 = dynamic_pointer_cast<var_decl>(f),
|
|
v1 = dynamic_pointer_cast<var_decl>(s);
|
|
if (!v0 || !v1)
|
|
return false;
|
|
|
|
if (get_data_member_offset(v0) != get_data_member_offset(v1))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
/// Test if the size of a non-static data member changed accross two
|
|
/// versions.
|
|
///
|
|
/// @param f the first version of the non-static data member.
|
|
///
|
|
/// @param s the second version of the non-static data member.
|
|
static bool
|
|
non_static_data_member_type_size_changed(const decl_base_sptr& f,
|
|
const decl_base_sptr& s)
|
|
{
|
|
if (!is_member_decl(f)
|
|
|| !is_member_decl(s))
|
|
return false;
|
|
|
|
var_decl_sptr fv = dynamic_pointer_cast<var_decl>(f),
|
|
sv = dynamic_pointer_cast<var_decl>(s);
|
|
if (!fv
|
|
|| !sv
|
|
|| get_member_is_static(fv)
|
|
|| get_member_is_static(sv))
|
|
return false;
|
|
|
|
return type_size_changed(fv->get_type(), sv->get_type());
|
|
}
|
|
|
|
/// Test if the size of a static data member changed accross two
|
|
/// versions.
|
|
///
|
|
/// @param f the first version of the static data member.
|
|
///
|
|
/// @param s the second version of the static data member.
|
|
static bool
|
|
static_data_member_type_size_changed(const decl_base_sptr& f,
|
|
const decl_base_sptr& s)
|
|
{
|
|
if (!is_member_decl(f)
|
|
|| !is_member_decl(s))
|
|
return false;
|
|
|
|
var_decl_sptr fv = dynamic_pointer_cast<var_decl>(f),
|
|
sv = dynamic_pointer_cast<var_decl>(s);
|
|
if (!fv
|
|
|| !sv
|
|
|| !get_member_is_static(fv)
|
|
|| !get_member_is_static(sv))
|
|
return false;
|
|
|
|
return type_size_changed(fv->get_type(), sv->get_type());
|
|
}
|
|
|
|
/// Test if two types are different but compatible.
|
|
///
|
|
/// @param d1 the declaration of the first type to consider.
|
|
///
|
|
/// @param d2 the declaration of the second type to consider.
|
|
///
|
|
/// @return true if d1 and d2 are different but compatible.
|
|
static bool
|
|
is_compatible_change(const decl_base_sptr& d1, const decl_base_sptr& d2)
|
|
{
|
|
if ((d1 && d2)
|
|
&& (d1 != d2)
|
|
&& types_are_compatible(d1, d2))
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
/// Test if two decls have different names.
|
|
///
|
|
/// @param d1 the first declaration to consider.
|
|
///
|
|
/// @param d2 the second declaration to consider.
|
|
///
|
|
/// @return true if d1 and d2 have different names.
|
|
static bool
|
|
decl_name_changed(const type_or_decl_base* a1, const type_or_decl_base *a2)
|
|
{
|
|
string d1_name, d2_name;
|
|
|
|
const decl_base *d1 = dynamic_cast<const decl_base*>(a1);
|
|
if (d1 == 0)
|
|
return false;
|
|
|
|
const decl_base *d2 = dynamic_cast<const decl_base*>(a2);
|
|
if (d2 == 0)
|
|
return false;
|
|
|
|
if (d1)
|
|
d1_name = d1->get_qualified_name();
|
|
if (d2)
|
|
d2_name = d2->get_qualified_name();
|
|
|
|
return d1_name != d2_name;
|
|
}
|
|
|
|
/// Test if two decls have different names.
|
|
///
|
|
/// @param d1 the first declaration to consider.
|
|
///
|
|
/// @param d2 the second declaration to consider.
|
|
///
|
|
/// @return true if d1 and d2 have different names.
|
|
static bool
|
|
decl_name_changed(const type_or_decl_base_sptr& d1,
|
|
const type_or_decl_base_sptr& d2)
|
|
{return decl_name_changed(d1.get(), d2.get());}
|
|
|
|
/// Test if a diff nodes carries a changes in which two decls have
|
|
/// different names.
|
|
///
|
|
/// @param d the diff node to consider.
|
|
///
|
|
/// @return true iff d carries a changes in which two decls have
|
|
/// different names.
|
|
static bool
|
|
decl_name_changed(const diff *d)
|
|
{return decl_name_changed(d->first_subject(), d->second_subject());}
|
|
|
|
/// Test if two decls represents a harmless name change.
|
|
///
|
|
/// For now, a harmless name change is considered only for a typedef,
|
|
/// enum or a data member.
|
|
///
|
|
/// @param f the first decl to consider in the comparison.
|
|
///
|
|
/// @param s the second decl to consider in the comparison.
|
|
///
|
|
/// @return true iff decl @p s represents a harmless change over @p f.
|
|
bool
|
|
has_harmless_name_change(const decl_base_sptr& f, const decl_base_sptr& s)
|
|
{
|
|
// So, a harmless name change is either ...
|
|
return (decl_name_changed(f, s)
|
|
&& (// ... an anonymous decl name changed into another
|
|
// anonymous decl name ...
|
|
(f->get_is_anonymous() && s->get_is_anonymous())
|
|
||
|
|
// ... an anonymous decl name changed harmlessly into
|
|
// another anonymous decl name ...
|
|
((f->get_is_anonymous_or_has_anonymous_parent()
|
|
&& s->get_is_anonymous_or_has_anonymous_parent())
|
|
&& tools_utils::decl_names_equal(f->get_qualified_name(),
|
|
s->get_qualified_name()))
|
|
// ... a typedef name change, without having the
|
|
// underlying type changed ...
|
|
|| (is_typedef(f)
|
|
&& is_typedef(s)
|
|
&& (is_typedef(f)->get_underlying_type()
|
|
== is_typedef(s)->get_underlying_type()))
|
|
// .. or a data member name change, without having its
|
|
// type changed ...
|
|
|| (is_data_member(f)
|
|
&& is_data_member(s)
|
|
&& (is_var_decl(f)->get_type()
|
|
== is_var_decl(s)->get_type()))
|
|
// .. an enum name change without having any other part
|
|
// of the enum to change.
|
|
|| (is_enum_type(f)
|
|
&& is_enum_type(s)
|
|
&& !enum_has_non_name_change(*is_enum_type(f),
|
|
*is_enum_type(s),
|
|
0))));
|
|
}
|
|
|
|
/// Test if two decls represents a harmful name change.
|
|
///
|
|
/// A harmful name change is a name change that is not harmless, so
|
|
/// this function uses the function has_harmless_name_change.
|
|
///
|
|
/// @param f the first decl to consider in the comparison.
|
|
///
|
|
/// @param s the second decl to consider in the comparison.
|
|
///
|
|
/// @return true iff decl @p s represents a harmful name change over
|
|
/// @p f.
|
|
bool
|
|
has_harmful_name_change(const decl_base_sptr& f, const decl_base_sptr& s)
|
|
{return decl_name_changed(f, s) && ! has_harmless_name_change(f, s);}
|
|
|
|
/// Test if a diff node represents a harmful name change.
|
|
///
|
|
/// A harmful name change is a name change that is not harmless, so
|
|
/// this function uses the function has_harmless_name_change.
|
|
///
|
|
/// @param f the first decl to consider in the comparison.
|
|
///
|
|
/// @param s the second decl to consider in the comparison.
|
|
///
|
|
/// @return true iff decl @p s represents a harmful name change over
|
|
/// @p f.
|
|
bool
|
|
has_harmful_name_change(const diff* dif)
|
|
{
|
|
decl_base_sptr f = is_decl(dif->first_subject()),
|
|
s = is_decl(dif->second_subject());
|
|
|
|
return has_harmful_name_change(f, s);
|
|
}
|
|
|
|
/// Test if a class_diff node has non-static members added or
|
|
/// removed.
|
|
///
|
|
/// @param diff the diff node to consider.
|
|
///
|
|
/// @return true iff the class_diff node has non-static members added
|
|
/// or removed.
|
|
static bool
|
|
non_static_data_member_added_or_removed(const class_diff* diff)
|
|
{
|
|
if (diff && !diff_involves_decl_only_class(diff))
|
|
{
|
|
for (string_decl_base_sptr_map::const_iterator i =
|
|
diff->inserted_data_members().begin();
|
|
i != diff->inserted_data_members().end();
|
|
++i)
|
|
if (!get_member_is_static(i->second))
|
|
return true;
|
|
|
|
for (string_decl_base_sptr_map::const_iterator i =
|
|
diff->deleted_data_members().begin();
|
|
i != diff->deleted_data_members().end();
|
|
++i)
|
|
if (!get_member_is_static(i->second))
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/// Test if a class_diff node has members added or removed.
|
|
///
|
|
/// @param diff the diff node to consider.
|
|
///
|
|
/// @return true iff the class_diff node has members added or removed.
|
|
static bool
|
|
non_static_data_member_added_or_removed(const diff* diff)
|
|
{
|
|
return non_static_data_member_added_or_removed
|
|
(dynamic_cast<const class_diff*>(diff));
|
|
}
|
|
|
|
/// Test if a @ref class_or_union_diff has a data member replaced by
|
|
/// an anonymous data member in a harmless way. That means, the new
|
|
/// anonymous data member somehow contains the replaced data member
|
|
/// and it doesn't break the layout of the containing class.
|
|
///
|
|
/// @param diff the diff node to consider.
|
|
///
|
|
/// @return true iff the @ref class_or_union_diff has a data member
|
|
/// harmlessly replaced by an anonymous data member.
|
|
bool
|
|
has_data_member_replaced_by_anon_dm(const diff* diff)
|
|
{
|
|
const class_or_union_diff *c = is_class_or_union_diff(diff);
|
|
|
|
if (!c)
|
|
return false;
|
|
return !c->data_members_replaced_by_adms().empty();
|
|
}
|
|
|
|
/// Test if we are looking at two variables which types are both one
|
|
/// dimension array, with one of them being of unknow size and the two
|
|
/// variables having the same symbol size.
|
|
///
|
|
/// This can happen in the case of these two declarations, for instance:
|
|
///
|
|
/// unsigned int array[];
|
|
///
|
|
/// and:
|
|
///
|
|
/// unsigned int array[] ={0};
|
|
///
|
|
/// In both cases, the size of the ELF symbol of the variable 'array'
|
|
/// is 32 bits, but, at least in the first case
|
|
bool
|
|
is_var_1_dim_unknown_size_array_change(const var_decl_sptr& var1,
|
|
const var_decl_sptr& var2)
|
|
{
|
|
type_base_sptr /*first type*/ft =
|
|
peel_qualified_or_typedef_type(var1->get_type());
|
|
type_base_sptr /*second type*/st =
|
|
peel_qualified_or_typedef_type(var2->get_type());
|
|
|
|
array_type_def_sptr /*first array type*/fat = is_array_type(ft);
|
|
array_type_def_sptr /*second array type*/sat = is_array_type(st);
|
|
|
|
// The types of the variables must be arrays.
|
|
if (!fat || !sat)
|
|
return false;
|
|
|
|
// The arrays must have one dimension and at least one of them must
|
|
// be of unknown size.
|
|
if (fat->get_subranges().size() != 1
|
|
|| sat->get_subranges().size() != 1
|
|
|| (!fat->is_infinite() && !sat->is_infinite()))
|
|
return false;
|
|
|
|
// The variables must be equal modulo their type.
|
|
if (!var_equals_modulo_types(*var1, *var2, nullptr))
|
|
return false;
|
|
|
|
// The symbols of the variables must be defined and of the same
|
|
// non-zero size.
|
|
if (!var1->get_symbol()
|
|
|| !var2->get_symbol()
|
|
|| var1->get_symbol()->get_size() != var2->get_symbol()->get_size())
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
/// Test if we are looking at a diff that carries a change of
|
|
/// variables which types are both one dimension array, with one of
|
|
/// them being of unknow size and the two variables having the same
|
|
/// symbol size.
|
|
///
|
|
/// This can happen in the case of these two declarations, for instance:
|
|
///
|
|
/// unsigned int array[];
|
|
///
|
|
/// and:
|
|
///
|
|
/// unsigned int array[] ={0};
|
|
///
|
|
/// In both cases, the size of the ELF symbol of the variable 'array'
|
|
/// is 32 bits, but, at least in the first case
|
|
bool
|
|
is_var_1_dim_unknown_size_array_change(const diff* diff)
|
|
{
|
|
const var_diff* d = is_var_diff(diff);
|
|
|
|
if (!d)
|
|
return false;
|
|
|
|
var_decl_sptr f = d->first_var(), s = d->second_var();
|
|
|
|
return is_var_1_dim_unknown_size_array_change(f, s);
|
|
}
|
|
|
|
/// Test if a class_diff node has static members added or removed.
|
|
///
|
|
/// @param diff the diff node to consider.
|
|
///
|
|
/// @return true iff the class_diff node has static members added
|
|
/// or removed.
|
|
static bool
|
|
static_data_member_added_or_removed(const class_diff* diff)
|
|
{
|
|
if (diff && !diff_involves_decl_only_class(diff))
|
|
{
|
|
for (string_decl_base_sptr_map::const_iterator i =
|
|
diff->inserted_data_members().begin();
|
|
i != diff->inserted_data_members().end();
|
|
++i)
|
|
if (get_member_is_static(i->second))
|
|
return true;
|
|
|
|
for (string_decl_base_sptr_map::const_iterator i =
|
|
diff->deleted_data_members().begin();
|
|
i != diff->deleted_data_members().end();
|
|
++i)
|
|
if (get_member_is_static(i->second))
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/// Test if a class_diff node has a harmless "One Definition Rule"
|
|
/// violation that will cause a diagnostic rule.
|
|
///
|
|
/// The conditions this function looks for are:
|
|
///
|
|
/// 1/ The two subject of the diff must be canonically different
|
|
///
|
|
/// 2/ The two subjects of the diff must be structurally equal
|
|
///
|
|
/// 3/ The canonical types of the subjects of the diff must be
|
|
/// structurally different.
|
|
///
|
|
/// These conditions makes the diff node appears as it carries changes
|
|
/// (because of a ODR glitch present in the binary), but the glitch
|
|
/// has no effect on the structural equality of the subjects of the
|
|
/// diff. If we do not detect these conditions, we'd end up with a
|
|
/// diagnostic glitch where the reporter thinks there is an ABI change
|
|
/// (because of the canonical difference), but then it fails to give
|
|
/// any detail about it, because there is no structural change.
|
|
///
|
|
/// @param diff the diff node to consider.
|
|
///
|
|
/// @return true iff the the diff node has a harmless "One Definition
|
|
/// Rule" violation that cause an empty false positive.
|
|
static bool
|
|
class_diff_has_harmless_odr_violation_change(const diff* dif)
|
|
{
|
|
class_diff* d = dynamic_cast<class_diff*>(const_cast<diff*>(dif));
|
|
if (!d || !d->has_changes())
|
|
return false;
|
|
|
|
class_decl_sptr first = d->first_class_decl();
|
|
class_decl_sptr second = d->second_class_decl();
|
|
|
|
if (first->get_qualified_name() == second->get_qualified_name()
|
|
&& first != second
|
|
&& first->get_corpus() == second->get_corpus())
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
/// Test if a class_diff node has static members added or
|
|
/// removed.
|
|
///
|
|
/// @param diff the diff node to consider.
|
|
///
|
|
/// @return true iff the class_diff node has static members added
|
|
/// or removed.
|
|
static bool
|
|
static_data_member_added_or_removed(const diff* diff)
|
|
{
|
|
return static_data_member_added_or_removed
|
|
(dynamic_cast<const class_diff*>(diff));
|
|
}
|
|
|
|
/// Test if the class_diff node has a change involving virtual member
|
|
/// functions.
|
|
///
|
|
/// That means whether there is an added, removed or changed virtual
|
|
/// member function.
|
|
///
|
|
/// @param diff the class_diff node to consider.
|
|
///
|
|
/// @return true iff the class_diff node contains changes involving
|
|
/// virtual member functions.
|
|
static bool
|
|
has_virtual_mem_fn_change(const class_diff* diff)
|
|
{
|
|
if (!diff || diff_involves_decl_only_class(diff))
|
|
return false;
|
|
|
|
for (string_member_function_sptr_map::const_iterator i =
|
|
diff->deleted_member_fns().begin();
|
|
i != diff->deleted_member_fns().end();
|
|
++i)
|
|
{
|
|
if (get_member_function_is_virtual(i->second))
|
|
{
|
|
// Do not consider a virtual function that got deleted from
|
|
// an offset and re-inserted at the same offset as a
|
|
// "virtual member function change".
|
|
string_member_function_sptr_map::const_iterator j =
|
|
diff->inserted_member_fns().find(i->first);
|
|
if (j != diff->inserted_member_fns().end()
|
|
&& (get_member_function_vtable_offset(i->second)
|
|
== get_member_function_vtable_offset(j->second)))
|
|
continue;
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
for (string_member_function_sptr_map::const_iterator i =
|
|
diff->inserted_member_fns().begin();
|
|
i != diff->inserted_member_fns().end();
|
|
++i)
|
|
{
|
|
if (get_member_function_is_virtual(i->second))
|
|
{
|
|
// Do not consider a virtual function that got deleted from
|
|
// an offset and re-inserted at the same offset as a
|
|
// "virtual member function change".
|
|
string_member_function_sptr_map::const_iterator j =
|
|
diff->deleted_member_fns().find(i->first);
|
|
if (j != diff->deleted_member_fns().end()
|
|
&& (get_member_function_vtable_offset(i->second)
|
|
== get_member_function_vtable_offset(j->second)))
|
|
continue;
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
for (function_decl_diff_sptrs_type::const_iterator i =
|
|
diff->changed_member_fns().begin();
|
|
i != diff->changed_member_fns().end();
|
|
++i)
|
|
if (get_member_function_is_virtual((*i)->first_function_decl())
|
|
|| get_member_function_is_virtual((*i)->second_function_decl()))
|
|
{
|
|
if (get_member_function_vtable_offset((*i)->first_function_decl())
|
|
== get_member_function_vtable_offset((*i)->second_function_decl()))
|
|
continue;
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/// Test if the function_decl_diff node has a change involving virtual
|
|
/// member functions.
|
|
///
|
|
/// That means whether there is an added, removed or changed virtual
|
|
/// member function.
|
|
///
|
|
/// @param diff the function_decl_diff node to consider.
|
|
///
|
|
/// @return true iff the function_decl_diff node contains changes
|
|
/// involving virtual member functions.
|
|
bool
|
|
has_virtual_mem_fn_change(const function_decl_diff* diff)
|
|
{
|
|
if (!diff)
|
|
return false;
|
|
|
|
function_decl_sptr ff = diff->first_function_decl(),
|
|
sf = diff->second_function_decl();
|
|
|
|
if (!is_member_function(ff)
|
|
|| !is_member_function(sf))
|
|
return false;
|
|
|
|
bool ff_is_virtual = get_member_function_is_virtual(ff),
|
|
sf_is_virtual = get_member_function_is_virtual(sf);
|
|
|
|
if (ff_is_virtual != sf_is_virtual)
|
|
return true;
|
|
|
|
size_t ff_vtable_offset = get_member_function_vtable_offset(ff),
|
|
sf_vtable_offset = get_member_function_vtable_offset(sf);
|
|
|
|
if (ff_vtable_offset != sf_vtable_offset)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
/// Test if the class_diff node has a change involving virtual member
|
|
/// functions.
|
|
///
|
|
/// That means whether there is an added, removed or changed virtual
|
|
/// member function.
|
|
///
|
|
/// @param diff the class_diff node to consider.
|
|
///
|
|
/// @return true iff the class_diff node contains changes involving
|
|
/// virtual member functions.
|
|
static bool
|
|
has_virtual_mem_fn_change(const diff* diff)
|
|
{
|
|
return (has_virtual_mem_fn_change(dynamic_cast<const class_diff*>(diff))
|
|
|| has_virtual_mem_fn_change(dynamic_cast<const function_decl_diff*>(diff)));
|
|
}
|
|
|
|
/// Test if the class_diff has changes to non virtual member
|
|
/// functions.
|
|
///
|
|
///@param diff the class_diff nod e to consider.
|
|
///
|
|
/// @retrurn iff the class_diff node has changes to non virtual member
|
|
/// functions.
|
|
static bool
|
|
has_non_virtual_mem_fn_change(const class_diff* diff)
|
|
{
|
|
if (!diff || diff_involves_decl_only_class(diff))
|
|
return false;
|
|
|
|
for (string_member_function_sptr_map::const_iterator i =
|
|
diff->deleted_member_fns().begin();
|
|
i != diff->deleted_member_fns().end();
|
|
++i)
|
|
if (!get_member_function_is_virtual(i->second))
|
|
return true;
|
|
|
|
for (string_member_function_sptr_map::const_iterator i =
|
|
diff->inserted_member_fns().begin();
|
|
i != diff->inserted_member_fns().end();
|
|
++i)
|
|
if (!get_member_function_is_virtual(i->second))
|
|
return true;
|
|
|
|
for (function_decl_diff_sptrs_type::const_iterator i =
|
|
diff->changed_member_fns().begin();
|
|
i != diff->changed_member_fns().end();
|
|
++i)
|
|
if(!get_member_function_is_virtual((*i)->first_function_decl())
|
|
&& !get_member_function_is_virtual((*i)->second_function_decl()))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
/// Test if the class_diff has changes to non virtual member
|
|
/// functions.
|
|
///
|
|
///@param diff the class_diff nod e to consider.
|
|
///
|
|
/// @retrurn iff the class_diff node has changes to non virtual member
|
|
/// functions.
|
|
static bool
|
|
has_non_virtual_mem_fn_change(const diff* diff)
|
|
{return has_non_virtual_mem_fn_change(dynamic_cast<const class_diff*>(diff));}
|
|
|
|
/// Test if a class_diff carries base classes adding or removals.
|
|
///
|
|
/// @param diff the class_diff to consider.
|
|
///
|
|
/// @return true iff @p diff carries base classes adding or removals.
|
|
static bool
|
|
base_classes_added_or_removed(const class_diff* diff)
|
|
{
|
|
if (!diff)
|
|
return false;
|
|
return diff->deleted_bases().size() || diff->inserted_bases().size();
|
|
}
|
|
|
|
/// Test if a class_diff carries base classes adding or removals.
|
|
///
|
|
/// @param diff the class_diff to consider.
|
|
///
|
|
/// @return true iff @p diff carries base classes adding or removals.
|
|
static bool
|
|
base_classes_added_or_removed(const diff* diff)
|
|
{return base_classes_added_or_removed(dynamic_cast<const class_diff*>(diff));}
|
|
|
|
/// Test if two classes that are decl-only (have the decl-only flag
|
|
/// and carry no data members) but are different just by their size.
|
|
///
|
|
/// In some weird DWARF representation, it happens that a decl-only
|
|
/// class (with no data member) actually carries a non-zero size.
|
|
/// That shouldn't happen, but hey, we need to deal with real life.
|
|
/// So we need to detect that case first.
|
|
///
|
|
/// @param first the first class or union to consider.
|
|
///
|
|
/// @param seconf the second class or union to consider.
|
|
///
|
|
/// @return true if the two classes are decl-only and differ in their
|
|
/// size.
|
|
bool
|
|
is_decl_only_class_with_size_change(const class_or_union& first,
|
|
const class_or_union& second)
|
|
{
|
|
if (first.get_qualified_name() != second.get_qualified_name())
|
|
return false;
|
|
|
|
if (!first.get_is_declaration_only() || !second.get_is_declaration_only())
|
|
return false;
|
|
|
|
bool f_is_empty = first.get_data_members().empty();
|
|
bool s_is_empty = second.get_data_members().empty();
|
|
|
|
return f_is_empty && s_is_empty;
|
|
}
|
|
|
|
/// Test if two classes that are decl-only (have the decl-only flag
|
|
/// and carry no data members) but are different just by their size.
|
|
///
|
|
/// In some weird DWARF representation, it happens that a decl-only
|
|
/// class (with no data member) actually carries a non-zero size.
|
|
/// That shouldn't happen, but hey, we need to deal with real life.
|
|
/// So we need to detect that case first.
|
|
///
|
|
/// @param first the first class or union to consider.
|
|
///
|
|
/// @param seconf the second class or union to consider.
|
|
///
|
|
/// @return true if the two classes are decl-only and differ in their
|
|
/// size.
|
|
bool
|
|
is_decl_only_class_with_size_change(const class_or_union_sptr& first,
|
|
const class_or_union_sptr& second)
|
|
{
|
|
if (!first || !second)
|
|
return false;
|
|
|
|
class_or_union_sptr f = look_through_decl_only_class(first);
|
|
class_or_union_sptr s = look_through_decl_only_class(second);
|
|
|
|
return is_decl_only_class_with_size_change(*f, *s);
|
|
}
|
|
|
|
/// Test if a diff node is for two classes that are decl-only (have
|
|
/// the decl-only flag and carry no data members) but are different
|
|
/// just by their size.
|
|
///
|
|
/// In some weird DWARF representation, it happens that a decl-only
|
|
/// class (with no data member) actually carries a non-zero size.
|
|
/// That shouldn't happen, but hey, we need to deal with real life.
|
|
/// So we need to detect that case first.
|
|
///
|
|
/// @param diff the diff node to consider.
|
|
///
|
|
/// @return true if the two classes are decl-only and differ in their
|
|
/// size.
|
|
bool
|
|
is_decl_only_class_with_size_change(const diff *diff)
|
|
{
|
|
const class_or_union_diff *d = dynamic_cast<const class_or_union_diff*>(diff);
|
|
if (!d)
|
|
return false;
|
|
|
|
class_or_union_sptr f =
|
|
look_through_decl_only_class(d->first_class_or_union());
|
|
class_or_union_sptr s =
|
|
look_through_decl_only_class(d->second_class_or_union());
|
|
|
|
return is_decl_only_class_with_size_change(f, s);
|
|
}
|
|
|
|
/// Test if two @ref decl_base_sptr are different just by the
|
|
/// fact that one is decl-only and the other one is defined.
|
|
///
|
|
/// @param first the first decl to consider.
|
|
///
|
|
/// @param second the second decl to consider.
|
|
///
|
|
/// @return true iff the two arguments are different just by the fact
|
|
/// that one is decl-only and the other one is defined.
|
|
bool
|
|
has_decl_only_def_change(const decl_base_sptr& first,
|
|
const decl_base_sptr& second)
|
|
{
|
|
if (!first || !second)
|
|
return false;
|
|
|
|
decl_base_sptr f =
|
|
look_through_decl_only(first);
|
|
decl_base_sptr s =
|
|
look_through_decl_only(second);
|
|
|
|
if (f->get_qualified_name() != s->get_qualified_name())
|
|
return false;
|
|
|
|
return f->get_is_declaration_only() != s->get_is_declaration_only();
|
|
}
|
|
|
|
/// Test if a diff carries a change in which the two decls are
|
|
/// different by the fact that one is a decl-only and the other one is
|
|
/// defined.
|
|
///
|
|
/// @param diff the diff node to consider.
|
|
///
|
|
/// @return true if the diff carries a change in which the two decls
|
|
/// are different by the fact that one is a decl-only and the other
|
|
/// one is defined.
|
|
bool
|
|
has_decl_only_def_change(const diff *d)
|
|
{
|
|
if (!d)
|
|
return false;
|
|
|
|
decl_base_sptr f =
|
|
look_through_decl_only(is_decl(d->first_subject()));
|
|
decl_base_sptr s =
|
|
look_through_decl_only(is_decl(d->second_subject()));
|
|
|
|
return has_decl_only_def_change(f, s);
|
|
}
|
|
|
|
|
|
/// Test if two @ref class_or_union_sptr are different just by the
|
|
/// fact that one is decl-only and the other one is defined.
|
|
///
|
|
/// @param first the first class or union to consider.
|
|
///
|
|
/// @param second the second class or union to consider.
|
|
///
|
|
/// @return true iff the two arguments are different just by the fact
|
|
/// that one is decl-only and the other one is defined.
|
|
bool
|
|
has_class_decl_only_def_change(const class_or_union_sptr& first,
|
|
const class_or_union_sptr& second)
|
|
{
|
|
if (!first || !second)
|
|
return false;
|
|
|
|
class_or_union_sptr f =
|
|
look_through_decl_only_class(first);
|
|
class_or_union_sptr s =
|
|
look_through_decl_only_class(second);
|
|
|
|
if (f->get_qualified_name() != s->get_qualified_name())
|
|
return false;
|
|
|
|
return f->get_is_declaration_only() != s->get_is_declaration_only();
|
|
}
|
|
|
|
/// Test if two @ref enum_sptr are different just by the
|
|
/// fact that one is decl-only and the other one is defined.
|
|
///
|
|
/// @param first the first enum to consider.
|
|
///
|
|
/// @param second the second enum to consider.
|
|
///
|
|
/// @return true iff the two arguments are different just by the fact
|
|
/// that one is decl-only and the other one is defined.
|
|
bool
|
|
has_enum_decl_only_def_change(const enum_type_decl_sptr& first,
|
|
const enum_type_decl_sptr& second)
|
|
{
|
|
if (!first || !second)
|
|
return false;
|
|
|
|
enum_type_decl_sptr f = look_through_decl_only_enum(first);
|
|
enum_type_decl_sptr s = look_through_decl_only_enum(second);
|
|
|
|
if (f->get_qualified_name() != s->get_qualified_name())
|
|
return false;
|
|
|
|
return f->get_is_declaration_only() != s->get_is_declaration_only();
|
|
}
|
|
|
|
/// Test if a class_or_union_diff carries a change in which the two
|
|
/// classes are different by the fact that one is a decl-only and the
|
|
/// other one is defined.
|
|
///
|
|
/// @param diff the diff node to consider.
|
|
///
|
|
/// @return true if the class_or_union_diff carries a change in which
|
|
/// the two classes are different by the fact that one is a decl-only
|
|
/// and the other one is defined.
|
|
bool
|
|
has_class_decl_only_def_change(const diff *diff)
|
|
{
|
|
const class_or_union_diff *d = dynamic_cast<const class_or_union_diff*>(diff);
|
|
if (!d)
|
|
return false;
|
|
|
|
class_or_union_sptr f =
|
|
look_through_decl_only_class(d->first_class_or_union());
|
|
class_or_union_sptr s =
|
|
look_through_decl_only_class(d->second_class_or_union());
|
|
|
|
return has_class_decl_only_def_change(f, s);
|
|
}
|
|
|
|
/// Test if a enum_diff carries a change in which the two enums are
|
|
/// different by the fact that one is a decl-only and the other one is
|
|
/// defined.
|
|
///
|
|
/// @param diff the diff node to consider.
|
|
///
|
|
/// @return true if the enum_diff carries a change in which the two
|
|
/// enums are different by the fact that one is a decl-only and the
|
|
/// other one is defined.
|
|
bool
|
|
has_enum_decl_only_def_change(const diff *diff)
|
|
{
|
|
const enum_diff *d = dynamic_cast<const enum_diff*>(diff);
|
|
if (!d)
|
|
return false;
|
|
|
|
enum_type_decl_sptr f = look_through_decl_only_enum(d->first_enum());
|
|
enum_type_decl_sptr s = look_through_decl_only_enum(d->second_enum());
|
|
|
|
return has_enum_decl_only_def_change(f, s);
|
|
}
|
|
|
|
/// Test if a diff node carries a basic type name change.
|
|
///
|
|
/// @param d the diff node to consider.
|
|
///
|
|
/// @return true iff the diff node carries a basic type name change.
|
|
bool
|
|
has_basic_type_name_change(const diff *d)
|
|
{
|
|
if (const type_decl_diff *dif = is_diff_of_basic_type(d))
|
|
if (decl_name_changed(dif))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
/// Test if a diff node carries a class or union type name change.
|
|
///
|
|
/// @param d the diff node to consider.
|
|
///
|
|
/// @return true iff the diff node carries a class or union type name
|
|
/// change.
|
|
bool
|
|
has_class_or_union_type_name_change(const diff *d)
|
|
{
|
|
if (const class_or_union_diff *dif = is_diff_of_class_or_union_type(d))
|
|
if (decl_name_changed(dif))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
/// Test if a diff node carries a basic or class type name change.
|
|
///
|
|
/// @param d the diff node to consider.
|
|
///
|
|
/// @return true iff the diff node carries a basic or class type name
|
|
/// change.
|
|
bool
|
|
has_basic_or_class_type_name_change(const diff *d)
|
|
{
|
|
return (has_basic_type_name_change(d)
|
|
|| has_class_or_union_type_name_change(d));
|
|
}
|
|
|
|
/// Test if a diff node carries a distinct type change or a
|
|
/// pointer/reference/typedef to distinct type change.
|
|
///
|
|
/// Note that a distinct type change is a change where the two
|
|
/// subjects of the change are not of the same kind, e.g, a basic type
|
|
/// that got changed into a qualified type.
|
|
///
|
|
/// @param d the diff node to consider.
|
|
///
|
|
/// @return true iff @p d is mostly a distinct diff.
|
|
bool
|
|
is_mostly_distinct_diff(const diff *d)
|
|
{
|
|
if (is_distinct_diff(d))
|
|
return true;
|
|
|
|
// Let's consider that 'd' is a type diff ...
|
|
diff * td = const_cast<type_diff_base*>(is_type_diff(d));
|
|
if (!td)
|
|
{
|
|
// ... or a function parameter diff. In which case, let's get
|
|
// its child type diff ...
|
|
fn_parm_diff *pd = const_cast<fn_parm_diff*>(is_fn_parm_diff(d));
|
|
if (pd)
|
|
{
|
|
td = const_cast<type_diff_base*>(is_type_diff(pd->type_diff().get()));
|
|
if (!td)
|
|
// if the diff of the fn_parm_diff is a a distinct diff
|
|
// then handle it.
|
|
td = const_cast<distinct_diff*>
|
|
(is_distinct_diff(pd->type_diff().get()));
|
|
}
|
|
else
|
|
return false;
|
|
}
|
|
|
|
// At this point, if we are not looking at a type diff we must have
|
|
// bailed out already.
|
|
ABG_ASSERT(td);
|
|
|
|
type_base_sptr first = is_type(td->first_subject());
|
|
type_base_sptr second = is_type(td->second_subject());
|
|
|
|
first = peel_typedef_pointer_or_reference_type(first);
|
|
second = peel_typedef_pointer_or_reference_type(second);
|
|
ABG_ASSERT(first && second);
|
|
|
|
return distinct_diff::entities_are_of_distinct_kinds(first, second);
|
|
}
|
|
|
|
/// Test if a diff node carries a non-anonymous data member to
|
|
/// anonymous data member change, or vice-versa.
|
|
///
|
|
/// @param d the diff node to consider.
|
|
///
|
|
/// @return true iff @p d carries a non-anonymous to anonymous data
|
|
/// member change, or vice-versa.
|
|
bool
|
|
has_anonymous_data_member_change(const diff *d)
|
|
{
|
|
if (is_anonymous_data_member(d->first_subject())
|
|
|| is_anonymous_data_member(d->second_subject()))
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
/// Test if a diff node carries a non-anonymous data member to
|
|
/// anonymous data member change, or vice-versa.
|
|
///
|
|
/// @param d the diff node to consider.
|
|
///
|
|
/// @return true iff @p d carries a non-anonymous to anonymous data
|
|
/// member change, or vice-versa.
|
|
bool
|
|
has_anonymous_data_member_change(const diff_sptr &d)
|
|
{return has_anonymous_data_member_change(d.get());}
|
|
|
|
/// Test if an enum_diff carries an enumerator insertion.
|
|
///
|
|
/// @param diff the enum_diff to consider.
|
|
///
|
|
/// @return true iff @p diff carries an enumerator insertion.
|
|
static bool
|
|
has_enumerator_insertion(const diff* diff)
|
|
{
|
|
if (const enum_diff* d = dynamic_cast<const enum_diff*>(diff))
|
|
return !d->inserted_enumerators().empty();
|
|
return false;
|
|
}
|
|
|
|
/// Test if an enum_diff carries an enumerator removal.
|
|
///
|
|
/// @param diff the enum_diff to consider.
|
|
///
|
|
/// @return true iff @p diff carries an enumerator removal or change.
|
|
static bool
|
|
has_enumerator_removal_or_change(const diff* diff)
|
|
{
|
|
if (const enum_diff* d = dynamic_cast<const enum_diff*>(diff))
|
|
return (!d->deleted_enumerators().empty()
|
|
|| !d->changed_enumerators().empty());
|
|
return false;
|
|
}
|
|
|
|
/// Test if an enum_diff carries a harmful change.
|
|
///
|
|
/// @param diff the enum_diff to consider.
|
|
///
|
|
/// @return true iff @p diff carries a harmful change.
|
|
static bool
|
|
has_harmful_enum_change(const diff* diff)
|
|
{
|
|
if (const enum_diff* d = dynamic_cast<const enum_diff*>(diff))
|
|
return (has_enumerator_removal_or_change(d)
|
|
|| has_type_size_change(d));
|
|
return false;
|
|
}
|
|
|
|
/// Test if a diff node carries a harmless change of an enum into an
|
|
/// integer (or vice-versa).
|
|
///
|
|
/// The test takes into account the fact change we care about might be
|
|
/// wrapped into a typedef or qualified type diff.
|
|
///
|
|
/// @param diff the diff node to consider.
|
|
///
|
|
/// @return true if @p diff is a harmless enum to integer change.
|
|
static bool
|
|
has_harmless_enum_to_int_change(const diff* diff)
|
|
{
|
|
if (!diff)
|
|
return false;
|
|
|
|
diff = peel_typedef_or_qualified_type_diff(diff);
|
|
|
|
if (const distinct_diff *d = is_distinct_diff(diff))
|
|
{
|
|
const enum_type_decl *enum_type = 0;
|
|
const type_base *integer_type = 0;
|
|
|
|
type_base *first_type =
|
|
peel_qualified_or_typedef_type(is_type(d->first().get()));
|
|
type_base *second_type =
|
|
peel_qualified_or_typedef_type(is_type(d->second().get()));
|
|
|
|
if (const enum_type_decl *e = is_enum_type(first_type))
|
|
enum_type = e;
|
|
else if (const enum_type_decl *e = is_enum_type(second_type))
|
|
enum_type = e;
|
|
|
|
if (const type_base * i = is_type_decl(first_type))
|
|
integer_type = i;
|
|
else if (const type_base *i = is_type_decl(second_type))
|
|
integer_type = i;
|
|
|
|
if (enum_type
|
|
&& integer_type
|
|
&& enum_type->get_size_in_bits() == integer_type->get_size_in_bits())
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/// Test if an @ref fn_parm_diff node has a top cv qualifier change on
|
|
/// the type of the function parameter.
|
|
///
|
|
/// @param diff the diff node to consider. It should be a @ref
|
|
/// fn_parm_diff, otherwise the function returns 'false' directly.
|
|
///
|
|
/// @return true iff @p diff is a @ref fn_parm_diff node that has a
|
|
/// top cv qualifier change on the type of the function parameter.
|
|
static bool
|
|
has_fn_parm_type_top_cv_qual_change(const diff* diff)
|
|
{
|
|
// is diff a "function parameter diff node?
|
|
const fn_parm_diff* parm_diff = is_fn_parm_diff(diff);
|
|
|
|
if (!parm_diff || !parm_diff->has_changes())
|
|
// diff either carries no change or is not a function parameter
|
|
// diff node. So bail out.
|
|
return false;
|
|
|
|
function_decl::parameter_sptr first_parm = parm_diff->first_parameter();
|
|
function_decl::parameter_sptr second_parm = parm_diff->second_parameter();
|
|
|
|
type_base_sptr first_parm_type = first_parm->get_type();
|
|
type_base_sptr second_parm_type = second_parm->get_type();
|
|
|
|
if (!is_qualified_type(first_parm_type)
|
|
&& !is_qualified_type(second_parm_type))
|
|
// None of the parameter types is qualified.
|
|
return false;
|
|
|
|
qualified_type_def::CV cv_quals_1 = qualified_type_def::CV_NONE;
|
|
qualified_type_def::CV cv_quals_2 = qualified_type_def::CV_NONE;
|
|
type_base_sptr peeled_type_1 = first_parm_type;
|
|
type_base_sptr peeled_type_2 = second_parm_type;
|
|
|
|
if (qualified_type_def_sptr qtype1 = is_qualified_type(first_parm_type))
|
|
{
|
|
cv_quals_1 = qtype1->get_cv_quals();
|
|
peeled_type_1 = peel_qualified_type(qtype1);
|
|
}
|
|
|
|
if (qualified_type_def_sptr qtype2 = is_qualified_type(second_parm_type))
|
|
{
|
|
cv_quals_2 = qtype2->get_cv_quals();
|
|
peeled_type_2 = peel_qualified_type(qtype2);
|
|
}
|
|
|
|
if (peeled_type_1
|
|
&& peeled_type_2
|
|
&& get_type_name(peeled_type_1) == get_type_name(peeled_type_2)
|
|
&& cv_quals_1 != cv_quals_2)
|
|
// The top-level CV qualifiers of the function type are different
|
|
// and the un-qualified variant (peeled) of said function types
|
|
// are equal. This means the only change the function types have
|
|
// are about top-level CV qualifiers.
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
/// Test if a type diff only carries a CV qualifier-only change.
|
|
///
|
|
/// @param type_dif the type dif to consider.
|
|
///
|
|
/// @return true iff the type_diff carries a CV qualifier only change.
|
|
static bool
|
|
type_diff_has_cv_qual_change_only(const diff *type_dif)
|
|
{
|
|
if (!is_type_diff(type_dif))
|
|
return false;
|
|
|
|
if (is_pointer_diff(type_dif))
|
|
type_dif = peel_pointer_diff(type_dif);
|
|
else if (is_reference_diff(type_dif))
|
|
type_dif = peel_reference_diff(type_dif);
|
|
|
|
const type_base *f = 0;
|
|
const type_base *s = 0;
|
|
if (const distinct_diff *d = is_distinct_diff(type_dif))
|
|
{
|
|
if (is_qualified_type(d->first()) == is_qualified_type(d->second()))
|
|
return false;
|
|
else
|
|
{
|
|
f = is_type(d->first()).get();
|
|
s = is_type(d->second()).get();
|
|
}
|
|
}
|
|
else if (const qualified_type_diff *d = is_qualified_type_diff(type_dif))
|
|
{
|
|
f = is_type(d->first_qualified_type()).get();
|
|
s = is_type(d->second_qualified_type()).get();
|
|
}
|
|
else
|
|
return false;
|
|
|
|
f = peel_qualified_type(f);
|
|
s = peel_qualified_type(s);
|
|
|
|
// If f and s are arrays, note that they can differ only by the cv
|
|
// qualifier of the array element type. That cv qualifier is not
|
|
// removed by peel_qualified_type. So we need to test this case
|
|
// specifically.
|
|
if (array_type_def *f_a = is_array_type(f))
|
|
if (array_type_def *s_a = is_array_type(s))
|
|
return equals_modulo_cv_qualifier(f_a, s_a);
|
|
|
|
return *f == *s;
|
|
}
|
|
|
|
/// Test if an @ref fn_parm_diff node has a cv qualifier change on the
|
|
/// type of the function parameter. That is, we are looking for
|
|
/// changes like 'const char*' to 'char*'.
|
|
///
|
|
/// @param diff the diff node to consider. It should be a @ref
|
|
/// fn_parm_diff, otherwise the function returns 'false' directly.
|
|
///
|
|
/// @return true iff @p diff is a @ref fn_parm_diff node that has a
|
|
/// cv qualifier change on the type of the function parameter.
|
|
static bool
|
|
has_fn_parm_type_cv_qual_change(const diff* dif)
|
|
{
|
|
// is diff a "function parameter diff node?
|
|
const fn_parm_diff* parm_diff = is_fn_parm_diff(dif);
|
|
|
|
if (!parm_diff || !parm_diff->has_changes())
|
|
// diff either carries no change or is not a function parameter
|
|
// diff node. So bail out.
|
|
return false;
|
|
|
|
const diff *type_dif = parm_diff->type_diff().get();
|
|
return type_diff_has_cv_qual_change_only(type_dif);
|
|
}
|
|
|
|
/// Test if a function type or decl diff node carries a CV
|
|
/// qualifier-only change on its return type.
|
|
///
|
|
/// @param dif the diff node to consider. Note that if this is
|
|
/// neither a function type nor decl diff node, the function returns
|
|
/// false.
|
|
///
|
|
/// @return true iff @p dif is a function decl or type diff node which
|
|
/// carries a CV qualifier-only change on its return type.
|
|
static bool
|
|
has_fn_return_type_cv_qual_change(const diff* dif)
|
|
{
|
|
const function_type_diff* fn_type_diff = is_function_type_diff(dif);
|
|
if (!fn_type_diff)
|
|
if (const function_decl_diff* fn_decl_diff = is_function_decl_diff(dif))
|
|
fn_type_diff = fn_decl_diff->type_diff().get();
|
|
|
|
if (!fn_type_diff)
|
|
return false;
|
|
|
|
const diff* return_type_diff = fn_type_diff->return_type_diff().get();
|
|
return type_diff_has_cv_qual_change_only(return_type_diff);
|
|
}
|
|
|
|
/// Test if a function type or decl diff node carries a function
|
|
/// parameter addition or removal.
|
|
///
|
|
/// @param dif the diff node to consider. Note that if this is
|
|
/// neither a function type nor decl diff node, the function returns
|
|
/// false.
|
|
///
|
|
/// @return true iff @p dif is a function decl or type diff node which
|
|
/// carries a function parameter addition or removal.
|
|
static bool
|
|
has_added_or_removed_function_parameters(const diff *dif)
|
|
{
|
|
const function_type_diff *fn_type_diff = is_function_type_diff(dif);
|
|
if (!fn_type_diff)
|
|
if (const function_decl_diff* fn_decl_diff = is_function_decl_diff(dif))
|
|
fn_type_diff = fn_decl_diff->type_diff().get();
|
|
|
|
if (!fn_type_diff)
|
|
return false;
|
|
|
|
if (!(fn_type_diff->sorted_deleted_parms().empty()
|
|
&& fn_type_diff->sorted_added_parms().empty()))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
/// Test if a variable diff node carries a CV qualifier change on its type.
|
|
///
|
|
/// @param dif the diff node to consider. Note that if it's not of
|
|
/// var_diff type, the function returns false.
|
|
///
|
|
/// @return true iff the @p dif carries a CV qualifier change on its
|
|
/// type.
|
|
static bool
|
|
has_var_type_cv_qual_change(const diff* dif)
|
|
{
|
|
const var_diff *var_dif = is_var_diff(dif);
|
|
if (!var_dif)
|
|
return false;
|
|
|
|
diff *type_dif = var_dif->type_diff().get();
|
|
if (!type_dif)
|
|
return false;
|
|
|
|
return type_diff_has_cv_qual_change_only(type_dif);
|
|
}
|
|
|
|
/// Test if a diff node carries a void* to pointer type change.
|
|
///
|
|
/// Note that this function looks through typedef and qualifier types
|
|
/// to find the void pointer.
|
|
///
|
|
/// @param dif the diff node to consider.
|
|
///
|
|
/// @return true iff @p dif carries a void* to pointer type change.
|
|
static bool
|
|
has_void_ptr_to_ptr_change(const diff* dif)
|
|
{
|
|
dif = peel_typedef_diff(dif);
|
|
|
|
if (const distinct_diff *d = is_distinct_diff(dif))
|
|
{
|
|
const type_base *f = is_type(d->first().get());
|
|
const type_base *s = is_type(d->second().get());
|
|
|
|
f = peel_qualified_or_typedef_type(f);
|
|
s = peel_qualified_or_typedef_type(s);
|
|
|
|
if (is_void_pointer_type(f)
|
|
&& is_pointer_type(s)
|
|
&& !is_void_pointer_type(s)
|
|
&& f->get_size_in_bits() == s->get_size_in_bits())
|
|
return true;
|
|
}
|
|
else if (const pointer_diff *d = is_pointer_diff(dif))
|
|
{
|
|
const type_base *f = is_type(d->first_pointer()).get();
|
|
const type_base *s = is_type(d->second_pointer()).get();
|
|
|
|
f = peel_qualified_or_typedef_type(f);
|
|
s = peel_qualified_or_typedef_type(s);
|
|
|
|
if (is_void_pointer_type(f)
|
|
&& is_pointer_type(s)
|
|
&& !is_void_pointer_type(s)
|
|
&& f->get_size_in_bits() == s->get_size_in_bits())
|
|
return true;
|
|
}
|
|
else if (const qualified_type_diff *d = is_qualified_type_diff(dif))
|
|
{
|
|
const type_base *f = is_type(d->first_qualified_type()).get();
|
|
const type_base *s = is_type(d->second_qualified_type()).get();
|
|
|
|
f = peel_qualified_or_typedef_type(f);
|
|
s = peel_qualified_or_typedef_type(s);
|
|
|
|
if (is_void_pointer_type(f)
|
|
&& is_pointer_type(s)
|
|
&& !is_void_pointer_type(s)
|
|
&& f->get_size_in_bits() == s->get_size_in_bits())
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/// Test if a diff node carries a benign change to the size of a
|
|
/// variable of type array.
|
|
///
|
|
/// A benign size change is a change in size (from or to infinite) of
|
|
/// the array as expressed by the debug info, but when the *ELF* size
|
|
/// (what really matters) of the variable object hasn't changed. This
|
|
/// happens when the debug info emitter did have trouble figuring out
|
|
/// the actual size of the array.
|
|
///
|
|
/// @param dif the diff node to consider.
|
|
///
|
|
/// @return true iff @p dif contains the benign array type size change.
|
|
static bool
|
|
has_benign_array_of_unknown_size_change(const diff* dif)
|
|
{
|
|
return is_var_1_dim_unknown_size_array_change(dif);
|
|
}
|
|
|
|
/// Test if a union diff node does have changes that don't impact its
|
|
/// size.
|
|
///
|
|
/// @param d the union diff node to consider.
|
|
///
|
|
/// @return true iff @p d is a diff node which has changes that don't
|
|
/// impact its size.
|
|
bool
|
|
union_diff_has_harmless_changes(const diff *d)
|
|
{
|
|
if (is_union_diff(d)
|
|
&& d->has_changes()
|
|
&& !has_type_size_change(d))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
/// Detect if the changes carried by a given diff node are deemed
|
|
/// harmless and do categorize the diff node accordingly.
|
|
///
|
|
/// @param d the diff node being visited.
|
|
///
|
|
/// @param pre this is true iff the node is being visited *before* the
|
|
/// children nodes of @p d.
|
|
///
|
|
/// @return true iff the traversal shall keep going after the
|
|
/// completion of this function.
|
|
static bool
|
|
categorize_harmless_diff_node(diff *d, bool pre)
|
|
{
|
|
if (!d->has_changes())
|
|
return true;
|
|
|
|
if (pre)
|
|
{
|
|
diff_category category = NO_CHANGE_CATEGORY;
|
|
|
|
decl_base_sptr f = is_decl(d->first_subject()),
|
|
s = is_decl(d->second_subject());
|
|
|
|
if (has_class_decl_only_def_change(d)
|
|
|| has_enum_decl_only_def_change(d))
|
|
category |= TYPE_DECL_ONLY_DEF_CHANGE_CATEGORY;
|
|
|
|
if (access_changed(f, s))
|
|
category |= ACCESS_CHANGE_CATEGORY;
|
|
|
|
if (is_compatible_change(f, s))
|
|
category |= COMPATIBLE_TYPE_CHANGE_CATEGORY;
|
|
|
|
if (has_harmless_name_change(f, s)
|
|
|| class_diff_has_harmless_odr_violation_change(d))
|
|
category |= HARMLESS_DECL_NAME_CHANGE_CATEGORY;
|
|
|
|
if (union_diff_has_harmless_changes(d))
|
|
category |= HARMLESS_UNION_CHANGE_CATEGORY;
|
|
|
|
if (has_non_virtual_mem_fn_change(d))
|
|
category |= NON_VIRT_MEM_FUN_CHANGE_CATEGORY;
|
|
|
|
if (static_data_member_added_or_removed(d)
|
|
|| static_data_member_type_size_changed(f, s))
|
|
category |= STATIC_DATA_MEMBER_CHANGE_CATEGORY;
|
|
|
|
if (has_data_member_replaced_by_anon_dm(d))
|
|
category |= HARMLESS_DATA_MEMBER_CHANGE_CATEGORY;
|
|
|
|
if ((has_enumerator_insertion(d)
|
|
&& !has_harmful_enum_change(d))
|
|
|| has_harmless_enum_to_int_change(d))
|
|
category |= HARMLESS_ENUM_CHANGE_CATEGORY;
|
|
|
|
if (function_name_changed_but_not_symbol(d))
|
|
category |= HARMLESS_SYMBOL_ALIAS_CHANGE_CATEGORY;
|
|
|
|
if (has_fn_parm_type_top_cv_qual_change(d))
|
|
category |= FN_PARM_TYPE_TOP_CV_CHANGE_CATEGORY;
|
|
|
|
if (has_fn_parm_type_cv_qual_change(d))
|
|
category |= FN_PARM_TYPE_CV_CHANGE_CATEGORY;
|
|
|
|
if (has_fn_return_type_cv_qual_change(d))
|
|
category |= FN_RETURN_TYPE_CV_CHANGE_CATEGORY;
|
|
|
|
if (has_var_type_cv_qual_change(d))
|
|
category |= VAR_TYPE_CV_CHANGE_CATEGORY;
|
|
|
|
if (has_void_ptr_to_ptr_change(d))
|
|
category |= VOID_PTR_TO_PTR_CHANGE_CATEGORY;
|
|
|
|
if (has_benign_array_of_unknown_size_change(d))
|
|
category |= BENIGN_INFINITE_ARRAY_CHANGE_CATEGORY;
|
|
|
|
if (category)
|
|
{
|
|
d->add_to_local_and_inherited_categories(category);
|
|
// Also update the category of the canonical node.
|
|
if (diff * canonical = d->get_canonical_diff())
|
|
canonical->add_to_local_and_inherited_categories(category);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/// Detect if the changes carried by a given diff node are deemed
|
|
/// harmful and do categorize the diff node accordingly.
|
|
///
|
|
/// @param d the diff node being visited.
|
|
///
|
|
/// @param pre this is true iff the node is being visited *before* the
|
|
/// children nodes of @p d.
|
|
///
|
|
/// @return true iff the traversal shall keep going after the
|
|
/// completion of this function.
|
|
static bool
|
|
categorize_harmful_diff_node(diff *d, bool pre)
|
|
{
|
|
if (!d->has_changes())
|
|
return true;
|
|
|
|
if (pre)
|
|
{
|
|
diff_category category = NO_CHANGE_CATEGORY;
|
|
decl_base_sptr f = is_decl(d->first_subject()),
|
|
s = is_decl(d->second_subject());
|
|
|
|
// Detect size or offset changes as well as data member addition
|
|
// or removal.
|
|
//
|
|
// TODO: be more specific -- not all size changes are harmful.
|
|
if (!has_class_decl_only_def_change(d)
|
|
&& !has_enum_decl_only_def_change(d)
|
|
&& (type_size_changed(f, s)
|
|
|| data_member_offset_changed(f, s)
|
|
|| non_static_data_member_type_size_changed(f, s)
|
|
|| non_static_data_member_added_or_removed(d)
|
|
|| base_classes_added_or_removed(d)
|
|
|| has_harmful_enum_change(d)
|
|
|| crc_changed(d)
|
|
|| namespace_changed(d)))
|
|
category |= SIZE_OR_OFFSET_CHANGE_CATEGORY;
|
|
|
|
if (has_virtual_mem_fn_change(d))
|
|
category |= VIRTUAL_MEMBER_CHANGE_CATEGORY;
|
|
|
|
if (has_added_or_removed_function_parameters(d))
|
|
category |= FN_PARM_ADD_REMOVE_CHANGE_CATEGORY;
|
|
|
|
if (category)
|
|
{
|
|
d->add_to_local_and_inherited_categories(category);
|
|
// Update the category of the canonical diff node too.
|
|
if (diff * canonical = d->get_canonical_diff())
|
|
canonical->add_to_local_and_inherited_categories(category);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/// The visiting code of the harmless_harmful_filter.
|
|
///
|
|
/// @param d the diff node being visited.
|
|
///
|
|
/// @param pre this is true iff the node is being visited *before* the
|
|
/// children nodes of @p d.
|
|
///
|
|
/// @return true iff the traversal shall keep going after the
|
|
/// completion of this function.
|
|
bool
|
|
harmless_harmful_filter::visit(diff* d, bool pre)
|
|
{
|
|
return (categorize_harmless_diff_node(d, pre)
|
|
&& categorize_harmful_diff_node(d, pre));
|
|
}
|
|
|
|
/// Part of the visiting code of the harmless_harmful_filter.
|
|
///
|
|
/// This function is called after the visiting of a given diff node.
|
|
/// Note that when this function is called, the visiting might not
|
|
/// have taken place *if* the node (or an equivalent node) has already
|
|
/// been visited.
|
|
///
|
|
/// @param d the diff node that has either been visited or skipped
|
|
/// (because it has already been visited during this traversing).
|
|
void
|
|
harmless_harmful_filter::visit_end(diff* d)
|
|
{
|
|
if (d->context()->diff_has_been_visited(d))
|
|
{
|
|
// This node or one of its equivalent node has already been
|
|
// visited. That means at this moment,
|
|
// harmless_harmful_filter::visit() has *not* been called prior
|
|
// to this harmless_harmful_filter::visit_end() is called. In
|
|
// other words, only harmless_harmful_filter::visit_begin() and
|
|
// harmless_harmful_filter::visit_end() are called.
|
|
//
|
|
// So let's update the category of this diff node from its
|
|
// canonical node.
|
|
if (diff* c = d->get_canonical_diff())
|
|
d->add_to_local_and_inherited_categories(c->get_local_category());
|
|
}
|
|
}
|
|
} // end namespace filtering
|
|
} // end namespace comparison
|
|
} // end namespace abigail
|