Skip to content

Commit eea9fa9

Browse files
committed
Add defenses against unexpected changes in the NodeTag enum list.
Having different build systems producing different contents of the NodeTag enum would be catastrophic for extension ABI stability. But that ordering depends on the order in which gen_node_support.pl processes its input files. It seems too fragile to let the Makefiles, MSVC build scripts, and soon meson build scripts all set this order independently. As a klugy but serviceable solution, put a canonical copy of the file list into gen_node_support.pl itself, and check that against the files given on the command line. Also, while it's fine to add and delete node tags during development, we must not let the assigned NodeTag values change unexpectedly in stable branches. Add a cross-check that can be enabled when a branch is forked off (or later, but that is a time when we're unlikely to miss doing it). It just checks that the last auto-assigned number doesn't change, which is simplistic but will catch the most likely sorts of mistakes. From time to time we do need to add a node tag in a stable branch. To support doing that without changing the branch's auto-assigned tag numbers, invent pg_node_attr(nodetag_number(VALUE)) which can be used to give such a node a hand-assigned tag above the last auto-assigned one. Discussion: https://postgr.es/m/1249010.1657574337@sss.pgh.pa.us
1 parent ca187d7 commit eea9fa9

File tree

3 files changed

+113
-22
lines changed

3 files changed

+113
-22
lines changed

src/backend/nodes/gen_node_support.pl

Lines changed: 104 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,72 @@ sub elem
3434
return grep { $_ eq $x } @_;
3535
}
3636

37+
38+
# This list defines the canonical set of header files to be read by this
39+
# script, and the order they are to be processed in. We must have a stable
40+
# processing order, else the NodeTag enum's order will vary, with catastrophic
41+
# consequences for ABI stability across different builds.
42+
#
43+
# Currently, the various build systems also have copies of this list,
44+
# so that they can do dependency checking properly. In future we may be
45+
# able to make this list the only copy. For now, we just check that
46+
# it matches the list of files passed on the command line.
47+
my @all_input_files = qw(
48+
nodes/nodes.h
49+
nodes/primnodes.h
50+
nodes/parsenodes.h
51+
nodes/pathnodes.h
52+
nodes/plannodes.h
53+
nodes/execnodes.h
54+
access/amapi.h
55+
access/sdir.h
56+
access/tableam.h
57+
access/tsmapi.h
58+
commands/event_trigger.h
59+
commands/trigger.h
60+
executor/tuptable.h
61+
foreign/fdwapi.h
62+
nodes/extensible.h
63+
nodes/lockoptions.h
64+
nodes/replnodes.h
65+
nodes/supportnodes.h
66+
nodes/value.h
67+
utils/rel.h
68+
);
69+
70+
# Nodes from these input files are automatically treated as nodetag_only.
71+
# In the future we might add explicit pg_node_attr labeling to some of these
72+
# files and remove them from this list, but for now this is the path of least
73+
# resistance.
74+
my @nodetag_only_files = qw(
75+
nodes/execnodes.h
76+
access/amapi.h
77+
access/sdir.h
78+
access/tableam.h
79+
access/tsmapi.h
80+
commands/event_trigger.h
81+
commands/trigger.h
82+
executor/tuptable.h
83+
foreign/fdwapi.h
84+
nodes/lockoptions.h
85+
nodes/replnodes.h
86+
nodes/supportnodes.h
87+
);
88+
89+
# ARM ABI STABILITY CHECK HERE:
90+
#
91+
# In stable branches, set $last_nodetag to the name of the last node type
92+
# that should receive an auto-generated nodetag number, and $last_nodetag_no
93+
# to its number. (Find these values in the last line of the current
94+
# nodetags.h file.) The script will then complain if those values don't
95+
# match reality, providing a cross-check that we haven't broken ABI by
96+
# adding or removing nodetags.
97+
# In HEAD, these variables should be left undef, since we don't promise
98+
# ABI stability during development.
99+
100+
my $last_nodetag = undef;
101+
my $last_nodetag_no = undef;
102+
37103
# output file names
38104
my @output_files;
39105

@@ -90,32 +156,16 @@ sub elem
90156
# Similarly for custom read/write implementations.
91157
my @custom_read_write;
92158

159+
# Track node types with manually assigned NodeTag numbers.
160+
my %manual_nodetag_number;
161+
93162
# EquivalenceClasses are never moved, so just shallow-copy the pointer
94163
push @scalar_types, qw(EquivalenceClass* EquivalenceMember*);
95164

96165
# This is a struct, so we can copy it by assignment. Equal support is
97166
# currently not required.
98167
push @scalar_types, qw(QualCost);
99168

