Skip to content

Commit 194dcab

Browse files
committed
Refine thrift audit compatibility options
Client: cpp Generated-by: OpenAI Codex (GPT-5)
1 parent 2ae9c11 commit 194dcab

20 files changed

Lines changed: 411 additions & 11 deletions

.github/workflows/build.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ jobs:
5959
run: ./bootstrap.sh
6060

6161
- name: Run configure
62-
run: ./configure --disable-debug --disable-tests --disable-libs
62+
run: ./configure --disable-debug --disable-libs
6363

6464
- name: Run make
6565
run: make -j$(nproc)
@@ -70,6 +70,9 @@ jobs:
7070
- name: Run thrift version
7171
run: /usr/local/bin/thrift -version
7272

73+
- name: Run thrift audit tests
74+
run: make -C test/audit check
75+
7376
- name: Test Delphi UUIDv8 GUID determinism
7477
run: |
7578
# Run the Delphi generator twice on the same input and verify identical output.

CMakeLists.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,10 @@ elseif(EXISTS ${THRIFT_COMPILER})
8484
set_property(TARGET thrift-compiler PROPERTY IMPORTED_LOCATION ${THRIFT_COMPILER})
8585
endif()
8686

87+
if(BUILD_TESTING AND TARGET thrift-compiler)
88+
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/test/audit)
89+
endif()
90+
8791
if(BUILD_CPP)
8892
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/lib/cpp)
8993
if(BUILD_TUTORIALS)

compiler/cpp/src/thrift/audit/t_audit.cpp

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
extern int g_warn;
2727
extern std::string g_curpath;
2828
extern bool g_return_failure;
29+
extern bool g_audit_allow_optional_field_removal;
30+
extern bool g_audit_allow_required_field_to_default;
2931

