Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 5 additions & 7 deletions serialization/cqlint/unmarshal_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@ import (

const (
negInt64 = int64(-1) << 32
negInt = int(-1) << 32
)

// negInt and decInt are defined in architecture-specific files
// (unmarshal_utils_64bit.go and unmarshal_utils_32bit.go)

var errWrongDataLen = fmt.Errorf("failed to unmarshal int: the length of the data should be 0 or 4")

func errNilReference(v interface{}) error {
Expand Down Expand Up @@ -760,12 +762,8 @@ func decInt64(p []byte) int64 {
return int64(p[0])<<24 | int64(p[1])<<16 | int64(p[2])<<8 | int64(p[3])
}

func decInt(p []byte) int {
if p[0] > math.MaxInt8 {
return negInt | int(p[0])<<24 | int(p[1])<<16 | int(p[2])<<8 | int(p[3])
}
return int(p[0])<<24 | int(p[1])<<16 | int(p[2])<<8 | int(p[3])
}
// decInt is defined in architecture-specific files
// (unmarshal_utils_64bit.go and unmarshal_utils_32bit.go)

func decUint64(p []byte) uint64 {
return uint64(p[0])<<24 | uint64(p[1])<<16 | uint64(p[2])<<8 | uint64(p[3])
Expand Down
9 changes: 9 additions & 0 deletions serialization/cqlint/unmarshal_utils_32bit.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
//go:build (386 || arm || mips || mipsle) || gocql_32bit

package cqlint

// On 32-bit architectures, int is 32 bits, so we use int32 for sign extension
func decInt(p []byte) int {
// Use int32 for proper sign extension on 32-bit systems
return int(int32(p[0])<<24 | int32(p[1])<<16 | int32(p[2])<<8 | int32(p[3]))
}
16 changes: 16 additions & 0 deletions serialization/cqlint/unmarshal_utils_64bit.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
//go:build (amd64 || arm64 || ppc64 || ppc64le || mips64 || mips64le || s390x || riscv64 || loong64) && !gocql_32bit

package cqlint

import "math"

const (
negInt = int(-1) << 32
)

func decInt(p []byte) int {
if p[0] > math.MaxInt8 {
return negInt | int(p[0])<<24 | int(p[1])<<16 | int(p[2])<<8 | int(p[3])
}
return int(p[0])<<24 | int(p[1])<<16 | int(p[2])<<8 | int(p[3])
}
117 changes: 117 additions & 0 deletions serialization/cqlint/unmarshal_utils_arch_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
//go:build all || unit

package cqlint

import (
"testing"
"unsafe"
)

// TestArchitectureSpecificDecInt tests that decInt works correctly on both 32-bit and 64-bit architectures
func TestArchitectureSpecificDecInt(t *testing.T) {
t.Parallel()

intSize := int(unsafe.Sizeof(int(0)))

t.Run("int size detection", func(t *testing.T) {
if intSize != 4 && intSize != 8 {
t.Fatalf("unexpected int size: %d bytes", intSize)
}
t.Logf("Running on %d-bit architecture (int size: %d bytes)", intSize*8, intSize)
})

t.Run("decInt with positive values", func(t *testing.T) {
testCases := []struct {
name string
input []byte
expected int
}{
{"zero", []byte{0, 0, 0, 0}, 0},
{"one", []byte{0, 0, 0, 1}, 1},
{"max int8", []byte{0, 0, 0, 127}, 127},
{"max int16", []byte{0, 0, 127, 255}, 32767},
{"max int32", []byte{127, 255, 255, 255}, 2147483647},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
result := decInt(tc.input)
if result != tc.expected {
t.Errorf("decInt(%v) = %d, expected %d", tc.input, result, tc.expected)
}
})
}
})

t.Run("decInt with negative values", func(t *testing.T) {
testCases := []struct {
name string
input []byte
expected int
}{
{"minus one", []byte{255, 255, 255, 255}, -1},
{"min int8", []byte{255, 255, 255, 128}, -128},
{"min int16", []byte{255, 255, 128, 0}, -32768},
{"min int32", []byte{128, 0, 0, 0}, -2147483648},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
result := decInt(tc.input)
if result != tc.expected {
t.Errorf("decInt(%v) = %d, expected %d", tc.input, result, tc.expected)
}
})
}
})

