cxx-ada-member-pointers

C++ Ada pointers to members

C++ data-member and member-function pointer representations contain blank underlying Ada types and make the generated specification invalid.

AcceptedApplies standaloneSince 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.

The C++ front end uses two related internal representations for pointers to members. A data-member pointer is an OFFSET_TYPE; a member-function pointer is an ABI record containing a pointer-to-METHOD_TYPE and a this adjustment. The Ada dumper handles neither internal type, so both forms produce malformed declarations.

The offending C++ forms are:

struct Data_Object { int first; int field; };
using Data_Member = int Data_Object::*;

struct Method_Object {
  virtual int virtual_method (int) const;
  int nonvirtual_method (int) const;
};
using Method_Member = int (Method_Object::*) (int) const;

The unpatched mapper leaves both underlying types blank:

subtype Data_Member is ;

type Method_Member is record
   uu_pfn : access ;
   uu_delta : aliased long;
end record
with Convention => C_Pass_By_Copy;

On the Itanium C++ ABI used by every supported repository host, a data-member pointer is a ptrdiff_t byte offset with -1 reserved for null. A member-function pointer is two words: the first is either a function address or an encoded virtual-table offset, and the second adjusts this. The corrected Ada preserves those ABI values without claiming that Ada can dereference them:

subtype Data_Member is ptrdiff_t;

type Method_Member is record
   uu_pfn : System.Address;
   uu_delta : aliased long;
end record
with Convention => C_Pass_By_Copy;

System.Address is deliberately opaque: a virtual member-function encoding is not an Ada access-to-subprogram value. Invocation still belongs in C++, but the generated Ada can safely store and pass the complete value. The executable regression obtains data, nonvirtual-function, virtual-function, and null member pointers from C++, round-trips them through Ada, and asks C++ to apply or classify them at -O0 and -O2.

Run it against an unpatched or patched compiler root:

./bundles/cxx-ada-member-pointers/run-test.sh \
  TOOLCHAIN_ROOT GCC_VERSION unpatched
./bundles/cxx-ada-member-pointers/run-test.sh \
  TOOLCHAIN_ROOT GCC_VERSION patched

Patch.

Variant gcc-13-14

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

+60 −0 2 files

Download the patch

