cxx-ada-visible-type-method-names

C++ Ada visible type/method names

A C++ method can collide with a type made use-visible from another generated class package, making later Ada profiles ambiguous.

AcceptedApplies in patchset orderSince 1.2.0

Where it applies.

How each patchset treats this bundle on each GCC major
PatchsetGCC 13GCC 14GCC 15GCC 16
1.2.0 (latest)Patchedgcc-13-1413.2.0Patchedgcc-13-1414.2.0Patchedgcc-15-1615.3.0Patchedgcc-15-1616.2.0
1.1.0Not applicable13.2.0Not applicable14.2.0Not applicable15.3.0Not applicable16.2.0
1.0.1Not applicable13.2.0Not applicable14.2.0Not applicable15.3.0Not applicable16.1.0
1.0.0Not applicable13.2.0Not applicable14.2.0Not applicable15.3.0Not applicable16.1.0

Explanation.

Every generated C++ class is placed in a nested Ada package followed by a use clause. A method in one class can therefore collide case-insensitively with a type imported from another class package, even though the C++ names belong to unrelated scopes.

For example, Result becomes directly visible through use Class_Result, then result becomes directly visible through use Class_Widget:

struct Result
{
  int get ();
  int value;
};

struct Widget
{
  int result ();
  int value;
};

extern "C" Result *cpp_create_result (int value);

The unpatched mapper emits both identifiers unchanged. GNAT cannot resolve the result type in the later function profile because both use-visible names are homographs:

package Class_Result is
   type Result is limited record ... end record;
end;
use Class_Result;

package Class_Widget is
   function result (this : access Widget) return int;
end;
use Class_Widget;

function cpp_create_result (value : int) return access Result;

The corrected mapper compares every method name with all types collected for the generated unit. Only a colliding method receives the same stable suffix used for an enclosing-type collision:

package Class_Widget is
   function result_Method (this : access Widget) return int
   with Import => True,
        Convention => CPP,
        External_Name => "_ZN6Widget6resultEv";
end;

function cpp_create_result (value : int) return access Result;

The executable regression runs at -O0 and -O2. Stock GCC must produce a specification GNAT rejects for the two use-visible names. With the patch, Ada calls the renamed C++ method, reads the unrelated result object, and releases both objects through C++.

Patch.

Variant gcc-13-14

Applies to 13.2.0, 14.2.0. Source flavors: linux, darwin_arm64.

+49 −16 2 files

Download the patch