3032
void thrift_audit_warning(int level, const char* fmt, ...) {
3133
if (g_warn < level) {
@@ -49,6 +51,19 @@ void thrift_audit_failure(const char* fmt, ...) {
4951
g_return_failure = true;
5052
}
5153

54+
static bool is_allowed_field_removal(const t_field* oldField) {
55+
return g_audit_allow_optional_field_removal
56+
&& oldField->get_req() == t_field::T_OPTIONAL;
57+
}
58+
59+
static void report_field_removal(const t_field* oldField, const std::string& structName) {
60+
if (!is_allowed_field_removal(oldField)) {
61+
thrift_audit_failure("Struct Field removed for Id = %d in %s \n",
62+
oldField->get_key(),
63+
structName.c_str());
64+
}
65+
}
66+
5267
void compare_namespace(t_program* newProgram, t_program* oldProgram)
5368
{
5469
const std::map<std::string, std::string>& newNamespaceMap = newProgram->get_all_namespaces();
@@ -226,7 +241,12 @@ void compare_struct_field(t_field* newField, t_field* oldField, std::string oldS
226241
bool newStructFieldOptional = (newField->get_req() != t_field::T_REQUIRED);
227242
bool oldStructFieldOptional = (oldField->get_req() != t_field::T_REQUIRED);
228243

229-
if(newStructFieldOptional != oldStructFieldOptional)
244+
bool requiredToDefaultAllowed =
245+
g_audit_allow_required_field_to_default
246+
&& oldField->get_req() == t_field::T_REQUIRED
247+
&& newField->get_req() == t_field::T_OPT_IN_REQ_OUT;
248+
249+
if(newStructFieldOptional != oldStructFieldOptional && !requiredToDefaultAllowed)
230250
{
231251
thrift_audit_failure("Struct Field Requiredness Changed for Id = %d in %s \n", newField->get_key(), oldStructName.c_str());
232252
}
@@ -261,7 +281,7 @@ void compare_single_struct(t_struct* newStruct, t_struct* oldStruct, const std::
261281
if(newStructMemberIt == newStructMembersInIdOrder.end() && oldStructMemberIt != oldStructMembersInIdOrder.end())
262282
{
263283
// A field ID has been removed from the end.
264-
thrift_audit_failure("Struct Field removed for Id = %d in %s \n", (*oldStructMemberIt)->get_key(), structName.c_str());
284+
report_field_removal(*oldStructMemberIt, structName);
265285
oldStructMemberIt++;
266286
}
267287
else if(newStructMemberIt != newStructMembersInIdOrder.end() && oldStructMemberIt == oldStructMembersInIdOrder.end())
@@ -291,7 +311,7 @@ void compare_single_struct(t_struct* newStruct, t_struct* oldStruct, const std::
291311
else if((*newStructMemberIt)->get_key() > (*oldStructMemberIt)->get_key())
292312
{
293313
//A field is deleted in newStruct.
294-
thrift_audit_failure("Struct Field removed for Id = %d in %s \n", (*oldStructMemberIt)->get_key(), structName.c_str());
314+
report_field_removal(*oldStructMemberIt, structName);
295315
oldStructMemberIt++;
296316
}
297317

compiler/cpp/src/thrift/main.cc

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,8 @@ bool gen_recurse = false;
153153
* Flags to control thrift audit
154154
*/
155155
bool g_audit = false;
156+
bool g_audit_allow_optional_field_removal = false;
157+
bool g_audit_allow_required_field_to_default = false;
156158

157159
/**
158160
* Flag to control return status
@@ -712,6 +714,10 @@ void help() {
712714
fprintf(stderr, "\n");
713715
fprintf(stderr, "Options related to audit operation\n");
714716
fprintf(stderr, " --audit OldFile Old Thrift file to be audited with 'file'\n");
717+
fprintf(stderr, " --audit-allow-optional-field-removal\n");
718+
fprintf(stderr, " Allow explicitly optional fields to be removed\n");
719+
fprintf(stderr, " --audit-allow-required-field-to-default\n");
720+
fprintf(stderr, " Allow required fields to use default requiredness\n");
715721
fprintf(stderr, " -Iold dir Add a directory to the list of directories\n");
716722
fprintf(stderr, " searched for include directives for old thrift file\n");
717723
fprintf(stderr, " -Inew dir Add a directory to the list of directories\n");
@@ -1189,6 +1195,10 @@ int main(int argc, char** argv) {
11891195
old_input_file = string(old_thrift_file_rp);
11901196
} else if (strcmp(arg, "-audit-nofatal") == 0) {
11911197
g_audit_fatal = false;
1198+
} else if (strcmp(arg, "-audit-allow-optional-field-removal") == 0) {
1199+
g_audit_allow_optional_field_removal = true;
1200+
} else if (strcmp(arg, "-audit-allow-required-field-to-default") == 0) {
1201+
g_audit_allow_required_field_to_default = true;
11921202
} else if (strcmp(arg, "-Iold") == 0) {
11931203
arg = argv[++i];
11941204
if (arg == nullptr) {

compiler/cpp/tests/thrift_test_globals.cc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,3 +43,5 @@ std::string g_curpath;
4343
std::vector<std::string> g_incl_searchpath;
4444

4545
bool g_return_failure = false;
46+
bool g_audit_allow_optional_field_removal = false;
47+
bool g_audit_allow_required_field_to_default = false;

configure.ac

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,8 @@ AM_CONDITIONAL(WITH_LUA, [test "$have_lua" = "yes"])
286286

287287
# Find python regardless of with_python value, because it's needed by make cross
288288
AM_PATH_PYTHON(2.6,, :)
289+
AC_PATH_PROG([PERL_FOR_BUILD], [perl])
290+
AM_CONDITIONAL([HAVE_PERL_FOR_BUILD], [test -n "$PERL_FOR_BUILD"])
289291
AX_THRIFT_LIB(python, [Python], yes)
290292
if test "$with_python" = "yes"; then
291293
if test -n "$PYTHON"; then
@@ -809,6 +811,7 @@ AC_CONFIG_FILES([
809811
lib/xml/Makefile
810812
lib/xml/test/Makefile
811813
test/Makefile
814+
test/audit/Makefile
812815
test/features/Makefile
813816
test/c_glib/Makefile
814817
test/cl/Makefile

test/Makefile.am

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
# under the License.
1818
#
1919

20-
SUBDIRS = features
20+
SUBDIRS = audit features
2121
PRECROSS_TARGET =
2222

2323
if WITH_C_GLIB
@@ -121,7 +121,6 @@ distdir:
121121
$(MAKE) $(AM_MAKEFLAGS) distdir-am
122122

123123
EXTRA_DIST = \
124-
audit \
125124
c_glib \
126125
cl \
127126
cpp \

test/audit/CMakeLists.txt

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# Licensed to the Apache Software Foundation (ASF) under one
2+
# or more contributor license agreements. See the NOTICE file
3+
# distributed with this work for additional information
4+
# regarding copyright ownership. The ASF licenses this file
5+
# to you under the Apache License, Version 2.0 (the
6+
# "License"); you may not use this file except in compliance
7+
# with the License. You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing,
12+
# software distributed under the License is distributed on an
13+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
# KIND, either express or implied. See the License for the
15+
# specific language governing permissions and limitations
16+
# under the License.
17+
18+
find_package(Perl QUIET)
19+
20+
if(PERL_FOUND)
21+
add_test(
22+
NAME ThriftAuditTest
23+
COMMAND ${PERL_EXECUTABLE}
24+
${CMAKE_CURRENT_SOURCE_DIR}/thrift_audit_test.pl
25+
-f ${CMAKE_CURRENT_SOURCE_DIR}
26+
-t $<TARGET_FILE:thrift-compiler>
27+
)
28+
else()
29+
message(WARNING "Skipping ThriftAuditTest because no Perl interpreter was found.")
30+
endif()

test/audit/Makefile.am

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
# Licensed to the Apache Software Foundation (ASF) under one
2+
# or more contributor license agreements. See the NOTICE file
3+
# distributed with this work for additional information
4+
# regarding copyright ownership. The ASF licenses this file
5+
# to you under the Apache License, Version 2.0 (the
6+
# "License"); you may not use this file except in compliance
7+
# with the License. You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing,
12+
# software distributed under the License is distributed on an
13+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
# KIND, either express or implied. See the License for the
15+
# specific language governing permissions and limitations
16+
# under the License.
17+
18+
AUTOMAKE_OPTIONS = serial-tests
19+
20+
if HAVE_PERL_FOR_BUILD
21+
TESTS = thrift_audit_test.pl
22+
TESTS_ENVIRONMENT = \
23+
THRIFT_AUDIT_TEST_COMPILER='$(abs_top_builddir)/compiler/cpp/thrift'; \
24+
export THRIFT_AUDIT_TEST_COMPILER; \
25+
THRIFT_AUDIT_TEST_FIXTURES='$(abs_srcdir)'; \
26+
export THRIFT_AUDIT_TEST_FIXTURES; \
27+
$(PERL_FOR_BUILD)
28+
endif
29+
30+
EXTRA_DIST = \
31+
CMakeLists.txt \
32+
README.md \
33+
thrift_audit_test.pl \
34+
break1.thrift \
35+
break2.thrift \
36+
break3.thrift \
37+
break4.thrift \
38+
break5.thrift \
39+
break6.thrift \
40+
break7.thrift \
41+
break8.thrift \
42+
break9.thrift \
43+
break10.thrift \
44+
break11.thrift \
45+
break12.thrift \
46+
break13.thrift \
47+
break14.thrift \
48+
break15.thrift \
49+
break16.thrift \
50+
break17.thrift \
51+
break18.thrift \
52+
break19.thrift \
53+
break20.thrift \
54+
break21.thrift \
55+
break22.thrift \
56+
break23.thrift \
57+
break24.thrift \
58+
break25.thrift \
59+
break26.thrift \
60+
break27.thrift \
61+
break28.thrift \
62+
break29.thrift \
63+
break30.thrift \
64+
break31.thrift \
65+
break32.thrift \
66+
break33.thrift \
67+
break34.thrift \
68+
default_field_old.thrift \
69+
optional_field_middle_old.thrift \
70+
optional_field_middle_removed.thrift \
71+
optional_field_old.thrift \
72+
optional_field_removed.thrift \
73+
required_field_old.thrift \
74+
required_to_default_new.thrift \
75+
required_to_default_old.thrift \
76+
required_to_optional_new.thrift \
77+
test.thrift \
78+
warning.thrift

test/audit/README.md

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,19 @@ Typical usage
33
```
44
thrift.exe --audit <oldFile> <newFile>
55
```
6+
7+
Compatibility options
8+
=====================
9+
The audit remains strict by default. The following options enable specific
10+
wire-compatible changes:
11+
12+
* `--audit-allow-optional-field-removal` allows removal of fields declared
13+
`optional`. It does not allow removal of fields with default or required
14+
requiredness.
15+
* `--audit-allow-required-field-to-default` allows a field declared `required`
16+
to change to default requiredness. It does not allow changing that field to
17+
`optional`, or changing a default field to `required`.
18+
619
Example run
720
===========
821
```
@@ -16,8 +29,8 @@ Problems that the audit tool can catch
1629
Errors
1730
* Removing an enum value
1831
* Changing the type of a struct field
19-
* Changing the required-ness of a struct field
20-
* Removing a struct field
32+
* Changing the required-ness of a struct field (unless explicitly allowed)
33+
* Removing a struct field (unless explicitly allowed)
2134
* Adding a required struct field
2235
* Adding a struct field 'in the middle'. This usually indicates an old ID has been recycled
2336
* Struct removed
@@ -37,4 +50,3 @@ Warnings
3750
* Removed constant
3851
* Type of constant changed
3952
* Value of constant changed
40-

0 commit comments

Comments
 (0)