Skip to content

Commit 645beaf

Browse files
kpumukcodex
authored andcommitted
THRIFT-5941: Add Ruby ext cppcheck coverage
Client: rb Co-Authored-By: OpenAI Codex (GPT-5.4) <codex@openai.com>
1 parent 550529f commit 645beaf

5 files changed

Lines changed: 119 additions & 39 deletions

File tree

.github/workflows/sca.yml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,21 @@ jobs:
146146
-i lib/c_glib/test/gen-cpp \
147147
--error-exitcode=1 -j2 lib/c_glib/src lib/c_glib/test test/c_glib/src tutorial/c_glib
148148
149+
# Ruby C extension error checks
150+
# suppress missingIncludeSystem:lib/rb/ext/* -> Ruby and libc headers are not needed for analysis.
151+
# suppress checkersReport -> hide info-only active-checker summary.
152+
# Use -j1 with a build dir: build-dir avoids a large staticFunction false-positive set in serial scans,
153+
# and -j1 avoids the -j2 whole-program path where Init_thrift_native's inline unusedFunction suppression misbinds.
154+
mkdir -p /tmp/cppcheck-rb-ext
155+
cppcheck --force --quiet --inline-suppr --enable=all \
156+
-j1 --cppcheck-build-dir=/tmp/cppcheck-rb-ext \
157+
-I lib/rb/ext \
158+
--library=ruby \
159+
--suppress="missingIncludeSystem:lib/rb/ext/*" \
160+
--suppress="checkersReport" \
161+
--check-level=exhaustive \
162+
--error-exitcode=1 lib/rb/ext
163+
149164
- name: Run flake8
150165
id: flake8
151166
continue-on-error: true

lib/rb/ext/compact_protocol.c

Lines changed: 40 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -124,33 +124,41 @@ static void write_field_begin_internal(VALUE self, VALUE type, VALUE id_value, V
124124
SET_LAST_ID(self, id_value);
125125
}
126126

