abicompat: Add the ability to process abi corpus group

Message ID 20250807151822.107760-1-claudiu.zissulescu-ianculescu@oracle.com
State New
Headers
Series abicompat: Add the ability to process abi corpus group |

Commit Message

Claudiu Zissulescu Aug. 7, 2025, 3:17 p.m. UTC
  From: Claudiu Zissulescu <claudiu.zissulescu-ianculescu@oracle.com>

This patch introduces a new option `--soname` to the `abicompat` tool.
This new option allows users to specify a shared library name when
comparing ABI XML files that contain groups of corpora. This is useful
when dealing with multiple shared libraries that have their ABIs
stored within a single XML file.

This patch also adds a new test case for this functionality, and
updates the documentation.
---
 big-tests                                     |  2 +-
 doc/manuals/abicompat.rst                     |  8 ++
 .../test-abicompat/libtest10-fn-changed.xml   | 86 +++++++++++++++++++
 .../test-abicompat/test10-fn-changed-app.xml  | 45 ++++++++++
 .../data/test-abicompat/test10-fn-changed.txt | 15 ++++
 tests/test-abicompat.cc                       | 10 +++
 tools/abicompat.cc                            | 41 ++++++++-
 7 files changed, 205 insertions(+), 2 deletions(-)
 create mode 100644 tests/data/test-abicompat/libtest10-fn-changed.xml
 create mode 100644 tests/data/test-abicompat/test10-fn-changed-app.xml
 create mode 100644 tests/data/test-abicompat/test10-fn-changed.txt
  

Comments

Dodji Seketeli Aug. 21, 2025, 4:40 p.m. UTC | #1
Hello Claudiu,

claudiu.zissulescu-ianculescu@oracle.com a écrit:

> From: Claudiu Zissulescu <claudiu.zissulescu-ianculescu@oracle.com>
>
> This patch introduces a new option `--soname` to the `abicompat` tool.
> This new option allows users to specify a shared library name when
> comparing ABI XML files that contain groups of corpora. This is useful
> when dealing with multiple shared libraries that have their ABIs
> stored within a single XML file.

Thank you for taking the time to craft this patch.

Fundamentally, I think the reason why you've needed to do this is
because I fell short in making a corpus group be a "particular" kind of
corpus, even if that was the initial intent behind making the
abigail::ir::corpus_group class inheritabigail::ir::corpus.

Please bear with me for a few paragraphs.

To perform compatibility verifications,
perform_compat_check_in_weak_mode compares each function F (respectively
variable V) expected by the application against the function F exported
by the library.  This is done by invoking corpus::lookup_functions on
the ABI corpus of the library, for each F.

The core problem we face is that corpus_group::lookup_functions and
corpus_group::lookup_variables hasn't been implemented yet, making it
cumbersome to handle a (set of) library represented by a corpus_group.

And this has compelled you to walking the corpora of a the corpus_group
of the library "by hand" in tools/abicompat.cc and needing the user to
specify the soname of the particular corpus (in the corpus group) to
consider as a provider for F.  Tedious.

I think you shouldn't have to do that and the user shouldn't be forced
to know about that soname detail.

Rather, I am amending the patch to make it do what I should have done to
begin with, which is, provide
corpus_group::lookup_{functions,variables}, so that once the
corpus_group (for the library) is constructed,
perform_compat_check_in_weak_mode just works on it as it works on any
other corpus.  I kept everything else as you did.

Please find the amended patch below.  You can get it in-extenso from git
in the branch "users/dodji/abicompat-corpus-group" at
https://sourceware.org/cgit/libabigail/log/?h=users/dodji/abicompat-corpus-group.

What do you think?

Many thanks.

From 22bb528d00a457fb0ff601a4adca22d8d3338a35 Mon Sep 17 00:00:00 2001
From: Claudiu Zissulescu <claudiu.zissulescu-ianculescu@oracle.com>
Date: Thu, 7 Aug 2025 18:17:57 +0300
Subject: [PATCH] abicompat: Add the ability to process abi corpus group

abicompat doesn't seem to support libraries being represented by
corpus group abixml files.

This patch fixes that.

First, the patch makes abigail::ir::corpus::lookup_{functions,variables} be
virtual member functions.  Then, it adds
abigail::ir::corpus_group::lookup_{functions,variables} overloads to the
abigail::ir::corpus_group child class of abigail::ir::corpus.

Basically, abigail::corpus_group::lookup_functions cycles over the
corpora of the group and invokes abigail::corpus::lookup_functions on
each corpus until it finds a corpus that has the functions. Similarly
for abigail::corpus_group::lookup_variables.

The function read_corpus in abicompat.cc is amended to support corpus
group ABIXML files by calling read_corpus_group_from_input on them.
Because a corpus_group is a particular type of corpus, the rest of the
code should now do the right thing.