gcc/c-family/c-ada-spec.cc +13−16modified
Unified diff for gcc/c-family/c-ada-spec.cc: original line, patched line, change, source
@@ -3717 +3717 @@print_method_qualifiers (pretty_printer *buffer, tree t)
37173717 pp_string (buffer, "_Rvalue");
37183718}
37193719
3720Removed line. /* Return true if METHOD_NAME collides with the enclosing C++ TYPE name after
3721Removed line. Ada's case-insensitive identifier conversion. */
3720Added line. /* Return true if METHOD_NAME collides with the enclosing class or another
3721Added line. type made directly visible by a generated class-package use clause. */
37223722
37233723static bool
3724Removed line. method_name_matches_type (tree method_name, tree type)
3724Added line. method_name_matches_visible_type (tree method_name, tree type)
37253725{
3726Removed line. if (!method_name || !RECORD_OR_UNION_TYPE_P (type) || !TYPE_NAME (type))
3726Added line. if (!method_name)
37273727 return false;
37283728
3729Removed line. tree type_decl = TYPE_NAME (type);
3730Removed line. tree type_name_node = TREE_CODE (type_decl) == TYPE_DECL
3731Removed line. ? DECL_NAME (type_decl) : type_decl;
3732Removed line. if (!type_name_node || TREE_CODE (type_name_node) != IDENTIFIER_NODE)
3733Removed line. return false;
3729Added line. if (identifier_matches_type (method_name, type))
3730Added line. return true;
37343731
3735Removed line. char *method_ada = to_ada_name (IDENTIFIER_POINTER (method_name), NULL);
3736Removed line. char *type_ada = to_ada_name (IDENTIFIER_POINTER (type_name_node), NULL);
3737Removed line. const bool matches = !strcasecmp (method_ada, type_ada);
3738Removed line. free (method_ada);
3739Removed line. free (type_ada);
3740Removed line. return matches;
3732Added line. for (int i = 0; i < to_dump_count; i++)
3733Added line. if (TREE_CODE (to_dump[i]) == TYPE_DECL
3734Added line. && identifier_matches_type (method_name, TREE_TYPE (to_dump[i])))
3735Added line. return true;
3736Added line.
3737Added line. return false;
37413738}
37423739
37433740/* Return the name of type T. */
@@ -4084 +4081 @@dump_ada_declaration (pretty_printer *buffer, tree t, tree type, int spc)
40844081 if (is_method)
40854082 {
40864083 print_method_qualifiers (buffer, t);
4087Removed line. if (method_name_matches_type (decl_name, type))
4084Added line. if (method_name_matches_visible_type (decl_name, type))
40884085 pp_string (buffer, "_Method");
40894086 }
40904087 if (suffix > 1)
gcc/testsuite/g++.dg/ada-spec/visible-type-method-names.C +36−0new file
Unified diff for gcc/testsuite/g++.dg/ada-spec/visible-type-method-names.C: original line, patched line, change, source
@@ -0 +1 @@
1Added line. /* { dg-do compile } */
2Added line. /* { dg-options "-fdump-ada-spec-slim" } */
3Added line. /* { dg-final { scan-file visible_type_method_names_c.ads "function result_Method" } } */
4Added line.
5Added line. struct Result
6Added line. {
7Added line. int get ();
8Added line. int value;
9Added line. };
10Added line.
11Added line. class Widget
12Added line. {
13Added line. public:
14Added line. int result ();
15Added line. int value;
16Added line. };
17Added line.
18Added line. int Result::get () { return value; }
19Added line. int Widget::result () { return value; }
20Added line.
21Added line. extern "C" Widget *cpp_create_widget (int value)
22Added line. {
23Added line. Widget *object = new Widget;
24Added line. object->value = value;
25Added line. return object;
26Added line. }
27Added line.
28Added line. extern "C" Result *cpp_create_result (int value)
29Added line. {
30Added line. return new Result { value };
31Added line. }
32Added line.
33Added line. extern "C" void cpp_delete_widget (Widget *object) { delete object; }
34Added line. extern "C" void cpp_delete_result (Result *object) { delete object; }
35Added line.
36Added line. /* { dg-final { cleanup-ada-spec } } */
37
138
239

Variant gcc-15-16

Applies to 15.3.0, 16.1.0, 16.2.0. Source flavors: linux, darwin_arm64.

+49 −16 2 files

Download the patch