127-
static int32_t int_to_zig_zag(int32_t n) {
128-
return (n << 1) ^ (n >> 31);
127+
static uint32_t int_to_zig_zag(int32_t n) {
128+
return (((uint32_t)n) << 1) ^ (0U - (uint32_t)(n < 0));
129129
}
130130

131131
static uint64_t ll_to_zig_zag(int64_t n) {
132-
return (n << 1) ^ (n >> 63);
132+
return (((uint64_t)n) << 1) ^ (0ULL - (uint64_t)(n < 0));
133+
}
134+
135+
static uint32_t message_seqid_to_varint32(int32_t seqid) {
136+
return seqid < 0 ? (uint32_t)((int64_t)seqid + (INT64_C(1) << 32)) : (uint32_t)seqid;
137+
}
138+
139+
static int32_t message_seqid_from_varint32(uint32_t seqid) {
140+
return seqid > INT32_MAX ? (int32_t)((int64_t)seqid - (INT64_C(1) << 32)) : (int32_t)seqid;
133141
}
134142

135143
static void write_varint32(VALUE transport, uint32_t n) {
136144
while (true) {
137-
if ((n & ~0x7F) == 0) {
138-
write_byte_direct(transport, n & 0x7f);
145+
if ((n & ~0x7FU) == 0U) {
146+
write_byte_direct(transport, n & 0x7FU);
139147
break;
140148
} else {
141-
write_byte_direct(transport, (n & 0x7F) | 0x80);
149+
write_byte_direct(transport, (n & 0x7FU) | 0x80U);
142150
n = n >> 7;
143151
}
144152
}
145153
}
146154

147155
static void write_varint64(VALUE transport, uint64_t n) {
148156
while (true) {
149-
if ((n & ~0x7F) == 0) {
150-
write_byte_direct(transport, n & 0x7f);
157+
if ((n & ~0x7FULL) == 0ULL) {
158+
write_byte_direct(transport, n & 0x7FULL);
151159
break;
152160
} else {
153-
write_byte_direct(transport, (n & 0x7F) | 0x80);
161+
write_byte_direct(transport, (n & 0x7FULL) | 0x80ULL);
154162
n = n >> 7;
155163
}
156164
}
@@ -208,9 +216,10 @@ VALUE rb_thrift_compact_proto_write_set_end(VALUE self) {
208216

209217
VALUE rb_thrift_compact_proto_write_message_begin(VALUE self, VALUE name, VALUE type, VALUE seqid) {
210218
VALUE transport = GET_TRANSPORT(self);
219+
int32_t seqid_value = FIX2INT(seqid);
211220
write_byte_direct(transport, PROTOCOL_ID);
212221
write_byte_direct(transport, (VERSION & VERSION_MASK) | ((FIX2INT(type) << TYPE_SHIFT_AMOUNT) & TYPE_MASK));
213-
write_varint32(transport, FIX2INT(seqid));
222+
write_varint32(transport, message_seqid_to_varint32(seqid_value));
214223
rb_thrift_compact_proto_write_string(self, name);
215224

216225
return Qnil;
@@ -419,20 +428,20 @@ static char read_byte_direct(VALUE self) {
419428
return (char)(FIX2INT(byte));
420429
}
421430

422-
static int64_t zig_zag_to_ll(int64_t n) {
423-
return (((uint64_t)n) >> 1) ^ -(n & 1);
431+
static int64_t zig_zag_to_ll(uint64_t n) {
432+
return (int64_t)((n >> 1) ^ (0ULL - (n & 1ULL)));
424433
}
425434

426-
static int32_t zig_zag_to_int(int32_t n) {
427-
return (((uint32_t)n) >> 1) ^ -(n & 1);
435+
static int32_t zig_zag_to_int(uint32_t n) {
436+
return (int32_t)((n >> 1) ^ (0U - (n & 1U)));
428437
}
429438

430-
static int64_t read_varint64(VALUE self) {
439+
static uint64_t read_varint64(VALUE self) {
431440
int shift = 0;
432-
int64_t result = 0;
441+
uint64_t result = 0;
433442
while (true) {
434443
int8_t b = read_byte_direct(self);
435-
result = result | ((uint64_t)(b & 0x7f) << shift);
444+
result |= ((uint64_t)(b & 0x7f) << shift);
436445
if ((b & 0x80) != 0x80) {
437446
break;
438447
}
@@ -441,8 +450,12 @@ static int64_t read_varint64(VALUE self) {
441450
return result;
442451
}
443452

453+
static uint32_t read_varint32(VALUE self) {
454+
return (uint32_t)read_varint64(self);
455+
}
456+
444457
static int16_t read_i16(VALUE self) {
445-
return zig_zag_to_int((int32_t)read_varint64(self));
458+
return (int16_t)zig_zag_to_int(read_varint32(self));
446459
}
447460

448461
VALUE rb_thrift_compact_proto_read_message_end(VALUE self) {
@@ -494,7 +507,7 @@ VALUE rb_thrift_compact_proto_read_message_begin(VALUE self) {
494507
}
495508

496509
int8_t type = (version_and_type >> TYPE_SHIFT_AMOUNT) & TYPE_BITS;
497-
int32_t seqid = (int32_t)read_varint64(self);
510+
int32_t seqid = message_seqid_from_varint32(read_varint32(self));
498511
VALUE messageName = rb_thrift_compact_proto_read_string(self);
499512
return rb_ary_new3(3, messageName, INT2FIX(type), INT2NUM(seqid));
500513
}
@@ -532,19 +545,19 @@ VALUE rb_thrift_compact_proto_read_field_begin(VALUE self) {
532545
}
533546

534547
VALUE rb_thrift_compact_proto_read_map_begin(VALUE self) {
535-
int32_t size = (int32_t)read_varint64(self);
548+
uint32_t size = read_varint32(self);
536549
uint8_t key_and_value_type = size == 0 ? 0 : read_byte_direct(self);
537-
return rb_ary_new3(3, INT2FIX(get_ttype(key_and_value_type >> 4)), INT2FIX(get_ttype(key_and_value_type & 0xf)), INT2FIX(size));
550+
return rb_ary_new3(3, INT2FIX(get_ttype(key_and_value_type >> 4)), INT2FIX(get_ttype(key_and_value_type & 0xf)), UINT2NUM(size));
538551
}
539552

540553
VALUE rb_thrift_compact_proto_read_list_begin(VALUE self) {
541554
uint8_t size_and_type = read_byte_direct(self);
542-
int32_t size = (size_and_type >> 4) & 0x0f;
555+
uint32_t size = (size_and_type >> 4) & 0x0f;
543556
if (size == 15) {
544-
size = (int32_t)read_varint64(self);
557+
size = read_varint32(self);
545558
}
546559
uint8_t type = get_ttype(size_and_type & 0x0f);
547-
return rb_ary_new3(2, INT2FIX(type), INT2FIX(size));
560+
return rb_ary_new3(2, INT2FIX(type), UINT2NUM(size));
548561
}
549562

550563
VALUE rb_thrift_compact_proto_read_set_begin(VALUE self) {
@@ -570,7 +583,7 @@ VALUE rb_thrift_compact_proto_read_i16(VALUE self) {
570583
}
571584

572585
VALUE rb_thrift_compact_proto_read_i32(VALUE self) {
573-
return INT2NUM(zig_zag_to_int((int32_t)read_varint64(self)));
586+
return INT2NUM(zig_zag_to_int(read_varint32(self)));
574587
}
575588

576589
VALUE rb_thrift_compact_proto_read_i64(VALUE self) {
@@ -603,8 +616,8 @@ VALUE rb_thrift_compact_proto_read_string(VALUE self) {
603616
}
604617

605618
VALUE rb_thrift_compact_proto_read_binary(VALUE self) {
606-
int64_t size = read_varint64(self);
607-
return READ(self, size);
619+
uint32_t size = read_varint32(self);
620+
return rb_funcall(GET_TRANSPORT(self), read_all_method_id, 1, UINT2NUM(size));
608621
}
609622

610623
VALUE rb_thrift_compact_proto_read_uuid(VALUE self) {

lib/rb/ext/struct.c

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -252,8 +252,6 @@ static void write_container(int ttype, VALUE field_info, VALUE value, VALUE prot
252252

253253
if (ttype == TTYPE_MAP) {
254254
VALUE keys;
255-
VALUE key;
256-
VALUE val;
257255

258256
Check_Type(value, T_HASH);
259257

@@ -272,8 +270,8 @@ static void write_container(int ttype, VALUE field_info, VALUE value, VALUE prot
272270
default_write_map_begin(protocol, keytype_value, valuetype_value, INT2FIX(sz));
273271

274272
for (i = 0; i < sz; i++) {
275-
key = rb_ary_entry(keys, i);
276-
val = rb_hash_aref(value, key);
273+
VALUE key = rb_ary_entry(keys, i);
274+
VALUE val = rb_hash_aref(value, key);
277275

278276
if (IS_CONTAINER(keytype)) {
279277
write_container(keytype, key_info, key, protocol);
@@ -489,8 +487,6 @@ static VALUE read_anything(VALUE protocol, int ttype, VALUE field_info) {
489487
rb_thrift_struct_read(result, protocol);
490488
}
491489
} else if (ttype == TTYPE_MAP) {
492-
int i;
493-
494490
VALUE map_header = default_read_map_begin(protocol);
495491
int key_ttype = FIX2INT(rb_ary_entry(map_header, 0));
496492
int value_ttype = FIX2INT(rb_ary_entry(map_header, 1));
@@ -511,7 +507,7 @@ static VALUE read_anything(VALUE protocol, int ttype, VALUE field_info) {
511507
if (num_entries == 0 || (specified_key_type == key_ttype && specified_value_type == value_ttype)) {
512508
result = rb_hash_new();
513509

514-
for (i = 0; i < num_entries; ++i) {
510+
for (int i = 0; i < num_entries; ++i) {
515511
VALUE key, val;
516512

517513
key = read_anything(protocol, key_ttype, key_info);
@@ -528,8 +524,6 @@ static VALUE read_anything(VALUE protocol, int ttype, VALUE field_info) {
528524

529525
default_read_map_end(protocol);
530526
} else if (ttype == TTYPE_LIST) {
531-
int i;
532-
533527
VALUE list_header = default_read_list_begin(protocol);
534528
int element_ttype = FIX2INT(rb_ary_entry(list_header, 0));
535529
int num_elements = FIX2INT(rb_ary_entry(list_header, 1));
@@ -542,7 +536,7 @@ static VALUE read_anything(VALUE protocol, int ttype, VALUE field_info) {
542536
if (specified_element_type == element_ttype) {
543537
result = new_container_array(num_elements);
544538

545-
for (i = 0; i < num_elements; ++i) {
539+
for (int i = 0; i < num_elements; ++i) {
546540
rb_ary_push(result, read_anything(protocol, element_ttype, rb_hash_aref(field_info, element_sym)));
547541
}
548542
} else {
@@ -555,7 +549,6 @@ static VALUE read_anything(VALUE protocol, int ttype, VALUE field_info) {
555549
default_read_list_end(protocol);
556550
} else if (ttype == TTYPE_SET) {
557551
VALUE items;
558-
int i;
559552

560553
VALUE set_header = default_read_set_begin(protocol);
561554
int element_ttype = FIX2INT(rb_ary_entry(set_header, 0));
@@ -569,7 +562,7 @@ static VALUE read_anything(VALUE protocol, int ttype, VALUE field_info) {
569562
if (specified_element_type == element_ttype) {
570563
items = new_container_array(num_elements);
571564

572-
for (i = 0; i < num_elements; ++i) {
565+
for (int i = 0; i < num_elements; ++i) {
573566
rb_ary_push(items, read_anything(protocol, element_ttype, rb_hash_aref(field_info, element_sym)));
574567
}
575568

lib/rb/ext/thrift_native.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ int PROTOERR_BAD_VERSION;
121121
int PROTOERR_NOT_IMPLEMENTED;
122122
int PROTOERR_DEPTH_LIMIT;
123123

124+
// cppcheck-suppress unusedFunction
124125
RUBY_FUNC_EXPORTED void Init_thrift_native(void) {
125126
// cached classes
126127
thrift_module = rb_const_get(rb_cObject, rb_intern("Thrift"));

lib/rb/spec/compact_protocol_spec.rb

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,16 @@
2121
require 'spec_helper'
2222

2323
describe Thrift::CompactProtocol do
24+
INTEGER_BOUNDARY_TESTS = {
25+
:i32 => [-(2**31), (2**31) - 1],
26+
:i64 => [-(2**63), (2**63) - 1]
27+
}
28+
29+
INTEGER_MINIMUM_ENCODINGS = {
30+
:i32 => [0xff, 0xff, 0xff, 0xff, 0x0f],
31+
:i64 => [0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01]
32+
}
33+
2434
TESTS = {
2535
:byte => (-127..127).to_a,
2636
:i16 => (0..14).map { |shift| [1 << shift, -(1 << shift)] }.flatten.sort,
@@ -71,6 +81,54 @@
7181
end
7282
end
7383

84+
it "should round-trip signed integer boundaries correctly" do
85+
INTEGER_BOUNDARY_TESTS.each_pair do |primitive_type, test_values|
86+
test_values.each do |value|
87+
trans = Thrift::MemoryBufferTransport.new
88+
proto = Thrift::CompactProtocol.new(trans)
89+
90+
proto.send(writer(primitive_type), value)
91+
expect(proto.send(reader(primitive_type))).to eq(value)
92+
end
93+
end
94+
end
95+
96+
it "should encode signed integer minima with the canonical zigzag varint bytes" do
97+
INTEGER_MINIMUM_ENCODINGS.each_pair do |primitive_type, expected_bytes|
98+
trans = Thrift::MemoryBufferTransport.new
99+
proto = Thrift::CompactProtocol.new(trans)
100+
101+
proto.send(writer(primitive_type), INTEGER_BOUNDARY_TESTS.fetch(primitive_type).first)
102+
expect(trans.read(trans.available).bytes).to eq(expected_bytes)
103+
end
104+
end
105+
106+
it "should decode i32 minima from direct canonical zigzag bytes" do
107+
trans = Thrift::MemoryBufferTransport.new
108+
trans.write(INTEGER_MINIMUM_ENCODINGS[:i32].pack("C*"))
109+
110+
proto = Thrift::CompactProtocol.new(trans)
111+
expect(proto.read_i32).to eq(INTEGER_BOUNDARY_TESTS[:i32].first)
112+
end
113+
114+
it "should decode i64 minima from direct canonical zigzag bytes" do
115+
trans = Thrift::MemoryBufferTransport.new
116+
trans.write(INTEGER_MINIMUM_ENCODINGS[:i64].pack("C*"))
117+
118+
proto = Thrift::CompactProtocol.new(trans)
119+
expect(proto.read_i64).to eq(INTEGER_BOUNDARY_TESTS[:i64].first)
120+
end
121+
122+
it "should read binary values with multi-byte varint32 lengths" do
123+
payload = "x" * 128
124+
trans = Thrift::MemoryBufferTransport.new
125+
trans.write([0x80, 0x01].pack("C*"))
126+
trans.write(payload)
127+
128+
proto = Thrift::CompactProtocol.new(trans)
129+
expect(proto.read_binary).to eq(payload)
130+
end
131+
74132
it "should write a uuid" do
75133
trans = Thrift::MemoryBufferTransport.new
76134
proto = Thrift::CompactProtocol.new(trans)

0 commit comments

Comments
 (0)