The patch also adds a new test case for this functionality.

	* include/abg-corpus.h (corpus::lookup_{function,variable}s):
	Make these member functions virtual.
	(corpus_group::{lookup_{function,variable}s): Add new overloads.
	* src/abg-corpus.cc (corpus_group::{lookup_{function,variable}s):
	Define new virtual overloads that walk over each corpus, invokes
	corpus::lookup_{functions,variables} on them and return the first
	one that is non-nil.
	* tests/data/test-abicompat/libtest11-fn-changed.xml: New test input.
	* tests/data/test-abicompat/test11-fn-changed-app.xml: Likewise.
	* tests/data/test-abicompat/test11-fn-changed.txt: Likewise.
	* tests/test-abicompat.cc (in_out_specs): Add the new test
	material above to this test harness.
	* tools/abicompat.cc (read_corpus): Support
	abigail::tools_utils::FILE_TYPE_XML_CORPUS_GROUP kind of input file.

Signed-off-by: Dodji Seketeli <dodji@redhat.com>
---
 include/abg-corpus.h                          | 20 +++-
 src/abg-corpus.cc                             | 96 +++++++++++++++++++
 tests/data/Makefile.am                        |  3 +
 .../test-abicompat/libtest11-fn-changed.xml   | 86 +++++++++++++++++
 .../test-abicompat/test11-fn-changed-app.xml  | 45 +++++++++
 .../data/test-abicompat/test11-fn-changed.txt | 14 +++
 tests/test-abicompat.cc                       | 10 ++
 tools/abicompat.cc                            |  7 +-
 8 files changed, 275 insertions(+), 6 deletions(-)
 create mode 100644 tests/data/test-abicompat/libtest11-fn-changed.xml
 create mode 100644 tests/data/test-abicompat/test11-fn-changed-app.xml
 create mode 100644 tests/data/test-abicompat/test11-fn-changed.txt

diff --git a/include/abg-corpus.h b/include/abg-corpus.h
index 62121543..0a7cf165 100644
--- a/include/abg-corpus.h
+++ b/include/abg-corpus.h
@@ -224,16 +224,16 @@ public:
   virtual const functions&
   get_functions() const;
 
-  const std::unordered_set<function_decl*>*
+  virtual const std::unordered_set<function_decl*>*
   lookup_functions(const interned_string& id) const;
 
-  const std::unordered_set<function_decl*>*
+  virtual const std::unordered_set<function_decl*>*
   lookup_functions(const char* id) const;
 
-  const std::unordered_set<var_decl_sptr>*
+  virtual const std::unordered_set<var_decl_sptr>*
   lookup_variables(const interned_string& id) const;
 
-  const std::unordered_set<var_decl_sptr>*
+  virtual const std::unordered_set<var_decl_sptr>*
   lookup_variables(const char* id) const;
 
   void
@@ -445,6 +445,18 @@ public:
 
   bool
   operator==(const corpus_group&) const;
+
+  virtual const std::unordered_set<function_decl*>*
+  lookup_functions(const interned_string& id) const;
+
+  virtual const std::unordered_set<function_decl*>*
+  lookup_functions(const char* id) const;
+
+  virtual const std::unordered_set<var_decl_sptr>*
+  lookup_variables(const interned_string& id) const;
+
+  virtual const std::unordered_set<var_decl_sptr>*
+  lookup_variables(const char* id) const;
 }; // end class corpus_group
 
 corpus_group_sptr
diff --git a/src/abg-corpus.cc b/src/abg-corpus.cc
index 8d5850e8..a53ae308 100644
--- a/src/abg-corpus.cc
+++ b/src/abg-corpus.cc
@@ -2267,6 +2267,102 @@ bool
 corpus_group::recording_types_reachable_from_public_interface_supported()
 {return !get_public_types_pretty_representations()->empty();}
 
+/// Lookup the function which has a given function ID.
+///
+/// Note that there can have been several functions with the same ID.
+/// This is because debug info can declare the same function in
+/// several different translation units.  Normally, all these
+/// functions should be equal.  But still, this function returns all
+/// these functions.
+///
+/// Also, note that this function cycles over each corpora of the
+/// corpus group, invokes corpus::lookup_functions on them and returns
+/// the result of the first one that succeeds.
+///
+/// @param id the ID of the function to lookup.  This ID must be
+/// either the result of invoking function::get_id() of
+/// elf_symbol::get_id_string().
+///
+/// @return the set of functions which ID is @p id, or nil if no
+/// function with that ID was found.
+const std::unordered_set<function_decl*>*
+corpus_group::lookup_functions(const interned_string& id) const
+{
+  for (auto& corp :get_corpora())
+    if (auto fns = corp->lookup_functions(id))
+      return fns;
+
+  return nullptr;
+}
+
+/// Lookup the function which has a given function ID.
+///
+/// Note that there can have been several functions with the same ID.
+/// This is because debug info can declare the same function in
+/// several different translation units.  Normally, all these
+/// functions should be equal.  But still, this function returns all
+/// these functions.
+///
+/// Also, note that this function cycles over each corpora of the
+/// corpus group, invokes corpus::lookup_functions on them and returns
+/// the result of the first one that succeeds.
+///
+/// @param id the ID of the function to lookup.  This ID must be
+/// either the result of invoking function::get_id() of
+/// elf_symbol::get_id_string().
+///
+/// @return the set of functions which ID is @p id, or nil if no
+/// function with that ID was found.
+const std::unordered_set<function_decl*>*
+corpus_group::lookup_functions(const char* id) const
+{
+  for (auto& corp :get_corpora())
+    if (const auto& fns = corp->lookup_functions(id))
+      return fns;
+
+  return nullptr;
+}
+
+/// Lookup the exported variables which all have a given variable ID.
+///
+/// Note that this function cycles over each corpora of the corpus
+/// group, invokes corpus::lookup_variabless on them and returns the
+/// result of the first one that succeeds.
+///
+/// @param id the ID of the variable to look up.
+///
+/// @return a pointer to the set of variables with ID @p id, or
+/// nullptr if no variable was found with that ID.
+const std::unordered_set<var_decl_sptr>*
+corpus_group::lookup_variables(const interned_string& id) const
+{
+  for (auto& corp :get_corpora())
+    if (const auto& vars = corp->lookup_variables(id))
+      return vars;
+
+  return nullptr;
+}
+
+/// Lookup the exported variables which all have a given variable ID.
+///
+/// Note that this function cycles over each corpora of the corpus
+/// group, invokes corpus::lookup_variabless on them and returns the
+/// result of the first one that succeeds.
+///
+/// @param id the ID of the variable to look up.
+///
+/// @return a pointer to the set of variables with ID @p id, or
+/// nullptr if no variable was found with that ID.
+const std::unordered_set<var_decl_sptr>*
+corpus_group::lookup_variables(const char* id) const
+{
+  for (auto& corp :get_corpora())
+    if (const auto& vars = corp->lookup_variables(id))
+      return vars;
+
+  return nullptr;
+}
+
 /// Test if a @ref corpus is a @ref corpus_group.
 ///
 /// @param corpus the corpus to consider.
diff --git a/tests/data/Makefile.am b/tests/data/Makefile.am
index 6c4e2cd2..dc1c4f12 100644
--- a/tests/data/Makefile.am
+++ b/tests/data/Makefile.am
@@ -2280,6 +2280,9 @@ test-abicompat/test10/test10-fn-changed-report-1.txt \
 test-abicompat/test10/test10-fn-changed-report-2.txt \
 test-abicompat/test10/test10-fn-changed-report-3.txt \
 test-abicompat/test10/test10-fn-changed-report-4.txt \
+test-abicompat/libtest11-fn-changed.xml \
+test-abicompat/test11-fn-changed-app.xml \
+test-abicompat/test11-fn-changed.txt \
 test-abicompat/test-diff-ptr-to-void-ptr/0/libtest-diff-ptr-to-void-ptr-fn.so \
 test-abicompat/test-diff-ptr-to-void-ptr/0/test-diff-ptr-to-void-ptr-fn-v0.c \
 test-abicompat/test-diff-ptr-to-void-ptr/1/libtest-diff-ptr-to-void-ptr-fn.so \
diff --git a/tests/data/test-abicompat/libtest11-fn-changed.xml b/tests/data/test-abicompat/libtest11-fn-changed.xml
new file mode 100644
index 00000000..097af2af
--- /dev/null
+++ b/tests/data/test-abicompat/libtest11-fn-changed.xml
@@ -0,0 +1,86 @@
+<abi-corpus-group version='2.4' architecture='elf-amd-x86_64'>
+  <abi-corpus version='2.4' path='libtest-1.so' architecture='elf-amd-x86_64' soname='libtest-1.so'>
+    <elf-needed>
+      <dependency name='libstdc++.so.6'/>
+      <dependency name='libm.so.6'/>
+      <dependency name='libgcc_s.so.1'/>
+      <dependency name='libc.so.6'/>
+    </elf-needed>
+    <elf-function-symbols>
+      <elf-symbol name='_Z10first_funcv' type='func-type' binding='global-binding' visibility='default-visibility' is-defined='yes'/>
+      <elf-symbol name='_Z11second_funcR3foo' type='func-type' binding='global-binding' visibility='default-visibility' is-defined='yes'/>
+      <elf-symbol name='_ZN3fooC1Ev' type='func-type' binding='weak-binding' visibility='default-visibility' is-defined='yes'/>
+      <elf-symbol name='_ZN3fooC2Ev' type='func-type' binding='weak-binding' visibility='default-visibility' alias='_ZN3fooC1Ev' is-defined='yes'/>
+    </elf-function-symbols>
+    <undefined-elf-function-symbols>
+      <elf-symbol name='_ZdlPvm' version='CXXABI_1.3.9' is-default-version='yes' type='func-type' binding='global-binding' visibility='default-visibility' is-defined='no'/>
+      <elf-symbol name='_Znwm' type='func-type' binding='global-binding' visibility='default-visibility' is-defined='no'/>
+      <elf-symbol name='__cxa_finalize' type='func-type' binding='weak-binding' visibility='default-visibility' is-defined='no'/>
+    </undefined-elf-function-symbols>
+    <undefined-elf-variable-symbols>
+      <elf-symbol name='_ITM_deregisterTMCloneTable' type='no-type' binding='weak-binding' visibility='default-visibility' is-defined='no'/>
+      <elf-symbol name='_ITM_registerTMCloneTable' type='no-type' binding='weak-binding' visibility='default-visibility' is-defined='no'/>
+      <elf-symbol name='__gmon_start__' type='no-type' binding='weak-binding' visibility='default-visibility' is-defined='no'/>
+    </undefined-elf-variable-symbols>
+    <abi-instr address-size='64' path='test1.cc' comp-dir-path='/home/cls/Projects/oracle/libabigail_work/BinLibCheck/simpleExample' language='LANG_C_plus_plus_14'>
+      <type-decl name='char' size-in-bits='8' hash='65b2d157027b431a' id='type-id-1'/>
+      <type-decl name='int' size-in-bits='32' hash='09d17c08f594edc7' id='type-id-2'/>
+      <class-decl name='foo' is-struct='yes' visibility='default' size-in-bits='64' filepath='/home/cls/Projects/oracle/libabigail_work/BinLibCheck/simpleExample/test1.h' line='1' column='1' hash='b9bca352028d2431' id='type-id-3'>
+        <data-member access='public' layout-offset-in-bits='0'>
+          <var-decl name='m0' type-id='type-id-2' visibility='default' filepath='/home/cls/Projects/oracle/libabigail_work/BinLibCheck/simpleExample/test1.h' line='3' column='1'/>
+        </data-member>
+        <data-member access='public' layout-offset-in-bits='32'>
+          <var-decl name='m1' type-id='type-id-1' visibility='default' filepath='/home/cls/Projects/oracle/libabigail_work/BinLibCheck/simpleExample/test1.h' line='4' column='1'/>
+        </data-member>
+        <member-function access='public' constructor='yes'>
+          <function-decl name='foo' mangled-name='_ZN3fooC2Ev' filepath='/home/cls/Projects/oracle/libabigail_work/BinLibCheck/simpleExample/test1.h' line='6' column='1' declared-inline='yes' visibility='default' binding='global' size-in-bits='64' elf-symbol-id='_ZN3fooC2Ev' hash='7f32ffea222edbe7'>
+            <parameter type-id='type-id-4' is-artificial='yes'/>
+            <return type-id='type-id-5'/>
+          </function-decl>
+        </member-function>
+      </class-decl>
+      <reference-type-def kind='lvalue' type-id='type-id-3' size-in-bits='64' hash='1854f0b6950169b7' id='type-id-6'/>
+      <pointer-type-def type-id='type-id-3' size-in-bits='64' hash='dc66dea208456f85' id='type-id-4'/>
+      <qualified-type-def type-id='type-id-4' const='yes' hash='0ee8cf1f479fe91e' id='type-id-7'/>
+      <function-decl name='first_func' mangled-name='_Z10first_funcv' filepath='/home/cls/Projects/oracle/libabigail_work/BinLibCheck/simpleExample/test1.cc' line='7' column='1' visibility='default' binding='global' size-in-bits='64' elf-symbol-id='_Z10first_funcv' hash='fb51336c1fc02347'>
+        <return type-id='type-id-4'/>
+      </function-decl>
+      <function-decl name='second_func' mangled-name='_Z11second_funcR3foo' filepath='/home/cls/Projects/oracle/libabigail_work/BinLibCheck/simpleExample/test1.cc' line='14' column='1' visibility='default' binding='global' size-in-bits='64' elf-symbol-id='_Z11second_funcR3foo' hash='01ba306f4098d54e'>
+        <parameter type-id='type-id-6'/>
+        <return type-id='type-id-5'/>
+      </function-decl>
+      <type-decl name='void' id='type-id-5'/>
+    </abi-instr>
+  </abi-corpus>
+  <abi-corpus version='2.4' path='libtest-2.so' architecture='elf-amd-x86_64' soname='libtest-2.so'>
+    <elf-needed>
+      <dependency name='libstdc++.so.6'/>
+      <dependency name='libm.so.6'/>
+      <dependency name='libgcc_s.so.1'/>
+      <dependency name='libc.so.6'/>
+    </elf-needed>
+    <elf-function-symbols>
+      <elf-symbol name='_Z3fooP2S0' type='func-type' binding='global-binding' visibility='default-visibility' is-defined='yes'/>
+    </elf-function-symbols>
+    <undefined-elf-function-symbols>
+      <elf-symbol name='__cxa_finalize' version='GLIBC_2.2.5' is-default-version='yes' type='func-type' binding='weak-binding' visibility='default-visibility' is-defined='no'/>
+    </undefined-elf-function-symbols>
+    <undefined-elf-variable-symbols>
+      <elf-symbol name='_ITM_deregisterTMCloneTable' type='no-type' binding='weak-binding' visibility='default-visibility' is-defined='no'/>
+      <elf-symbol name='_ITM_registerTMCloneTable' type='no-type' binding='weak-binding' visibility='default-visibility' is-defined='no'/>
+      <elf-symbol name='__gmon_start__' type='no-type' binding='weak-binding' visibility='default-visibility' is-defined='no'/>
+    </undefined-elf-variable-symbols>
+    <abi-instr address-size='64' path='test2.cc' comp-dir-path='/home/cls/Projects/oracle/libabigail_work/BinLibCheck/simpleExample' language='LANG_C_plus_plus_14'>
+      <class-decl name='S0' is-struct='yes' visibility='default' size-in-bits='32' filepath='/home/cls/Projects/oracle/libabigail_work/BinLibCheck/simpleExample/test2.cc' line='4' column='1' hash='55ff770e9a972727' id='type-id-8'>
+        <data-member access='public' layout-offset-in-bits='0'>
+          <var-decl name='m0' type-id='type-id-2' visibility='default' filepath='/home/cls/Projects/oracle/libabigail_work/BinLibCheck/simpleExample/test2.cc' line='6' column='1'/>
+        </data-member>
+      </class-decl>
+      <pointer-type-def type-id='type-id-8' size-in-bits='64' hash='c816e79b49fbd0b0' id='type-id-9'/>
+      <function-decl name='foo' mangled-name='_Z3fooP2S0' filepath='/home/cls/Projects/oracle/libabigail_work/BinLibCheck/simpleExample/test2.cc' line='10' column='1' visibility='default' binding='global' size-in-bits='64' elf-symbol-id='_Z3fooP2S0' hash='a6fe781ceb581b24'>
+        <parameter type-id='type-id-9'/>
+        <return type-id='type-id-5'/>
+      </function-decl>
+    </abi-instr>
+  </abi-corpus>
+</abi-corpus-group>
diff --git a/tests/data/test-abicompat/test11-fn-changed-app.xml b/tests/data/test-abicompat/test11-fn-changed-app.xml
new file mode 100644
index 00000000..8e5b2728
--- /dev/null
+++ b/tests/data/test-abicompat/test11-fn-changed-app.xml
@@ -0,0 +1,45 @@
+<abi-corpus version='2.4' path='test-app' architecture='elf-amd-x86_64'>
+  <elf-needed>
+    <dependency name='libtest-0.so'/>
+    <dependency name='libstdc++.so.6'/>
+    <dependency name='libm.so.6'/>
+    <dependency name='libgcc_s.so.1'/>
+    <dependency name='libc.so.6'/>
+  </elf-needed>
+  <elf-function-symbols>
+    <elf-symbol name='_start' type='func-type' binding='global-binding' visibility='default-visibility' is-defined='yes'/>
+    <elf-symbol name='main' type='func-type' binding='global-binding' visibility='default-visibility' is-defined='yes'/>
+  </elf-function-symbols>
+  <elf-variable-symbols>
+    <elf-symbol name='_IO_stdin_used' size='4' type='object-type' binding='global-binding' visibility='default-visibility' is-defined='yes'/>
+  </elf-variable-symbols>
+  <undefined-elf-function-symbols>
+    <elf-symbol name='_Z10first_funcv' type='func-type' binding='global-binding' visibility='default-visibility' is-defined='no'/>
+    <elf-symbol name='_Z11second_funcR3foo' type='func-type' binding='global-binding' visibility='default-visibility' is-defined='no'/>
+    <elf-symbol name='__libc_start_main@GLIBC_2.34' type='func-type' binding='global-binding' visibility='default-visibility' is-defined='no'/>
+  </undefined-elf-function-symbols>
+  <undefined-elf-variable-symbols>
+    <elf-symbol name='__gmon_start__' type='no-type' binding='weak-binding' visibility='default-visibility' is-defined='no'/>
+  </undefined-elf-variable-symbols>
+  <abi-instr address-size='64' path='test-app.cc' comp-dir-path='/home/cls/Projects/oracle/libabigail_work/BinLibCheck/simpleExample' language='LANG_C_plus_plus_14'>
+    <type-decl name='int' size-in-bits='32' hash='09d17c08f594edc7' id='type-id-1'/>
+    <class-decl name='foo' is-struct='yes' visibility='default' size-in-bits='32' filepath='/home/cls/Projects/oracle/libabigail_work/BinLibCheck/simpleExample/test0.h' line='1' column='1' hash='c994494c47450411' id='type-id-2'>
+      <data-member access='public' layout-offset-in-bits='0'>
+        <var-decl name='m0' type-id='type-id-1' visibility='default' filepath='/home/cls/Projects/oracle/libabigail_work/BinLibCheck/simpleExample/test0.h' line='3' column='1'/>
+      </data-member>
+    </class-decl>
+    <reference-type-def kind='lvalue' type-id='type-id-2' size-in-bits='64' hash='1854f0b6950169b7' id='type-id-3'/>
+    <pointer-type-def type-id='type-id-2' size-in-bits='64' hash='dc66dea208456f85' id='type-id-4'/>
+    <function-decl name='main' mangled-name='main' filepath='/home/cls/Projects/oracle/libabigail_work/BinLibCheck/simpleExample/test-app.cc' line='7' column='1' visibility='default' binding='global' size-in-bits='64' elf-symbol-id='main' hash='388da3fa973fde78'>
+      <return type-id='type-id-1'/>
+    </function-decl>
+    <function-decl name='first_func' mangled-name='_Z10first_funcv' filepath='/home/cls/Projects/oracle/libabigail_work/BinLibCheck/simpleExample/test0.h' line='11' column='1' visibility='default' binding='global' size-in-bits='64' elf-symbol-id='_Z10first_funcv' hash='fb51336c1fc02347'>
+      <return type-id='type-id-4'/>
+    </function-decl>
+    <function-decl name='second_func' mangled-name='_Z11second_funcR3foo' filepath='/home/cls/Projects/oracle/libabigail_work/BinLibCheck/simpleExample/test0.h' line='14' column='1' visibility='default' binding='global' size-in-bits='64' elf-symbol-id='_Z11second_funcR3foo' hash='01ba306f4098d54e'>
+      <parameter type-id='type-id-3'/>
+      <return type-id='type-id-5'/>
+    </function-decl>
+    <type-decl name='void' id='type-id-5'/>
+  </abi-instr>
+</abi-corpus>
diff --git a/tests/data/test-abicompat/test11-fn-changed.txt b/tests/data/test-abicompat/test11-fn-changed.txt
new file mode 100644
index 00000000..2fade2df
--- /dev/null
+++ b/tests/data/test-abicompat/test11-fn-changed.txt
@@ -0,0 +1,14 @@
+functions defined in library 'libtest11-fn-changed.xml'
+have sub-types that are different from what application 'test11-fn-changed-app.xml' expects:
+
+  function foo* first_func(void):
+    return type changed:
+      in pointed to type 'struct foo' at test1.h:1:1:
+        type size changed from 32 to 64 (in bits)
+        1 data member insertion:
+          'char m1', at offset 32 (in bits) at test1.h:4:1
+
+  function void second_func(foo&):
+    parameter 1 of type 'foo&' has sub-type changes:
+      referenced type 'struct foo' changed at test0.h:1:1, as reported earlier
+
diff --git a/tests/test-abicompat.cc b/tests/test-abicompat.cc
index 051cfba4..ca97884a 100644
--- a/tests/test-abicompat.cc
+++ b/tests/test-abicompat.cc
@@ -334,6 +334,16 @@ InOutSpec in_out_specs[] =
     "data/test-abicompat/test10/test10-fn-changed-report-4.txt",
     "output/test-abicompat/test10/test10-fn-changed-report-4.txt",
   },