t.Run("round trip with DecInt and EncInt", func(t *testing.T) {
testValues := []int{
0, 1, -1, 127, -128, 32767, -32768,
2147483647, -2147483648,
}

for _, value := range testValues {
encoded, err := EncInt(value)
if err != nil {
t.Errorf("EncInt(%d) failed: %v", value, err)
continue
}

var decoded int
err = DecInt(encoded, &decoded)
if err != nil {
t.Errorf("DecInt failed for value %d: %v", value, err)
continue
}

if decoded != value {
t.Errorf("Round trip failed for value %d: got %d", value, decoded)
}
}
})
}

// TestDecIntConsistency verifies that decInt produces the same results as decInt32 cast to int
func TestDecIntConsistency(t *testing.T) {
t.Parallel()

testCases := [][]byte{
{0, 0, 0, 0},
{0, 0, 0, 1},
{127, 255, 255, 255},
{255, 255, 255, 255},
{128, 0, 0, 0},
{255, 255, 255, 128},
}

for _, input := range testCases {
resultInt := decInt(input)
resultInt32 := decInt32(input)
expected := int(resultInt32)

if resultInt != expected {
t.Errorf("decInt(%v) = %d, but int(decInt32(%v)) = %d", input, resultInt, input, expected)
}
}
}
16 changes: 8 additions & 8 deletions serialization/decimal/unmarshal_ints.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@ import (
)

const (
neg8 = int64(-1) << 8
neg16 = int64(-1) << 16
neg24 = int64(-1) << 24
neg32 = int64(-1) << 32
neg40 = int64(-1) << 40
neg48 = int64(-1) << 48
neg56 = int64(-1) << 56
neg32Int = int(-1) << 32
neg8 = int64(-1) << 8
neg16 = int64(-1) << 16
neg24 = int64(-1) << 24
neg32 = int64(-1) << 32
neg40 = int64(-1) << 40
neg48 = int64(-1) << 48
neg56 = int64(-1) << 56
// neg32Int was defined but never used, so it has been removed
)