gcc/c-family/c-ada-spec.cc +15−0modified
Unified diff for gcc/c-family/c-ada-spec.cc: original line, patched line, change, source
@@ -2129 +2129 @@dump_ada_node (pretty_printer *buffer, tree node, tree type, int spc,
21292129 pp_string (buffer, "address");
21302130 break;
21312131
2132Added line. case OFFSET_TYPE:
2133Added line. /* The Itanium C++ ABI represents a data-member pointer as a ptrdiff_t
2134Added line. offset, with -1 reserved for null. */
2135Added line. pp_string (buffer, "ptrdiff_t");
2136Added line. break;
2137Added line.
21322138 case VECTOR_TYPE:
21332139 pp_string (buffer, "<vector>");
21342140 break;
@@ -2227 +2233 @@dump_ada_node (pretty_printer *buffer, tree node, tree type, int spc,
22272233 dump_ada_node (buffer, TYPE_NAME (node), node, spc, limited_access,
22282234 true);
22292235
2236Added line. else if (TREE_CODE (TREE_TYPE (node)) == METHOD_TYPE)
2237Added line. {
2238Added line. /* A member-function pointer's first word is either a function pointer
2239Added line. or an encoded virtual-table offset, so it is not an Ada access-to-
2240Added line. subprogram value. Preserve it as an opaque address-sized word. */
2241Added line. append_withs ("System", false);
2242Added line. pp_string (buffer, "System.Address");
2243Added line. }
2244Added line.
22302245 else if (TREE_CODE (TREE_TYPE (node)) == FUNCTION_TYPE)
22312246 {
22322247 if (VOID_TYPE_P (TREE_TYPE (TREE_TYPE (node))))
gcc/testsuite/g++.dg/ada-spec/member-pointers.C +45−0new file
Unified diff for gcc/testsuite/g++.dg/ada-spec/member-pointers.C: original line, patched line, change, source
@@ -0 +1 @@
1Added line. /* { dg-do compile } */
2Added line. /* { dg-options "-std=gnu++20 -fdump-ada-spec-slim" } */
3Added line. /* { dg-final { scan-file member_pointers_c.ads "subtype Data_Member is ptrdiff_t;" } } */
4Added line. /* { dg-final { scan-file member_pointers_c.ads "uu_pfn : System.Address;" } } */
5Added line. /* { dg-final { scan-file member_pointers_c.ads "uu_delta : aliased long;" } } */
6Added line.
7Added line. struct Data_Object
8Added line. {
9Added line. int first;
10Added line. int field;
11Added line. };
12Added line.
13Added line. using Data_Member = int Data_Object::*;
14Added line.
15Added line. struct Method_Object
16Added line. {
17Added line. virtual int virtual_method (int value) const { return value + 20; }
18Added line. int nonvirtual_method (int value) const { return value + 10; }
19Added line. };
20Added line.
21Added line. using Method_Member = int (Method_Object::*) (int) const;
22Added line.
23Added line. Data_Member get_data_member () { return &Data_Object::field; }
24Added line. Data_Member get_null_data_member () { return nullptr; }
25Added line. int apply_data_member (Data_Member member)
26Added line. {
27Added line. Data_Object object { 1, 42 };
28Added line. return object.*member;
29Added line. }
30Added line. bool is_null_data_member (Data_Member member) { return member == nullptr; }
31Added line.
32Added line. Method_Member get_nonvirtual_method ()
33Added line. {
34Added line. return &Method_Object::nonvirtual_method;
35Added line. }
36Added line. Method_Member get_virtual_method () { return &Method_Object::virtual_method; }
37Added line. Method_Member get_null_method () { return nullptr; }
38Added line. int apply_method (Method_Member member, int value)
39Added line. {
40Added line. Method_Object object;
41Added line. return (object.*member) (value);
42Added line. }
43Added line. bool is_null_method (Method_Member member) { return member == nullptr; }
44Added line.
45Added line. /* { dg-final { cleanup-ada-spec } } */
46

Variant gcc-15-16

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

+60 −0 2 files

Download the patch

gcc/c-family/c-ada-spec.cc +15−0modified
Unified diff for gcc/c-family/c-ada-spec.cc: original line, patched line, change, source
@@ -2189 +2189 @@dump_ada_node (pretty_printer *pp, tree node, tree type, int spc,
21892189 pp_string (pp, "address");
21902190 break;
21912191
2192Added line. case OFFSET_TYPE:
2193Added line. /* The Itanium C++ ABI represents a data-member pointer as a ptrdiff_t
2194Added line. offset, with -1 reserved for null. */
2195Added line. pp_string (pp, "ptrdiff_t");
2196Added line. break;
2197Added line.
21922198 case VECTOR_TYPE:
21932199 pp_string (pp, "<vector>");
21942200 break;
@@ -2292 +2298 @@dump_ada_node (pretty_printer *pp, tree node, tree type, int spc,
22922298 dump_ada_node (pp, TYPE_NAME (node), node, spc, limited_access,
22932299 true);
22942300
2301Added line. else if (TREE_CODE (TREE_TYPE (node)) == METHOD_TYPE)
2302Added line. {
2303Added line. /* A member-function pointer's first word is either a function pointer
2304Added line. or an encoded virtual-table offset, so it is not an Ada access-to-
2305Added line. subprogram value. Preserve it as an opaque address-sized word. */
2306Added line. append_withs ("System", false);
2307Added line. pp_string (pp, "System.Address");
2308Added line. }
2309Added line.
22952310 else if (TREE_CODE (TREE_TYPE (node)) == FUNCTION_TYPE)
22962311 {
22972312 if (VOID_TYPE_P (TREE_TYPE (TREE_TYPE (node))))
gcc/testsuite/g++.dg/ada-spec/member-pointers.C +45−0new file
Unified diff for gcc/testsuite/g++.dg/ada-spec/member-pointers.C: original line, patched line, change, source
@@ -0 +1 @@
1Added line. /* { dg-do compile } */
2Added line. /* { dg-options "-std=gnu++20 -fdump-ada-spec-slim" } */
3Added line. /* { dg-final { scan-file member_pointers_c.ads "subtype Data_Member is ptrdiff_t;" } } */
4Added line. /* { dg-final { scan-file member_pointers_c.ads "uu_pfn : System.Address;" } } */
5Added line. /* { dg-final { scan-file member_pointers_c.ads "uu_delta : aliased long;" } } */
6Added line.
7Added line. struct Data_Object
8Added line. {
9Added line. int first;
10Added line. int field;
11Added line. };
12Added line.
13Added line. using Data_Member = int Data_Object::*;
14Added line.
15Added line. struct Method_Object
16Added line. {
17Added line. virtual int virtual_method (int value) const { return value + 20; }
18Added line. int nonvirtual_method (int value) const { return value + 10; }
19Added line. };
20Added line.
21Added line. using Method_Member = int (Method_Object::*) (int) const;
22Added line.
23Added line. Data_Member get_data_member () { return &Data_Object::field; }
24Added line. Data_Member get_null_data_member () { return nullptr; }
25Added line. int apply_data_member (Data_Member member)
26Added line. {
27Added line. Data_Object object { 1, 42 };
28Added line. return object.*member;
29Added line. }
30Added line. bool is_null_data_member (Data_Member member) { return member == nullptr; }
31Added line.
32Added line. Method_Member get_nonvirtual_method ()
33Added line. {
34Added line. return &Method_Object::nonvirtual_method;
35Added line. }
36Added line. Method_Member get_virtual_method () { return &Method_Object::virtual_method; }
37Added line. Method_Member get_null_method () { return nullptr; }
38Added line. int apply_method (Method_Member member, int value)
39Added line. {
40Added line. Method_Object object;
41Added line. return (object.*member) (value);
42Added line. }
43Added line. bool is_null_method (Method_Member member) { return member == nullptr; }
44Added line.
45Added line. /* { dg-final { cleanup-ada-spec } } */
46

Tests.

member-pointers.C C++ · 45 lines
/* { dg-do compile } */
/* { dg-options "-std=gnu++20 -fdump-ada-spec-slim" } */
/* { dg-final { scan-file member_pointers_c.ads "subtype Data_Member is ptrdiff_t;" } } */
/* { dg-final { scan-file member_pointers_c.ads "uu_pfn : System.Address;" } } */
/* { dg-final { scan-file member_pointers_c.ads "uu_delta : aliased long;" } } */

struct Data_Object
{
  int first;
  int field;
};

using Data_Member = int Data_Object::*;

struct Method_Object
{
  virtual int virtual_method (int value) const { return value + 20; }
  int nonvirtual_method (int value) const { return value + 10; }
};

using Method_Member = int (Method_Object::*) (int) const;

Data_Member get_data_member () { return &Data_Object::field; }
Data_Member get_null_data_member () { return nullptr; }
int apply_data_member (Data_Member member)
{
  Data_Object object { 1, 42 };
  return object.*member;
}
bool is_null_data_member (Data_Member member) { return member == nullptr; }

Method_Member get_nonvirtual_method ()
{
  return &Method_Object::nonvirtual_method;
}
Method_Member get_virtual_method () { return &Method_Object::virtual_method; }
Method_Member get_null_method () { return nullptr; }
int apply_method (Method_Member member, int value)
{
  Method_Object object;
  return (object.*member) (value);
}
bool is_null_method (Method_Member member) { return member == nullptr; }

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

Download · View in repository

member_pointers_consumer.adb Ada · 28 lines
with Interfaces.C; use Interfaces.C;
with Interfaces.C.Extensions;
with Member_Pointers_C;

procedure Member_Pointers_Consumer is
   package Bindings renames Member_Pointers_C;
   use type Interfaces.C.Extensions.bool;

   Data : constant Bindings.Data_Member := Bindings.get_data_member;
   Null_Data : constant Bindings.Data_Member := Bindings.get_null_data_member;
   Nonvirtual : constant Bindings.Method_Member :=
     Bindings.get_nonvirtual_method;
   Virtual : constant Bindings.Method_Member := Bindings.get_virtual_method;
   Null_Method : constant Bindings.Method_Member := Bindings.get_null_method;
begin
   if Bindings.apply_data_member (Data) /= 42
     or else Bindings.is_null_data_member (Data)
       /= Interfaces.C.Extensions.bool'(False)
     or else Bindings.is_null_data_member (Null_Data)
       /= Interfaces.C.Extensions.bool'(True)
     or else Bindings.apply_method (Nonvirtual, 5) /= 15
     or else Bindings.apply_method (Virtual, 5) /= 25
     or else Bindings.is_null_method (Null_Method)
       /= Interfaces.C.Extensions.bool'(True)
   then
      raise Program_Error with "pointer-to-member ABI mismatch";
   end if;
end Member_Pointers_Consumer;

Download · View in repository

run-test.sh shell · 79 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 ]] || {
  echo "error: state must be unpatched or patched" >&2
  exit 2
}

gxx="$REGRESSION_TOOLCHAIN/bin/g++"
[[ -x "$gxx" ]] || {
  echo "error: no g++ in $REGRESSION_TOOLCHAIN" >&2
  exit 1
}

cxx_fixture="$root/bundles/cxx-ada-member-pointers/tests/member-pointers.C"
ada_fixture="$root/bundles/cxx-ada-member-pointers/tests/member_pointers_consumer.adb"
work=$(mktemp -d "${TMPDIR:-/tmp}/gnat-cxx-ada-member-pointers.XXXXXX")
trap 'rm -rf "$work"' EXIT

for optimization in 0 2; do
  case_dir="$work/O$optimization"
  mkdir -p "$case_dir"
  cp "$cxx_fixture" "$ada_fixture" "$case_dir/"
  (
    cd "$case_dir"
    "${REGRESSION_ENV[@]}" "$gxx" -std=gnu++20 -c "-O$optimization" \
      -fdump-ada-spec-slim member-pointers.C
  )

  spec="$case_dir/member_pointers_c.ads"
  [[ -f "$spec" ]] || {
    echo "error: g++ did not generate $spec" >&2
    exit 1
  }

  set +e
  (
    cd "$case_dir"
    "${REGRESSION_ENV[@]}" "$REGRESSION_GNATMAKE" -q -f \
      "-O$optimization" member_pointers_consumer.adb \
      -largs member-pointers.o -lstdc++
    "${REGRESSION_ENV[@]}" ./member_pointers_consumer
  ) >"$case_dir/run.log" 2>&1
  run_status=$?
  set -e

  if [[ "$state" == unpatched ]]; then
    [[ $run_status -ne 0 ]] || {
      echo "error: unpatched member-pointer regression unexpectedly passed" >&2
      exit 1
    }
    grep -Eiq 'missing operand|extra.*;|identifier expected' \
      "$case_dir/run.log" || {
      cat "$case_dir/run.log"
      exit 1
    }
    echo "cxx-ada-member-pointers -O$optimization: expected invalid Ada (GCC $version)"
    continue
  fi

  [[ $run_status -eq 0 ]] || {
    cat "$case_dir/run.log"
    exit 1
  }
  grep -Eq 'subtype Data_Member is ptrdiff_t;' "$spec"
  grep -Eq 'uu_pfn : System.Address;' "$spec"
  grep -Eq 'uu_delta : aliased long;' "$spec"
  echo "cxx-ada-member-pointers -O$optimization: patched (GCC $version)"
done

Download · View in repository

Commands.

Apply the patch
patch --fuzz=0 -p1 -i bundles/cxx-ada-member-pointers/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.