+  {
+    "data/test-abicompat/test11-fn-changed-app.xml",
+    "data/test-abicompat/libtest11-fn-changed.xml",
+    "",
+    "",
+    "--show-base-names --weak-mode",
+    abigail::tools_utils::ABIDIFF_ABI_CHANGE,
+    "data/test-abicompat/test11-fn-changed.txt",
+    "output/test-abicompat/test11-fn-changed.txt",
+  },
   // This entry must be the last one.
   {0, 0, 0, 0, 0, abigail::tools_utils::ABIDIFF_OK, 0, 0}
 };
diff --git a/tools/abicompat.cc b/tools/abicompat.cc
index b3e556dd..820ea129 100644
--- a/tools/abicompat.cc
+++ b/tools/abicompat.cc
@@ -881,14 +881,17 @@ read_corpus(options			opts,
       }
       break;
     case abigail::tools_utils::FILE_TYPE_XML_CORPUS:
+    case abigail::tools_utils::FILE_TYPE_XML_CORPUS_GROUP:
       {
 	rdr = abixml::create_reader(path, env);
 	assert(rdr);
-	retval = rdr->read_corpus(status);
+	retval =
+	  (type == abigail::tools_utils::FILE_TYPE_XML_CORPUS_GROUP)
+	  ? abixml::read_corpus_group_from_input(*rdr)
+	  : rdr->read_corpus(status);
       }
       break;
     case abigail::tools_utils::FILE_TYPE_AR:
-    case abigail::tools_utils::FILE_TYPE_XML_CORPUS_GROUP:
     case abigail::tools_utils::FILE_TYPE_RPM:
     case abigail::tools_utils::FILE_TYPE_SRPM:
     case abigail::tools_utils::FILE_TYPE_DEB:
  
Claudiu Zissulescu Aug. 22, 2025, 12:45 p.m. UTC | #2
Hi Dodji,

Indeed your patch is more neat than mine, removing the extra work of
checking multiple times (in my case) the courpus group when having
multipe so.

IMHO looks very good your patch,
Claudiu