gcc/c-family/c-ada-spec.cc +13−16modified
Unified diff for gcc/c-family/c-ada-spec.cc: original line, patched line, change, source
@@ -3717 +3717 @@print_method_qualifiers (pretty_printer *pp, tree t)
37173717 pp_string (pp, "_Rvalue");
37183718}
37193719
3720Removed line. /* Return true if METHOD_NAME collides with the enclosing C++ TYPE name after
3721Removed line. Ada's case-insensitive identifier conversion. */
3720Added line. /* Return true if METHOD_NAME collides with the enclosing class or another
3721Added line. type made directly visible by a generated class-package use clause. */
37223722
37233723static bool
3724Removed line. method_name_matches_type (tree method_name, tree type)
3724Added line. method_name_matches_visible_type (tree method_name, tree type)
37253725{
3726Removed line. if (!method_name || !RECORD_OR_UNION_TYPE_P (type) || !TYPE_NAME (type))
3726Added line. if (!method_name)
37273727 return false;
37283728
3729Removed line. tree type_decl = TYPE_NAME (type);
3730Removed line. tree type_name_node = TREE_CODE (type_decl) == TYPE_DECL
3731Removed line. ? DECL_NAME (type_decl) : type_decl;
3732Removed line. if (!type_name_node || TREE_CODE (type_name_node) != IDENTIFIER_NODE)
3733Removed line. return false;
3729Added line. if (identifier_matches_type (method_name, type))
3730Added line. return true;
37343731
3735Removed line. char *method_ada = to_ada_name (IDENTIFIER_POINTER (method_name), NULL);
3736Removed line. char *type_ada = to_ada_name (IDENTIFIER_POINTER (type_name_node), NULL);
3737Removed line. const bool matches = !strcasecmp (method_ada, type_ada);
3738Removed line. free (method_ada);
3739Removed line. free (type_ada);
3740Removed line. return matches;
3732Added line. for (int i = 0; i < to_dump_count; i++)
3733Added line. if (TREE_CODE (to_dump[i]) == TYPE_DECL
3734Added line. && identifier_matches_type (method_name, TREE_TYPE (to_dump[i])))
3735Added line. return true;
3736Added line.
3737Added line. return false;
37413738}
37423739
37433740/* Return the name of type T. */
@@ -4084 +4081 @@dump_ada_declaration (pretty_printer *pp, tree t, tree type, int spc)
40844081 if (is_method)
40854082 {
40864083 print_method_qualifiers (pp, t);
4087Removed line. if (method_name_matches_type (decl_name, type))
4084Added line. if (method_name_matches_visible_type (decl_name, type))
40884085 pp_string (pp, "_Method");
40894086 }
40904087 if (suffix > 1)
gcc/testsuite/g++.dg/ada-spec/visible-type-method-names.C +36−0new file
Unified diff for gcc/testsuite/g++.dg/ada-spec/visible-type-method-names.C: original line, patched line, change, source
@@ -0 +1 @@
1Added line. /* { dg-do compile } */
2Added line. /* { dg-options "-fdump-ada-spec-slim" } */
3Added line. /* { dg-final { scan-file visible_type_method_names_c.ads "function result_Method" } } */
4Added line.
5Added line. struct Result
6Added line. {
7Added line. int get ();
8Added line. int value;
9Added line. };
10Added line.
11Added line. class Widget
12Added line. {
13Added line. public:
14Added line. int result ();
15Added line. int value;
16Added line. };
17Added line.
18Added line. int Result::get () { return value; }
19Added line. int Widget::result () { return value; }
20Added line.
21Added line. extern "C" Widget *cpp_create_widget (int value)
22Added line. {
23Added line. Widget *object = new Widget;
24Added line. object->value = value;
25Added line. return object;
26Added line. }
27Added line.
28Added line. extern "C" Result *cpp_create_result (int value)
29Added line. {
30Added line. return new Result { value };
31Added line. }
32Added line.
33Added line. extern "C" void cpp_delete_widget (Widget *object) { delete object; }
34Added line. extern "C" void cpp_delete_result (Result *object) { delete object; }
35Added line.
36Added line. /* { dg-final { cleanup-ada-spec } } */
37
138

Tests.

visible-type-method-names.C C++ · 36 lines
/* { dg-do compile } */
/* { dg-options "-fdump-ada-spec-slim" } */
/* { dg-final { scan-file visible_type_method_names_c.ads "function result_Method" } } */

struct Result
{
  int get ();
  int value;
};

class Widget
{
public:
  int result ();
  int value;
};

int Result::get () { return value; }
int Widget::result () { return value; }

extern "C" Widget *cpp_create_widget (int value)
{
  Widget *object = new Widget;
  object->value = value;
  return object;
}

extern "C" Result *cpp_create_result (int value)
{
  return new Result { value };
}

extern "C" void cpp_delete_widget (Widget *object) { delete object; }
extern "C" void cpp_delete_result (Result *object) { delete object; }

/* { dg-final { cleanup-ada-spec } } */

Download · View in repository