100-
# Nodes from these input files are automatically treated as nodetag_only.
101-
# In the future we might add explicit pg_node_attr labeling to some of these
102-
# files and remove them from this list, but for now this is the path of least
103-
# resistance.
104-
my @nodetag_only_files = qw(
105-
nodes/execnodes.h
106-
access/amapi.h
107-
access/sdir.h
108-
access/tableam.h
109-
access/tsmapi.h
110-
commands/event_trigger.h
111-
commands/trigger.h
112-
executor/tuptable.h
113-
foreign/fdwapi.h
114-
nodes/lockoptions.h
115-
nodes/replnodes.h
116-
nodes/supportnodes.h
117-
);
118-
119169
# XXX various things we are not publishing right now to stay level
120170
# with the manual system
121171
push @no_read_write,
@@ -134,8 +184,13 @@ sub elem
134184
TriggerTransition TypeCast TypeName WindowDef WithClause XmlSerialize);
135185

136186

187+
## check that we have the expected number of files on the command line
188+
die "wrong number of input files, expected @all_input_files\n"
189+
if ($#ARGV != $#all_input_files);
190+
137191
## read input
138192

193+
my $next_input_file = 0;
139194
foreach my $infile (@ARGV)
140195
{
141196
my $in_struct;
@@ -150,11 +205,17 @@ sub elem
150205
my %my_field_types;
151206
my %my_field_attrs;
152207

208+
# open file with name from command line, which may have a path prefix
153209
open my $ifh, '<', $infile or die "could not open \"$infile\": $!";
154210

155211
# now shorten filename for use below
156212
$infile =~ s!.*src/include/!!;
157213

214+
# check it against next member of @all_input_files
215+
die "wrong input file ordering, expected @all_input_files\n"
216+
if ($infile ne $all_input_files[$next_input_file]);
217+
$next_input_file++;
218+
158219
my $raw_file_content = do { local $/; <$ifh> };
159220

160221
# strip C comments, preserving newlines so we can count lines correctly
@@ -274,6 +335,10 @@ sub elem
274335
# does in fact exist.
275336
push @no_read_write, $in_struct;
276337
}
338+
elsif ($attr =~ /^nodetag_number\((\d+)\)$/)
339+
{
340+
$manual_nodetag_number{$in_struct} = $1;
341+
}
277342
else
278343
{
279344
die
@@ -475,14 +540,31 @@ sub elem
475540

476541
printf $nt $header_comment, 'nodetags.h';
477542

478-
my $i = 1;
543+
my $tagno = 0;
544+
my $last_tag = undef;
479545
foreach my $n (@node_types, @extra_tags)
480546
{
481547
next if elem $n, @abstract_types;
482-
print $nt "\tT_${n} = $i,\n";
483-
$i++;
548+
if (defined $manual_nodetag_number{$n})
549+
{
550+
# do not change $tagno or $last_tag
551+
print $nt "\tT_${n} = $manual_nodetag_number{$n},\n";
552+
}
553+
else
554+
{
555+
$tagno++;
556+
$last_tag = $n;
557+
print $nt "\tT_${n} = $tagno,\n";
558+
}
484559
}
485560

561+
# verify that last auto-assigned nodetag stays stable
562+
die "ABI stability break: last nodetag is $last_tag not $last_nodetag\n"
563+
if (defined $last_nodetag && $last_nodetag ne $last_tag);
564+
die
565+
"ABI stability break: last nodetag number is $tagno not $last_nodetag_no\n"
566+
if (defined $last_nodetag_no && $last_nodetag_no != $tagno);
567+
486568
close $nt;
487569

488570

src/include/nodes/nodes.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,11 @@ typedef enum NodeTag
6666
*
6767
* - special_read_write: Has special treatment in outNode() and nodeRead().
6868
*
69+
* - nodetag_number(VALUE): assign the specified nodetag number instead of
70+
* an auto-generated number. Typically this would only be used in stable
71+
* branches, to give a newly-added node type a number without breaking ABI
72+
* by changing the numbers of existing node types.
73+
*
6974
* Node types can be supertypes of other types whether or not they are marked
7075
* abstract: if a node struct appears as the first field of another struct
7176
* type, then it is the supertype of that type. The no_copy, no_equal, and

src/tools/RELEASE_CHANGES

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,10 @@ Starting a New Development Cycle
107107
placeholder), "git rm" the previous one, and update release.sgml and
108108
filelist.sgml to match.
109109

110+
* In the newly-made branch, change src/backend/nodes/gen_node_support.pl
111+
to enforce ABI stability of the NodeTag list (see "ARM ABI STABILITY
112+
CHECK HERE" therein).
113+
110114
* Notify the private committers email list, to ensure all committers
111115
are aware of the new branch even if they're not paying close attention
112116
to pgsql-hackers.

0 commit comments

Comments
 (0)