On 8/21/25 7:40 PM, Dodji Seketeli wrote:
> Hello Claudiu,
> 
> claudiu.zissulescu-ianculescu@oracle.com a écrit:
> 
>> From: Claudiu Zissulescu <claudiu.zissulescu-ianculescu@oracle.com>
>>
>> This patch introduces a new option `--soname` to the `abicompat` tool.
>> This new option allows users to specify a shared library name when
>> comparing ABI XML files that contain groups of corpora. This is useful
>> when dealing with multiple shared libraries that have their ABIs
>> stored within a single XML file.
> 
> Thank you for taking the time to craft this patch.
> 
> Fundamentally, I think the reason why you've needed to do this is
> because I fell short in making a corpus group be a "particular" kind of
> corpus, even if that was the initial intent behind making the
> abigail::ir::corpus_group class inheritabigail::ir::corpus.
> 
> Please bear with me for a few paragraphs.
> 
> To perform compatibility verifications,
> perform_compat_check_in_weak_mode compares each function F (respectively
> variable V) expected by the application against the function F exported
> by the library.  This is done by invoking corpus::lookup_functions on
> the ABI corpus of the library, for each F.
> 
> The core problem we face is that corpus_group::lookup_functions and
> corpus_group::lookup_variables hasn't been implemented yet, making it
> cumbersome to handle a (set of) library represented by a corpus_group.
> 
> And this has compelled you to walking the corpora of a the corpus_group
> of the library "by hand" in tools/abicompat.cc and needing the user to
> specify the soname of the particular corpus (in the corpus group) to
> consider as a provider for F.  Tedious.
> 
> I think you shouldn't have to do that and the user shouldn't be forced
> to know about that soname detail.
> 
> Rather, I am amending the patch to make it do what I should have done to
> begin with, which is, provide
> corpus_group::lookup_{functions,variables}, so that once the
> corpus_group (for the library) is constructed,
> perform_compat_check_in_weak_mode just works on it as it works on any
> other corpus.  I kept everything else as you did.
> 
> Please find the amended patch below.  You can get it in-extenso from git
> in the branch "users/dodji/abicompat-corpus-group" at
> https://sourceware.org/cgit/libabigail/log/?h=users/dodji/abicompat-corpus-group.
> 
> What do you think?
> 
> Many thanks.
> 
> From 22bb528d00a457fb0ff601a4adca22d8d3338a35 Mon Sep 17 00:00:00 2001
> From: Claudiu Zissulescu <claudiu.zissulescu-ianculescu@oracle.com>
> Date: Thu, 7 Aug 2025 18:17:57 +0300
> Subject: [PATCH] abicompat: Add the ability to process abi corpus group
> 
> abicompat doesn't seem to support libraries being represented by
> corpus group abixml files.
> 
> This patch fixes that.
> 
> First, the patch makes abigail::ir::corpus::lookup_{functions,variables} be
> virtual member functions.  Then, it adds
> abigail::ir::corpus_group::lookup_{functions,variables} overloads to the
> abigail::ir::corpus_group child class of abigail::ir::corpus.
> 
> Basically, abigail::corpus_group::lookup_functions cycles over the
> corpora of the group and invokes abigail::corpus::lookup_functions on
> each corpus until it finds a corpus that has the functions. Similarly
> for abigail::corpus_group::lookup_variables.
> 
> The function read_corpus in abicompat.cc is amended to support corpus
> group ABIXML files by calling read_corpus_group_from_input on them.
> Because a corpus_group is a particular type of corpus, the rest of the
> code should now do the right thing.
> 
> The patch also adds a new test case for this functionality.
> 
> 	* include/abg-corpus.h (corpus::lookup_{function,variable}s):
> 	Make these member functions virtual.
> 	(corpus_group::{lookup_{function,variable}s): Add new overloads.
> 	* src/abg-corpus.cc (corpus_group::{lookup_{function,variable}s):
> 	Define new virtual overloads that walk over each corpus, invokes
> 	corpus::lookup_{functions,variables} on them and return the first
> 	one that is non-nil.
> 	* tests/data/test-abicompat/libtest11-fn-changed.xml: New test input.
> 	* tests/data/test-abicompat/test11-fn-changed-app.xml: Likewise.
> 	* tests/data/test-abicompat/test11-fn-changed.txt: Likewise.
> 	* tests/test-abicompat.cc (in_out_specs): Add the new test
> 	material above to this test harness.
> 	* tools/abicompat.cc (read_corpus): Support
> 	abigail::tools_utils::FILE_TYPE_XML_CORPUS_GROUP kind of input file.
> 
> Signed-off-by: Dodji Seketeli <dodji@redhat.com>
> ---
>  include/abg-corpus.h                          | 20 +++-
>  src/abg-corpus.cc                             | 96 +++++++++++++++++++
>  tests/data/Makefile.am                        |  3 +
>  .../test-abicompat/libtest11-fn-changed.xml   | 86 +++++++++++++++++
>  .../test-abicompat/test11-fn-changed-app.xml  | 45 +++++++++
>  .../data/test-abicompat/test11-fn-changed.txt | 14 +++
>  tests/test-abicompat.cc                       | 10 ++
>  tools/abicompat.cc                            |  7 +-
>  8 files changed, 275 insertions(+), 6 deletions(-)
>  create mode 100644 tests/data/test-abicompat/libtest11-fn-changed.xml
>  create mode 100644 tests/data/test-abicompat/test11-fn-changed-app.xml
>  create mode 100644 tests/data/test-abicompat/test11-fn-changed.txt
> 
> diff --git a/include/abg-corpus.h b/include/abg-corpus.h
> index 62121543..0a7cf165 100644
> --- a/include/abg-corpus.h
> +++ b/include/abg-corpus.h
> @@ -224,16 +224,16 @@ public:
>    virtual const functions&
>    get_functions() const;
>  
> -  const std::unordered_set<function_decl*>*
> +  virtual const std::unordered_set<function_decl*>*
>    lookup_functions(const interned_string& id) const;
>  
> -  const std::unordered_set<function_decl*>*
> +  virtual const std::unordered_set<function_decl*>*
>    lookup_functions(const char* id) const;
>  
> -  const std::unordered_set<var_decl_sptr>*
> +  virtual const std::unordered_set<var_decl_sptr>*
>    lookup_variables(const interned_string& id) const;
>  
> -  const std::unordered_set<var_decl_sptr>*
> +  virtual const std::unordered_set<var_decl_sptr>*
>    lookup_variables(const char* id) const;
>  
>    void
> @@ -445,6 +445,18 @@ public:
>  
>    bool
>    operator==(const corpus_group&) const;
> +
> +  virtual const std::unordered_set<function_decl*>*
> +  lookup_functions(const interned_string& id) const;
> +
> +  virtual const std::unordered_set<function_decl*>*
> +  lookup_functions(const char* id) const;
> +
> +  virtual const std::unordered_set<var_decl_sptr>*
> +  lookup_variables(const interned_string& id) const;
> +
> +  virtual const std::unordered_set<var_decl_sptr>*
> +  lookup_variables(const char* id) const;
>  }; // end class corpus_group
>  
>  corpus_group_sptr
> diff --git a/src/abg-corpus.cc b/src/abg-corpus.cc
> index 8d5850e8..a53ae308 100644
> --- a/src/abg-corpus.cc
> +++ b/src/abg-corpus.cc
> @@ -2267,6 +2267,102 @@ bool
>  corpus_group::recording_types_reachable_from_public_interface_supported()
>  {return !get_public_types_pretty_representations()->empty();}
>  
> +/// Lookup the function which has a given function ID.
> +///
> +/// Note that there can have been several functions with the same ID.
> +/// This is because debug info can declare the same function in
> +/// several different translation units.  Normally, all these
> +/// functions should be equal.  But still, this function returns all
> +/// these functions.
> +///
> +/// Also, note that this function cycles over each corpora of the
> +/// corpus group, invokes corpus::lookup_functions on them and returns
> +/// the result of the first one that succeeds.
> +///
> +/// @param id the ID of the function to lookup.  This ID must be
> +/// either the result of invoking function::get_id() of
> +/// elf_symbol::get_id_string().
> +///
> +/// @return the set of functions which ID is @p id, or nil if no
> +/// function with that ID was found.
> +const std::unordered_set<function_decl*>*
> +corpus_group::lookup_functions(const interned_string& id) const
> +{
> +  for (auto& corp :get_corpora())
> +    if (auto fns = corp->lookup_functions(id))
> +      return fns;
> +
> +  return nullptr;
> +}
> +
> +/// Lookup the function which has a given function ID.
> +///
> +/// Note that there can have been several functions with the same ID.
> +/// This is because debug info can declare the same function in
> +/// several different translation units.  Normally, all these
> +/// functions should be equal.  But still, this function returns all
> +/// these functions.
> +///
> +/// Also, note that this function cycles over each corpora of the
> +/// corpus group, invokes corpus::lookup_functions on them and returns
> +/// the result of the first one that succeeds.
> +///
> +/// @param id the ID of the function to lookup.  This ID must be
> +/// either the result of invoking function::get_id() of
> +/// elf_symbol::get_id_string().
> +///
> +/// @return the set of functions which ID is @p id, or nil if no
> +/// function with that ID was found.
> +const std::unordered_set<function_decl*>*
> +corpus_group::lookup_functions(const char* id) const
> +{
> +  for (auto& corp :get_corpora())
> +    if (const auto& fns = corp->lookup_functions(id))
> +      return fns;
> +
> +  return nullptr;
> +}
> +
> +/// Lookup the exported variables which all have a given variable ID.
> +///
> +/// Note that this function cycles over each corpora of the corpus
> +/// group, invokes corpus::lookup_variabless on them and returns the
> +/// result of the first one that succeeds.
> +///
> +/// @param id the ID of the variable to look up.
> +///
> +/// @return a pointer to the set of variables with ID @p id, or
> +/// nullptr if no variable was found with that ID.
> +const std::unordered_set<var_decl_sptr>*
> +corpus_group::lookup_variables(const interned_string& id) const
> +{
> +  for (auto& corp :get_corpora())
> +    if (const auto& vars = corp->lookup_variables(id))
> +      return vars;
> +
> +  return nullptr;
> +}
> +
> +/// Lookup the exported variables which all have a given variable ID.
> +///
> +/// Note that this function cycles over each corpora of the corpus
> +/// group, invokes corpus::lookup_variabless on them and returns the
> +/// result of the first one that succeeds.
> +///
> +/// @param id the ID of the variable to look up.
> +///
> +/// @return a pointer to the set of variables with ID @p id, or
> +/// nullptr if no variable was found with that ID.
> +const std::unordered_set<var_decl_sptr>*
> +corpus_group::lookup_variables(const char* id) const
> +{
> +  for (auto& corp :get_corpora())
> +    if (const auto& vars = corp->lookup_variables(id))
> +      return vars;
> +
> +  return nullptr;
> +}
> +
>  /// Test if a @ref corpus is a @ref corpus_group.
>  ///
>  /// @param corpus the corpus to consider.
> diff --git a/tests/data/Makefile.am b/tests/data/Makefile.am
> index 6c4e2cd2..dc1c4f12 100644
> --- a/tests/data/Makefile.am
> +++ b/tests/data/Makefile.am
> @@ -2280,6 +2280,9 @@ test-abicompat/test10/test10-fn-changed-report-1.txt \
>  test-abicompat/test10/test10-fn-changed-report-2.txt \
>  test-abicompat/test10/test10-fn-changed-report-3.txt \
>  test-abicompat/test10/test10-fn-changed-report-4.txt \
> +test-abicompat/libtest11-fn-changed.xml \
> +test-abicompat/test11-fn-changed-app.xml \
> +test-abicompat/test11-fn-changed.txt \
>  test-abicompat/test-diff-ptr-to-void-ptr/0/libtest-diff-ptr-to-void-ptr-fn.so \
>  test-abicompat/test-diff-ptr-to-void-ptr/0/test-diff-ptr-to-void-ptr-fn-v0.c \
>  test-abicompat/test-diff-ptr-to-void-ptr/1/libtest-diff-ptr-to-void-ptr-fn.so \
> diff --git a/tests/data/test-abicompat/libtest11-fn-changed.xml b/tests/data/test-abicompat/libtest11-fn-changed.xml
> new file mode 100644
> index 00000000..097af2af
> --- /dev/null
> +++ b/tests/data/test-abicompat/libtest11-fn-changed.xml
> @@ -0,0 +1,86 @@
> +<abi-corpus-group version='2.4' architecture='elf-amd-x86_64'>
> +  <abi-corpus version='2.4' path='libtest-1.so' architecture='elf-amd-x86_64' soname='libtest-1.so'>
> +    <elf-needed>
> +      <dependency name='libstdc++.so.6'/>
> +      <dependency name='libm.so.6'/>
> +      <dependency name='libgcc_s.so.1'/>
> +      <dependency name='libc.so.6'/>
> +    </elf-needed>
> +    <elf-function-symbols>
> +      <elf-symbol name='_Z10first_funcv' type='func-type' binding='global-binding' visibility='default-visibility' is-defined='yes'/>
> +      <elf-symbol name='_Z11second_funcR3foo' type='func-type' binding='global-binding' visibility='default-visibility' is-defined='yes'/>
> +      <elf-symbol name='_ZN3fooC1Ev' type='func-type' binding='weak-binding' visibility='default-visibility' is-defined='yes'/>
> +      <elf-symbol name='_ZN3fooC2Ev' type='func-type' binding='weak-binding' visibility='default-visibility' alias='_ZN3fooC1Ev' is-defined='yes'/>
> +    </elf-function-symbols>
> +    <undefined-elf-function-symbols>
> +      <elf-symbol name='_ZdlPvm' version='CXXABI_1.3.9' is-default-version='yes' type='func-type' binding='global-binding' visibility='default-visibility' is-defined='no'/>
> +      <elf-symbol name='_Znwm' type='func-type' binding='global-binding' visibility='default-visibility' is-defined='no'/>
> +      <elf-symbol name='__cxa_finalize' type='func-type' binding='weak-binding' visibility='default-visibility' is-defined='no'/>
> +    </undefined-elf-function-symbols>
> +    <undefined-elf-variable-symbols>
> +      <elf-symbol name='_ITM_deregisterTMCloneTable' type='no-type' binding='weak-binding' visibility='default-visibility' is-defined='no'/>
> +      <elf-symbol name='_ITM_registerTMCloneTable' type='no-type' binding='weak-binding' visibility='default-visibility' is-defined='no'/>
> +      <elf-symbol name='__gmon_start__' type='no-type' binding='weak-binding' visibility='default-visibility' is-defined='no'/>
> +    </undefined-elf-variable-symbols>
> +    <abi-instr address-size='64' path='test1.cc' comp-dir-path='/home/cls/Projects/oracle/libabigail_work/BinLibCheck/simpleExample' language='LANG_C_plus_plus_14'>
> +      <type-decl name='char' size-in-bits='8' hash='65b2d157027b431a' id='type-id-1'/>
> +      <type-decl name='int' size-in-bits='32' hash='09d17c08f594edc7' id='type-id-2'/>
> +      <class-decl name='foo' is-struct='yes' visibility='default' size-in-bits='64' filepath='/home/cls/Projects/oracle/libabigail_work/BinLibCheck/simpleExample/test1.h' line='1' column='1' hash='b9bca352028d2431' id='type-id-3'>
> +        <data-member access='public' layout-offset-in-bits='0'>
> +          <var-decl name='m0' type-id='type-id-2' visibility='default' filepath='/home/cls/Projects/oracle/libabigail_work/BinLibCheck/simpleExample/test1.h' line='3' column='1'/>
> +        </data-member>
> +        <data-member access='public' layout-offset-in-bits='32'>
> +          <var-decl name='m1' type-id='type-id-1' visibility='default' filepath='/home/cls/Projects/oracle/libabigail_work/BinLibCheck/simpleExample/test1.h' line='4' column='1'/>
> +        </data-member>
> +        <member-function access='public' constructor='yes'>
> +          <function-decl name='foo' mangled-name='_ZN3fooC2Ev' filepath='/home/cls/Projects/oracle/libabigail_work/BinLibCheck/simpleExample/test1.h' line='6' column='1' declared-inline='yes' visibility='default' binding='global' size-in-bits='64' elf-symbol-id='_ZN3fooC2Ev' hash='7f32ffea222edbe7'>
> +            <parameter type-id='type-id-4' is-artificial='yes'/>
> +            <return type-id='type-id-5'/>
> +          </function-decl>
> +        </member-function>
> +      </class-decl>
> +      <reference-type-def kind='lvalue' type-id='type-id-3' size-in-bits='64' hash='1854f0b6950169b7' id='type-id-6'/>
> +      <pointer-type-def type-id='type-id-3' size-in-bits='64' hash='dc66dea208456f85' id='type-id-4'/>
> +      <qualified-type-def type-id='type-id-4' const='yes' hash='0ee8cf1f479fe91e' id='type-id-7'/>
> +      <function-decl name='first_func' mangled-name='_Z10first_funcv' filepath='/home/cls/Projects/oracle/libabigail_work/BinLibCheck/simpleExample/test1.cc' line='7' column='1' visibility='default' binding='global' size-in-bits='64' elf-symbol-id='_Z10first_funcv' hash='fb51336c1fc02347'>
> +        <return type-id='type-id-4'/>
> +      </function-decl>
> +      <function-decl name='second_func' mangled-name='_Z11second_funcR3foo' filepath='/home/cls/Projects/oracle/libabigail_work/BinLibCheck/simpleExample/test1.cc' line='14' column='1' visibility='default' binding='global' size-in-bits='64' elf-symbol-id='_Z11second_funcR3foo' hash='01ba306f4098d54e'>
> +        <parameter type-id='type-id-6'/>
> +        <return type-id='type-id-5'/>
> +      </function-decl>
> +      <type-decl name='void' id='type-id-5'/>
> +    </abi-instr>
> +  </abi-corpus>
> +  <abi-corpus version='2.4' path='libtest-2.so' architecture='elf-amd-x86_64' soname='libtest-2.so'>
> +    <elf-needed>
> +      <dependency name='libstdc++.so.6'/>
> +      <dependency name='libm.so.6'/>
> +      <dependency name='libgcc_s.so.1'/>
> +      <dependency name='libc.so.6'/>
> +    </elf-needed>
> +    <elf-function-symbols>
> +      <elf-symbol name='_Z3fooP2S0' type='func-type' binding='global-binding' visibility='default-visibility' is-defined='yes'/>
> +    </elf-function-symbols>
> +    <undefined-elf-function-symbols>
> +      <elf-symbol name='__cxa_finalize' version='GLIBC_2.2.5' is-default-version='yes' type='func-type' binding='weak-binding' visibility='default-visibility' is-defined='no'/>
> +    </undefined-elf-function-symbols>
> +    <undefined-elf-variable-symbols>
> +      <elf-symbol name='_ITM_deregisterTMCloneTable' type='no-type' binding='weak-binding' visibility='default-visibility' is-defined='no'/>
> +      <elf-symbol name='_ITM_registerTMCloneTable' type='no-type' binding='weak-binding' visibility='default-visibility' is-defined='no'/>
> +      <elf-symbol name='__gmon_start__' type='no-type' binding='weak-binding' visibility='default-visibility' is-defined='no'/>
> +    </undefined-elf-variable-symbols>
> +    <abi-instr address-size='64' path='test2.cc' comp-dir-path='/home/cls/Projects/oracle/libabigail_work/BinLibCheck/simpleExample' language='LANG_C_plus_plus_14'>
> +      <class-decl name='S0' is-struct='yes' visibility='default' size-in-bits='32' filepath='/home/cls/Projects/oracle/libabigail_work/BinLibCheck/simpleExample/test2.cc' line='4' column='1' hash='55ff770e9a972727' id='type-id-8'>
> +        <data-member access='public' layout-offset-in-bits='0'>
> +          <var-decl name='m0' type-id='type-id-2' visibility='default' filepath='/home/cls/Projects/oracle/libabigail_work/BinLibCheck/simpleExample/test2.cc' line='6' column='1'/>
> +        </data-member>
> +      </class-decl>
> +      <pointer-type-def type-id='type-id-8' size-in-bits='64' hash='c816e79b49fbd0b0' id='type-id-9'/>
> +      <function-decl name='foo' mangled-name='_Z3fooP2S0' filepath='/home/cls/Projects/oracle/libabigail_work/BinLibCheck/simpleExample/test2.cc' line='10' column='1' visibility='default' binding='global' size-in-bits='64' elf-symbol-id='_Z3fooP2S0' hash='a6fe781ceb581b24'>
> +        <parameter type-id='type-id-9'/>
> +        <return type-id='type-id-5'/>
> +      </function-decl>
> +    </abi-instr>
> +  </abi-corpus>
> +</abi-corpus-group>
> diff --git a/tests/data/test-abicompat/test11-fn-changed-app.xml b/tests/data/test-abicompat/test11-fn-changed-app.xml
> new file mode 100644
> index 00000000..8e5b2728
> --- /dev/null
> +++ b/tests/data/test-abicompat/test11-fn-changed-app.xml
> @@ -0,0 +1,45 @@
> +<abi-corpus version='2.4' path='test-app' architecture='elf-amd-x86_64'>
> +  <elf-needed>
> +    <dependency name='libtest-0.so'/>
> +    <dependency name='libstdc++.so.6'/>
> +    <dependency name='libm.so.6'/>
> +    <dependency name='libgcc_s.so.1'/>
> +    <dependency name='libc.so.6'/>
> +  </elf-needed>
> +  <elf-function-symbols>
> +    <elf-symbol name='_start' type='func-type' binding='global-binding' visibility='default-visibility' is-defined='yes'/>
> +    <elf-symbol name='main' type='func-type' binding='global-binding' visibility='default-visibility' is-defined='yes'/>
> +  </elf-function-symbols>
> +  <elf-variable-symbols>
> +    <elf-symbol name='_IO_stdin_used' size='4' type='object-type' binding='global-binding' visibility='default-visibility' is-defined='yes'/>
> +  </elf-variable-symbols>
> +  <undefined-elf-function-symbols>
> +    <elf-symbol name='_Z10first_funcv' type='func-type' binding='global-binding' visibility='default-visibility' is-defined='no'/>
> +    <elf-symbol name='_Z11second_funcR3foo' type='func-type' binding='global-binding' visibility='default-visibility' is-defined='no'/>
> +    <elf-symbol name='__libc_start_main@GLIBC_2.34' type='func-type' binding='global-binding' visibility='default-visibility' is-defined='no'/>
> +  </undefined-elf-function-symbols>
> +  <undefined-elf-variable-symbols>
> +    <elf-symbol name='__gmon_start__' type='no-type' binding='weak-binding' visibility='default-visibility' is-defined='no'/>
> +  </undefined-elf-variable-symbols>
> +  <abi-instr address-size='64' path='test-app.cc' comp-dir-path='/home/cls/Projects/oracle/libabigail_work/BinLibCheck/simpleExample' language='LANG_C_plus_plus_14'>
> +    <type-decl name='int' size-in-bits='32' hash='09d17c08f594edc7' id='type-id-1'/>
> +    <class-decl name='foo' is-struct='yes' visibility='default' size-in-bits='32' filepath='/home/cls/Projects/oracle/libabigail_work/BinLibCheck/simpleExample/test0.h' line='1' column='1' hash='c994494c47450411' id='type-id-2'>
> +      <data-member access='public' layout-offset-in-bits='0'>
> +        <var-decl name='m0' type-id='type-id-1' visibility='default' filepath='/home/cls/Projects/oracle/libabigail_work/BinLibCheck/simpleExample/test0.h' line='3' column='1'/>
> +      </data-member>
> +    </class-decl>
> +    <reference-type-def kind='lvalue' type-id='type-id-2' size-in-bits='64' hash='1854f0b6950169b7' id='type-id-3'/>
> +    <pointer-type-def type-id='type-id-2' size-in-bits='64' hash='dc66dea208456f85' id='type-id-4'/>
> +    <function-decl name='main' mangled-name='main' filepath='/home/cls/Projects/oracle/libabigail_work/BinLibCheck/simpleExample/test-app.cc' line='7' column='1' visibility='default' binding='global' size-in-bits='64' elf-symbol-id='main' hash='388da3fa973fde78'>
> +      <return type-id='type-id-1'/>
> +    </function-decl>
> +    <function-decl name='first_func' mangled-name='_Z10first_funcv' filepath='/home/cls/Projects/oracle/libabigail_work/BinLibCheck/simpleExample/test0.h' line='11' column='1' visibility='default' binding='global' size-in-bits='64' elf-symbol-id='_Z10first_funcv' hash='fb51336c1fc02347'>
> +      <return type-id='type-id-4'/>
> +    </function-decl>
> +    <function-decl name='second_func' mangled-name='_Z11second_funcR3foo' filepath='/home/cls/Projects/oracle/libabigail_work/BinLibCheck/simpleExample/test0.h' line='14' column='1' visibility='default' binding='global' size-in-bits='64' elf-symbol-id='_Z11second_funcR3foo' hash='01ba306f4098d54e'>
> +      <parameter type-id='type-id-3'/>
> +      <return type-id='type-id-5'/>
> +    </function-decl>
> +    <type-decl name='void' id='type-id-5'/>
> +  </abi-instr>
> +</abi-corpus>
> diff --git a/tests/data/test-abicompat/test11-fn-changed.txt b/tests/data/test-abicompat/test11-fn-changed.txt
> new file mode 100644
> index 00000000..2fade2df
> --- /dev/null
> +++ b/tests/data/test-abicompat/test11-fn-changed.txt
> @@ -0,0 +1,14 @@
> +functions defined in library 'libtest11-fn-changed.xml'
> +have sub-types that are different from what application 'test11-fn-changed-app.xml' expects:
> +
> +  function foo* first_func(void):
> +    return type changed:
> +      in pointed to type 'struct foo' at test1.h:1:1:
> +        type size changed from 32 to 64 (in bits)
> +        1 data member insertion:
> +          'char m1', at offset 32 (in bits) at test1.h:4:1
> +
> +  function void second_func(foo&):
> +    parameter 1 of type 'foo&' has sub-type changes:
> +      referenced type 'struct foo' changed at test0.h:1:1, as reported earlier
> +
> diff --git a/tests/test-abicompat.cc b/tests/test-abicompat.cc
> index 051cfba4..ca97884a 100644
> --- a/tests/test-abicompat.cc
> +++ b/tests/test-abicompat.cc
> @@ -334,6 +334,16 @@ InOutSpec in_out_specs[] =
>      "data/test-abicompat/test10/test10-fn-changed-report-4.txt",
>      "output/test-abicompat/test10/test10-fn-changed-report-4.txt",
>    },
> +  {
> +    "data/test-abicompat/test11-fn-changed-app.xml",
> +    "data/test-abicompat/libtest11-fn-changed.xml",
> +    "",
> +    "",
> +    "--show-base-names --weak-mode",
> +    abigail::tools_utils::ABIDIFF_ABI_CHANGE,
> +    "data/test-abicompat/test11-fn-changed.txt",
> +    "output/test-abicompat/test11-fn-changed.txt",
> +  },
>    // This entry must be the last one.
>    {0, 0, 0, 0, 0, abigail::tools_utils::ABIDIFF_OK, 0, 0}
>  };
> diff --git a/tools/abicompat.cc b/tools/abicompat.cc
> index b3e556dd..820ea129 100644
> --- a/tools/abicompat.cc
> +++ b/tools/abicompat.cc
> @@ -881,14 +881,17 @@ read_corpus(options			opts,
>        }
>        break;
>      case abigail::tools_utils::FILE_TYPE_XML_CORPUS:
> +    case abigail::tools_utils::FILE_TYPE_XML_CORPUS_GROUP:
>        {
>  	rdr = abixml::create_reader(path, env);
>  	assert(rdr);
> -	retval = rdr->read_corpus(status);
> +	retval =
> +	  (type == abigail::tools_utils::FILE_TYPE_XML_CORPUS_GROUP)
> +	  ? abixml::read_corpus_group_from_input(*rdr)
> +	  : rdr->read_corpus(status);
>        }
>        break;
>      case abigail::tools_utils::FILE_TYPE_AR:
> -    case abigail::tools_utils::FILE_TYPE_XML_CORPUS_GROUP:
>      case abigail::tools_utils::FILE_TYPE_RPM:
>      case abigail::tools_utils::FILE_TYPE_SRPM:
>      case abigail::tools_utils::FILE_TYPE_DEB:
  