visible_type_method_names_consumer.adb Ada · 22 lines
with Ada.Text_IO;
with Interfaces.C; use Interfaces.C;
with Visible_Type_Method_Names_C;

procedure Visible_Type_Method_Names_Consumer is
   package Bindings renames Visible_Type_Method_Names_C;
   Widget_Object : access Bindings.Class_Widget.Widget :=
     Bindings.cpp_create_widget (41);
   Result_Object : access Bindings.Class_Result.Result :=
     Bindings.cpp_create_result (73);
begin
   if Widget_Object = null or else Result_Object = null
     or else Bindings.Class_Widget.result_Method (Widget_Object) /= 41
     or else Result_Object.value /= 73
   then
      raise Program_Error with "visible type/method collision changed values";
   end if;

   Bindings.cpp_delete_widget (Widget_Object);
   Bindings.cpp_delete_result (Result_Object);
   Ada.Text_IO.Put_Line ("MATCH visible type and method names");
end Visible_Type_Method_Names_Consumer;

Download · View in repository

run-test.sh shell · 33 lines
#!/usr/bin/env bash
set -euo pipefail

if [[ $# -ne 3 ]]; then echo "usage: $0 TOOLCHAIN_ROOT GCC_VERSION unpatched|patched" >&2; exit 2; fi
root=$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)
source "$root/scripts/regression-common.sh"
resolve_regression_toolchain "$1"
version=$2
state=$3
[[ "$state" == unpatched || "$state" == patched ]] || exit 2
gxx="$REGRESSION_TOOLCHAIN/bin/g++"
cxx="$root/bundles/cxx-ada-visible-type-method-names/tests/visible-type-method-names.C"
ada="$root/bundles/cxx-ada-visible-type-method-names/tests/visible_type_method_names_consumer.adb"
work=$(mktemp -d "${TMPDIR:-/tmp}/gnat-cxx-ada-visible-method.XXXXXX")
trap 'rm -rf "$work"' EXIT

for optimization in 0 2; do
  dir="$work/O$optimization"; mkdir -p "$dir"; cp "$cxx" "$ada" "$dir/"
  (cd "$dir"; "${REGRESSION_ENV[@]}" "$gxx" -c "-O$optimization" -fdump-ada-spec-slim visible-type-method-names.C)
  if [[ "$state" == unpatched ]]; then
    set +e
    (cd "$dir"; "${REGRESSION_ENV[@]}" "$REGRESSION_GCC" -c visible_type_method_names_c.ads) >"$dir/build.log" 2>&1
    status=$?; set -e
    [[ $status -ne 0 ]] || { echo "error: unpatched visible type/method binding compiled" >&2; exit 1; }
    grep -Eiq '(result).*not visible|multiple use clauses cause hiding' "$dir/build.log"
    echo "cxx-ada-visible-type-method-names -O$optimization: expected hiding (GCC $version)"
  else
    grep -F "function result_Method" "$dir/visible_type_method_names_c.ads"
    (cd "$dir"; "${REGRESSION_ENV[@]}" "$REGRESSION_GNATMAKE" -q -f "-O$optimization" visible_type_method_names_consumer.adb -largs visible-type-method-names.o -lstdc++; "${REGRESSION_ENV[@]}" ./visible_type_method_names_consumer) >"$dir/output.log" 2>&1 || { cat "$dir/output.log"; exit 1; }
    grep -Fx "MATCH visible type and method names" "$dir/output.log"
    echo "cxx-ada-visible-type-method-names -O$optimization: patched (GCC $version)"
  fi
done

Download · View in repository

Commands.

Apply the patch
patch --fuzz=0 -p1 -i bundles/cxx-ada-visible-type-method-names/patches/VARIANT.patch
Build the compiler
PATH=/path/to/bootstrap/bin:$PATH ./scripts/build-gnat.sh SOURCE BUILD INSTALL
Run the regression
./scripts/run-regressions.sh INSTALL 1.2.0 GCC_MAJOR patched

Metadata.