func decScale(p []byte) inf.Scale {
Expand Down
25 changes: 1 addition & 24 deletions serialization/varint/marshal_ints.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,27 +104,4 @@ func EncInt64Ext(v int64) []byte {
return []byte{byte(v >> 56), byte(v >> 48), byte(v >> 40), byte(v >> 32), byte(v >> 24), byte(v >> 16), byte(v >> 8), byte(v)}
}

func encInt(v int) []byte {
if v <= maxInt8 && v >= minInt8 {
return []byte{byte(v)}
}
if v <= maxInt16 && v >= minInt16 {
return []byte{byte(v >> 8), byte(v)}
}
if v <= maxInt24 && v >= minInt24 {
return []byte{byte(v >> 16), byte(v >> 8), byte(v)}
}
if v <= maxInt32 && v >= minInt32 {
return []byte{byte(v >> 24), byte(v >> 16), byte(v >> 8), byte(v)}
}
if v <= maxInt40 && v >= minInt40 {
return []byte{byte(v >> 32), byte(v >> 24), byte(v >> 16), byte(v >> 8), byte(v)}
}
if v <= maxInt48 && v >= minInt48 {
return []byte{byte(v >> 40), byte(v >> 32), byte(v >> 24), byte(v >> 16), byte(v >> 8), byte(v)}
}
if v <= maxInt56 && v >= minInt56 {
return []byte{byte(v >> 48), byte(v >> 40), byte(v >> 32), byte(v >> 24), byte(v >> 16), byte(v >> 8), byte(v)}
}
return []byte{byte(v >> 56), byte(v >> 48), byte(v >> 40), byte(v >> 32), byte(v >> 24), byte(v >> 16), byte(v >> 8), byte(v)}
}
// encInt is defined in architecture-specific files (marshal_ints_64bit.go and marshal_ints_32bit.go)
18 changes: 18 additions & 0 deletions serialization/varint/marshal_ints_32bit.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
//go:build (386 || arm || mips || mipsle) || gocql_32bit

package varint

// On 32-bit architectures, int is 32 bits maximum, so we only support up to 4 bytes
func encInt(v int) []byte {
if v <= maxInt8 && v >= minInt8 {
return []byte{byte(v)}
}
if v <= maxInt16 && v >= minInt16 {
return []byte{byte(v >> 8), byte(v)}
}
if v <= maxInt24 && v >= minInt24 {
return []byte{byte(v >> 16), byte(v >> 8), byte(v)}
}
// On 32-bit, int max is int32, so this is the final case
return []byte{byte(v >> 24), byte(v >> 16), byte(v >> 8), byte(v)}
}
28 changes: 28 additions & 0 deletions serialization/varint/marshal_ints_64bit.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
//go:build (amd64 || arm64 || ppc64 || ppc64le || mips64 || mips64le || s390x || riscv64 || loong64) && !gocql_32bit

package varint

func encInt(v int) []byte {
if v <= maxInt8 && v >= minInt8 {
return []byte{byte(v)}
}
if v <= maxInt16 && v >= minInt16 {
return []byte{byte(v >> 8), byte(v)}
}
if v <= maxInt24 && v >= minInt24 {
return []byte{byte(v >> 16), byte(v >> 8), byte(v)}
}
if v <= maxInt32 && v >= minInt32 {
return []byte{byte(v >> 24), byte(v >> 16), byte(v >> 8), byte(v)}
}
if v <= maxInt40 && v >= minInt40 {
return []byte{byte(v >> 32), byte(v >> 24), byte(v >> 16), byte(v >> 8), byte(v)}
}
if v <= maxInt48 && v >= minInt48 {
return []byte{byte(v >> 40), byte(v >> 32), byte(v >> 24), byte(v >> 16), byte(v >> 8), byte(v)}
}
if v <= maxInt56 && v >= minInt56 {
return []byte{byte(v >> 48), byte(v >> 40), byte(v >> 32), byte(v >> 24), byte(v >> 16), byte(v >> 8), byte(v)}
}
return []byte{byte(v >> 56), byte(v >> 48), byte(v >> 40), byte(v >> 32), byte(v >> 24), byte(v >> 16), byte(v >> 8), byte(v)}
}
84 changes: 84 additions & 0 deletions serialization/varint/marshal_ints_arch_64bit_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
//go:build ((all || unit) && (amd64 || arm64 || ppc64 || ppc64le || mips64 || mips64le || s390x || riscv64 || loong64)) && !gocql_32bit

Check failure on line 1 in serialization/varint/marshal_ints_arch_64bit_test.go

View workflow job for this annotation

GitHub Actions / Build

File is not properly formatted (goimports)

package varint

import (
"testing"
)

// Test64BitIntOperations tests int operations that only work on 64-bit architectures
func Test64BitIntOperations(t *testing.T) {
t.Parallel()

testCases := []struct {
name string
value int
bytes int
}{
{"max int8", 127, 1},
{"min int8", -128, 1},
{"max int16", 32767, 2},
{"min int16", -32768, 2},
{"max int24", 8388607, 3},
{"min int24", -8388608, 3},
{"max int32", 2147483647, 4},
{"min int32", -2147483648, 4},
{"max int40", 549755813887, 5},
{"min int40", -549755813888, 5},
{"max int48", 140737488355327, 6},
{"min int48", -140737488355328, 6},
{"max int56", 36028797018963967, 7},
{"min int56", -36028797018963968, 7},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
// Test marshal
encoded := encInt(tc.value)
if len(encoded) != tc.bytes {
t.Errorf("encInt(%d) returned %d bytes, expected %d", tc.value, len(encoded), tc.bytes)
}

// Test unmarshal
var decoded int
err := DecInt(encoded, &decoded)
if err != nil {
t.Errorf("DecInt failed: %v", err)
}
if decoded != tc.value {
t.Errorf("DecInt(%x) = %d, expected %d", encoded, decoded, tc.value)
}

// Test unmarshal with pointer
var decodedPtr *int
err = DecIntR(encoded, &decodedPtr)
if err != nil {
t.Errorf("DecIntR failed: %v", err)
}
if decodedPtr == nil || *decodedPtr != tc.value {
t.Errorf("DecIntR(%x) = %v, expected %d", encoded, decodedPtr, tc.value)
}
})
}

// Round trip test with 64-bit values
t.Run("round trip with large values", func(t *testing.T) {
testValues := []int{
549755813887, -549755813888,
140737488355327, -140737488355328,
}

for _, value := range testValues {
encoded := encInt(value)
var decoded int
err := DecInt(encoded, &decoded)
if err != nil {
t.Errorf("DecInt failed for value %d: %v", value, err)
continue
}
if decoded != value {
t.Errorf("Round trip failed for value %d: got %d", value, decoded)
}
}
})
}
Loading
Loading