Dodji Seketeli Aug. 22, 2025, 6:19 p.m. UTC | #3
Hey hey Claudiu,

Claudiu Zissulescu-Ianculescu <claudiu.zissulescu-ianculescu@oracle.com>
a écrit:

> Hi Dodji,
>
> Indeed your patch is more neat than mine, removing the extra work of
> checking multiple times (in my case) the courpus group when having
> multipe so.
>
> IMHO looks very good your patch,

Many thanks!

[...]

>> From 22bb528d00a457fb0ff601a4adca22d8d3338a35 Mon Sep 17 00:00:00 2001
>> From: Claudiu Zissulescu <claudiu.zissulescu-ianculescu@oracle.com>
>> Date: Thu, 7 Aug 2025 18:17:57 +0300
>> Subject: [PATCH] abicompat: Add the ability to process abi corpus group
>> 
>> abicompat doesn't seem to support libraries being represented by
>> corpus group abixml files.
>> 
>> This patch fixes that.
>> 
>> First, the patch makes abigail::ir::corpus::lookup_{functions,variables} be
>> virtual member functions.  Then, it adds
>> abigail::ir::corpus_group::lookup_{functions,variables} overloads to the
>> abigail::ir::corpus_group child class of abigail::ir::corpus.
>> 
>> Basically, abigail::corpus_group::lookup_functions cycles over the
>> corpora of the group and invokes abigail::corpus::lookup_functions on
>> each corpus until it finds a corpus that has the functions. Similarly
>> for abigail::corpus_group::lookup_variables.
>> 
>> The function read_corpus in abicompat.cc is amended to support corpus
>> group ABIXML files by calling read_corpus_group_from_input on them.
>> Because a corpus_group is a particular type of corpus, the rest of the
>> code should now do the right thing.
>> 
>> The patch also adds a new test case for this functionality.
>> 
>> 	* include/abg-corpus.h (corpus::lookup_{function,variable}s):
>> 	Make these member functions virtual.
>> 	(corpus_group::{lookup_{function,variable}s): Add new overloads.
>> 	* src/abg-corpus.cc (corpus_group::{lookup_{function,variable}s):
>> 	Define new virtual overloads that walk over each corpus, invokes
>> 	corpus::lookup_{functions,variables} on them and return the first
>> 	one that is non-nil.
>> 	* tests/data/test-abicompat/libtest11-fn-changed.xml: New test input.
>> 	* tests/data/test-abicompat/test11-fn-changed-app.xml: Likewise.
>> 	* tests/data/test-abicompat/test11-fn-changed.txt: Likewise.
>> 	* tests/test-abicompat.cc (in_out_specs): Add the new test
>> 	material above to this test harness.
>> 	* tools/abicompat.cc (read_corpus): Support
>> 	abigail::tools_utils::FILE_TYPE_XML_CORPUS_GROUP kind of input file.
>> 
>> Signed-off-by: Dodji Seketeli <dodji@redhat.com>

I am applying it to the mainline then.

Many thanks.

[...]

Cheers,
  

Patch

diff --git a/big-tests b/big-tests
index e772c1e6..bcec65b5 160000
--- a/big-tests
+++ b/big-tests
@@ -1 +1 @@ 
-Subproject commit e772c1e67aba14141dd16735ecdf19368b0cb29e
+Subproject commit bcec65b53b8332710f8d74adf4d6a060a91ab5ee
diff --git a/doc/manuals/abicompat.rst b/doc/manuals/abicompat.rst
index 8f19c433..99e41896 100644
--- a/doc/manuals/abicompat.rst
+++ b/doc/manuals/abicompat.rst
@@ -131,6 +131,14 @@  Options
     application but that are removed from the library.  That is why it
     is called ``weak`` mode.
 
+  * ``--soname`` <shared-library-name>
+
+    Specify a shared library name. When the shared libraries versions
+    are given as ABIXMLs and those XML files contains groups of
+    corpora, then the shared library name is used to select the right
+    ABI corpus to be used when checking the ABI compatibility between
+    the application and the shared library.
+
 .. _abicompat_return_value_label:
 
 Return values
diff --git a/tests/data/test-abicompat/libtest10-fn-changed.xml b/tests/data/test-abicompat/libtest10-fn-changed.xml
new file mode 100644
index 00000000..097af2af
--- /dev/null
+++ b/tests/data/test-abicompat/libtest10-fn-changed.xml
@@ -0,0 +1,86 @@ 
+<abi-corpus-group version='2.4' architecture='elf-amd-x86_64'>
+  <abi-corpus version='2.4' path='libtest-1.so' architecture='elf-amd-x86_64' soname='libtest-1.so'>
+    <elf-needed>
+      <dependency name='libstdc++.so.6'/>
+      <dependency name='libm.so.6'/>
+      <dependency name='libgcc_s.so.1'/>
+      <dependency name='libc.so.6'/>
+    </elf-needed>
+    <elf-function-symbols>
+      <elf-symbol name='_Z10first_funcv' type='func-type' binding='global-binding' visibility='default-visibility' is-defined='yes'/>
+      <elf-symbol name='_Z11second_funcR3foo' type='func-type' binding='global-binding' visibility='default-visibility' is-defined='yes'/>
+      <elf-symbol name='_ZN3fooC1Ev' type='func-type' binding='weak-binding' visibility='default-visibility' is-defined='yes'/>
+      <elf-symbol name='_ZN3fooC2Ev' type='func-type' binding='weak-binding' visibility='default-visibility' alias='_ZN3fooC1Ev' is-defined='yes'/>
+    </elf-function-symbols>
+    <undefined-elf-function-symbols>
+      <elf-symbol name='_ZdlPvm' version='CXXABI_1.3.9' is-default-version='yes' type='func-type' binding='global-binding' visibility='default-visibility' is-defined='no'/>
+      <elf-symbol name='_Znwm' type='func-type' binding='global-binding' visibility='default-visibility' is-defined='no'/>
+      <elf-symbol name='__cxa_finalize' type='func-type' binding='weak-binding' visibility='default-visibility' is-defined='no'/>
+    </undefined-elf-function-symbols>
+    <undefined-elf-variable-symbols>
+      <elf-symbol name='_ITM_deregisterTMCloneTable' type='no-type' binding='weak-binding' visibility='default-visibility' is-defined='no'/>
+      <elf-symbol name='_ITM_registerTMCloneTable' type='no-type' binding='weak-binding' visibility='default-visibility' is-defined='no'/>
+      <elf-symbol name='__gmon_start__' type='no-type' binding='weak-binding' visibility='default-visibility' is-defined='no'/>
+    </undefined-elf-variable-symbols>
+    <abi-instr address-size='64' path='test1.cc' comp-dir-path='/home/cls/Projects/oracle/libabigail_work/BinLibCheck/simpleExample' language='LANG_C_plus_plus_14'>
+      <type-decl name='char' size-in-bits='8' hash='65b2d157027b431a' id='type-id-1'/>
+      <type-decl name='int' size-in-bits='32' hash='09d17c08f594edc7' id='type-id-2'/>
+      <class-decl name='foo' is-struct='yes' visibility='default' size-in-bits='64' filepath='/home/cls/Projects/oracle/libabigail_work/BinLibCheck/simpleExample/test1.h' line='1' column='1' hash='b9bca352028d2431' id='type-id-3'>
+        <data-member access='public' layout-offset-in-bits='0'>
+          <var-decl name='m0' type-id='type-id-2' visibility='default' filepath='/home/cls/Projects/oracle/libabigail_work/BinLibCheck/simpleExample/test1.h' line='3' column='1'/>
+        </data-member>
+        <data-member access='public' layout-offset-in-bits='32'>
+          <var-decl name='m1' type-id='type-id-1' visibility='default' filepath='/home/cls/Projects/oracle/libabigail_work/BinLibCheck/simpleExample/test1.h' line='4' column='1'/>
+        </data-member>
+        <member-function access='public' constructor='yes'>
+          <function-decl name='foo' mangled-name='_ZN3fooC2Ev' filepath='/home/cls/Projects/oracle/libabigail_work/BinLibCheck/simpleExample/test1.h' line='6' column='1' declared-inline='yes' visibility='default' binding='global' size-in-bits='64' elf-symbol-id='_ZN3fooC2Ev' hash='7f32ffea222edbe7'>
+            <parameter type-id='type-id-4' is-artificial='yes'/>
+            <return type-id='type-id-5'/>
+          </function-decl>
+        </member-function>
+      </class-decl>
+      <reference-type-def kind='lvalue' type-id='type-id-3' size-in-bits='64' hash='1854f0b6950169b7' id='type-id-6'/>
+      <pointer-type-def type-id='type-id-3' size-in-bits='64' hash='dc66dea208456f85' id='type-id-4'/>
+      <qualified-type-def type-id='type-id-4' const='yes' hash='0ee8cf1f479fe91e' id='type-id-7'/>
+      <function-decl name='first_func' mangled-name='_Z10first_funcv' filepath='/home/cls/Projects/oracle/libabigail_work/BinLibCheck/simpleExample/test1.cc' line='7' column='1' visibility='default' binding='global' size-in-bits='64' elf-symbol-id='_Z10first_funcv' hash='fb51336c1fc02347'>
+        <return type-id='type-id-4'/>
+      </function-decl>
+      <function-decl name='second_func' mangled-name='_Z11second_funcR3foo' filepath='/home/cls/Projects/oracle/libabigail_work/BinLibCheck/simpleExample/test1.cc' line='14' column='1' visibility='default' binding='global' size-in-bits='64' elf-symbol-id='_Z11second_funcR3foo' hash='01ba306f4098d54e'>
+        <parameter type-id='type-id-6'/>
+        <return type-id='type-id-5'/>
+      </function-decl>
+      <type-decl name='void' id='type-id-5'/>
+    </abi-instr>
+  </abi-corpus>
+  <abi-corpus version='2.4' path='libtest-2.so' architecture='elf-amd-x86_64' soname='libtest-2.so'>
+    <elf-needed>
+      <dependency name='libstdc++.so.6'/>
+      <dependency name='libm.so.6'/>
+      <dependency name='libgcc_s.so.1'/>
+      <dependency name='libc.so.6'/>
+    </elf-needed>
+    <elf-function-symbols>
+      <elf-symbol name='_Z3fooP2S0' type='func-type' binding='global-binding' visibility='default-visibility' is-defined='yes'/>
+    </elf-function-symbols>
+    <undefined-elf-function-symbols>
+      <elf-symbol name='__cxa_finalize' version='GLIBC_2.2.5' is-default-version='yes' type='func-type' binding='weak-binding' visibility='default-visibility' is-defined='no'/>
+    </undefined-elf-function-symbols>
+    <undefined-elf-variable-symbols>
+      <elf-symbol name='_ITM_deregisterTMCloneTable' type='no-type' binding='weak-binding' visibility='default-visibility' is-defined='no'/>
+      <elf-symbol name='_ITM_registerTMCloneTable' type='no-type' binding='weak-binding' visibility='default-visibility' is-defined='no'/>
+      <elf-symbol name='__gmon_start__' type='no-type' binding='weak-binding' visibility='default-visibility' is-defined='no'/>
+    </undefined-elf-variable-symbols>
+    <abi-instr address-size='64' path='test2.cc' comp-dir-path='/home/cls/Projects/oracle/libabigail_work/BinLibCheck/simpleExample' language='LANG_C_plus_plus_14'>
+      <class-decl name='S0' is-struct='yes' visibility='default' size-in-bits='32' filepath='/home/cls/Projects/oracle/libabigail_work/BinLibCheck/simpleExample/test2.cc' line='4' column='1' hash='55ff770e9a972727' id='type-id-8'>
+        <data-member access='public' layout-offset-in-bits='0'>
+          <var-decl name='m0' type-id='type-id-2' visibility='default' filepath='/home/cls/Projects/oracle/libabigail_work/BinLibCheck/simpleExample/test2.cc' line='6' column='1'/>
+        </data-member>
+      </class-decl>
+      <pointer-type-def type-id='type-id-8' size-in-bits='64' hash='c816e79b49fbd0b0' id='type-id-9'/>
+      <function-decl name='foo' mangled-name='_Z3fooP2S0' filepath='/home/cls/Projects/oracle/libabigail_work/BinLibCheck/simpleExample/test2.cc' line='10' column='1' visibility='default' binding='global' size-in-bits='64' elf-symbol-id='_Z3fooP2S0' hash='a6fe781ceb581b24'>
+        <parameter type-id='type-id-9'/>
+        <return type-id='type-id-5'/>
+      </function-decl>
+    </abi-instr>
+  </abi-corpus>
+</abi-corpus-group>
diff --git a/tests/data/test-abicompat/test10-fn-changed-app.xml b/tests/data/test-abicompat/test10-fn-changed-app.xml
new file mode 100644
index 00000000..8e5b2728
--- /dev/null
+++ b/tests/data/test-abicompat/test10-fn-changed-app.xml
@@ -0,0 +1,45 @@ 
+<abi-corpus version='2.4' path='test-app' architecture='elf-amd-x86_64'>
+  <elf-needed>
+    <dependency name='libtest-0.so'/>
+    <dependency name='libstdc++.so.6'/>
+    <dependency name='libm.so.6'/>
+    <dependency name='libgcc_s.so.1'/>
+    <dependency name='libc.so.6'/>
+  </elf-needed>
+  <elf-function-symbols>
+    <elf-symbol name='_start' type='func-type' binding='global-binding' visibility='default-visibility' is-defined='yes'/>
+    <elf-symbol name='main' type='func-type' binding='global-binding' visibility='default-visibility' is-defined='yes'/>
+  </elf-function-symbols>
+  <elf-variable-symbols>
+    <elf-symbol name='_IO_stdin_used' size='4' type='object-type' binding='global-binding' visibility='default-visibility' is-defined='yes'/>
+  </elf-variable-symbols>
+  <undefined-elf-function-symbols>
+    <elf-symbol name='_Z10first_funcv' type='func-type' binding='global-binding' visibility='default-visibility' is-defined='no'/>
+    <elf-symbol name='_Z11second_funcR3foo' type='func-type' binding='global-binding' visibility='default-visibility' is-defined='no'/>
+    <elf-symbol name='__libc_start_main@GLIBC_2.34' type='func-type' binding='global-binding' visibility='default-visibility' is-defined='no'/>
+  </undefined-elf-function-symbols>
+  <undefined-elf-variable-symbols>
+    <elf-symbol name='__gmon_start__' type='no-type' binding='weak-binding' visibility='default-visibility' is-defined='no'/>
+  </undefined-elf-variable-symbols>
+  <abi-instr address-size='64' path='test-app.cc' comp-dir-path='/home/cls/Projects/oracle/libabigail_work/BinLibCheck/simpleExample' language='LANG_C_plus_plus_14'>
+    <type-decl name='int' size-in-bits='32' hash='09d17c08f594edc7' id='type-id-1'/>
+    <class-decl name='foo' is-struct='yes' visibility='default' size-in-bits='32' filepath='/home/cls/Projects/oracle/libabigail_work/BinLibCheck/simpleExample/test0.h' line='1' column='1' hash='c994494c47450411' id='type-id-2'>
+      <data-member access='public' layout-offset-in-bits='0'>
+        <var-decl name='m0' type-id='type-id-1' visibility='default' filepath='/home/cls/Projects/oracle/libabigail_work/BinLibCheck/simpleExample/test0.h' line='3' column='1'/>
+      </data-member>
+    </class-decl>
+    <reference-type-def kind='lvalue' type-id='type-id-2' size-in-bits='64' hash='1854f0b6950169b7' id='type-id-3'/>
+    <pointer-type-def type-id='type-id-2' size-in-bits='64' hash='dc66dea208456f85' id='type-id-4'/>
+    <function-decl name='main' mangled-name='main' filepath='/home/cls/Projects/oracle/libabigail_work/BinLibCheck/simpleExample/test-app.cc' line='7' column='1' visibility='default' binding='global' size-in-bits='64' elf-symbol-id='main' hash='388da3fa973fde78'>
+      <return type-id='type-id-1'/>
+    </function-decl>
+    <function-decl name='first_func' mangled-name='_Z10first_funcv' filepath='/home/cls/Projects/oracle/libabigail_work/BinLibCheck/simpleExample/test0.h' line='11' column='1' visibility='default' binding='global' size-in-bits='64' elf-symbol-id='_Z10first_funcv' hash='fb51336c1fc02347'>
+      <return type-id='type-id-4'/>
+    </function-decl>
+    <function-decl name='second_func' mangled-name='_Z11second_funcR3foo' filepath='/home/cls/Projects/oracle/libabigail_work/BinLibCheck/simpleExample/test0.h' line='14' column='1' visibility='default' binding='global' size-in-bits='64' elf-symbol-id='_Z11second_funcR3foo' hash='01ba306f4098d54e'>
+      <parameter type-id='type-id-3'/>
+      <return type-id='type-id-5'/>
+    </function-decl>
+    <type-decl name='void' id='type-id-5'/>
+  </abi-instr>
+</abi-corpus>
diff --git a/tests/data/test-abicompat/test10-fn-changed.txt b/tests/data/test-abicompat/test10-fn-changed.txt
new file mode 100644
index 00000000..357b9d75
--- /dev/null
+++ b/tests/data/test-abicompat/test10-fn-changed.txt
@@ -0,0 +1,15 @@ 
+Searching for soname libtest-1.so ... found.
+functions defined in library 'libtest10-fn-changed.xml'
+have sub-types that are different from what application 'test10-fn-changed-app.xml' expects:
+
+  function foo* first_func(void):
+    return type changed:
+      in pointed to type 'struct foo' at test1.h:1:1:
+        type size changed from 32 to 64 (in bits)
+        1 data member insertion:
+          'char m1', at offset 32 (in bits) at test1.h:4:1
+
+  function void second_func(foo&):
+    parameter 1 of type 'foo&' has sub-type changes:
+      referenced type 'struct foo' changed at test0.h:1:1, as reported earlier
+
diff --git a/tests/test-abicompat.cc b/tests/test-abicompat.cc
index 051cfba4..02ca97c3 100644
--- a/tests/test-abicompat.cc
+++ b/tests/test-abicompat.cc
@@ -334,6 +334,16 @@  InOutSpec in_out_specs[] =
     "data/test-abicompat/test10/test10-fn-changed-report-4.txt",
     "output/test-abicompat/test10/test10-fn-changed-report-4.txt",
   },
+  {
+    "data/test-abicompat/test10-fn-changed-app.xml",
+    "data/test-abicompat/libtest10-fn-changed.xml",
+    "",
+    "",
+    "--show-base-names --weak-mode --soname libtest-1.so",
+    abigail::tools_utils::ABIDIFF_ABI_CHANGE,
+    "data/test-abicompat/test10-fn-changed.txt",
+    "output/test-abicompat/test10-fn-changed.txt",
+  },
   // This entry must be the last one.
   {0, 0, 0, 0, 0, abigail::tools_utils::ABIDIFF_OK, 0, 0}
 };
diff --git a/tools/abicompat.cc b/tools/abicompat.cc
index b3e556dd..9b28d9ad 100644
--- a/tools/abicompat.cc
+++ b/tools/abicompat.cc
@@ -100,6 +100,7 @@  public:
   string		app_di_root_path;
   string		lib1_di_root_path;
   string		lib2_di_root_path;
+  string		soname;
   vector<string>	suppression_paths;
   bool			display_help;
   bool			display_version;
@@ -246,6 +247,7 @@  display_usage(const string& prog_name, ostream& out)
     << "  --redundant  display redundant changes (this is the default)\n"
     << "  --weak-mode  check compatibility between the application and "
     "just one version of the library.\n"
+    << "  --soname  specify the search so name (only for xml corpus groups)\n"
 #ifdef WITH_CTF
     << "  --ctf use CTF instead of DWARF in ELF files\n"
 #endif
@@ -274,6 +276,14 @@  parse_command_line(int argc, char* argv[], options& opts)
 	  else
 	    return false;
 	}
+      else if (!strcmp(argv[i], "--soname"))
+	{
+	  if (argc <= i + 1
+	      || argv[i + 1][0] == '-')
+	    return false;
+	  opts.soname = argv[i + 1];
+	  ++i;
+	}
       else if (!strcmp(argv[i], "--version")
 	       || !strcmp(argv[i], "-v"))
 	{
@@ -887,8 +897,37 @@  read_corpus(options			opts,
 	retval = rdr->read_corpus(status);
       }
       break;
-    case abigail::tools_utils::FILE_TYPE_AR:
     case abigail::tools_utils::FILE_TYPE_XML_CORPUS_GROUP:
+      {
+	if (opts.soname.empty())
+	  {
+	    cout << "No SONAME provided.\n";
+	    return NULL;
+	  }
+
+	corpus_group_sptr grp;
+	rdr = abixml::create_reader(path, env);
+	assert(rdr);
+	grp = abixml::read_corpus_group_from_input(*rdr);
+	cout << "Searching for soname " << opts.soname << " ...";
+	for (corpus_group::corpora_type::const_iterator i =
+	       grp->get_corpora().begin();
+	     i != grp->get_corpora().end();
+	     ++i)
+	  {
+	    corpus_sptr c = *i;
+	    if (c->get_soname().compare(opts.soname) == 0)
+	      {
+		cout << " found.\n";
+		retval = c;
+		break;
+	      }
+	  }
+	if (!retval)
+	  cout << "not found.\n";
+      }
+      break;
+    case abigail::tools_utils::FILE_TYPE_AR:
     case abigail::tools_utils::FILE_TYPE_RPM:
     case abigail::tools_utils::FILE_TYPE_SRPM:
     case abigail::tools_utils::FILE_TYPE_DEB: