Skip to content

Commit b716774

Browse files
authored
fix: panic when JOIN condition type conversion fails (#23191)
### **User description** ## What type of PR is this? - [ ] API-change - [x] BUG - [ ] Improvement - [ ] Documentation - [ ] Feature - [ ] Test and CI - [ ] Code Refactoring ## Which issue(s) this PR fixes: issue matrixorigin/MO-Cloud#6733 ## What this PR does / why we need it: When a JOIN operation encounters a type mismatch (e.g., INT = VARCHAR), the type conversion fails during evalJoinCondition(), leaving HashmapBuilder.vecs in a partially initialized state. When Reset() is called during cleanup, it attempts to free nil pointers, causing a panic. ___ ### **PR Type** Bug fix, Tests ___ ### **Description** - Add nil pointer checks in `Reset()` and `Free()` methods to prevent panics - Introduce `cleanupPartiallyCreatedVecs()` helper to handle partial initialization failures - Call cleanup on errors in `evalJoinCondition()` to prevent memory leaks - Add comprehensive regression tests for nil pointer handling in Reset/Free - Add BVT test case for LEFT JOIN with type mismatch error handling ___ ### Diagram Walkthrough ```mermaid flowchart LR A["evalJoinCondition<br/>encounters error"] -->|calls| B["cleanupPartiallyCreatedVecs"] B -->|frees partial vecs| C["hb.vecs = nil"] C -->|prevents nil deref| D["Reset/Free safe"] E["Reset/Free methods"] -->|check nil| F["vecs[i] != nil"] F -->|check nil| G["vecs[i][j] != nil"] G -->|safe free| H["No panic"] ``` <details> <summary><h3> File Walkthrough</h3></summary> <table><thead><tr><th></th><th align="left">Relevant files</th></tr></thead><tbody><tr><td><strong>Bug fix</strong></td><td><table> <tr> <td> <details> <summary><strong>hashmap_util.go</strong><dd><code>Add nil pointer safety checks and cleanup helper</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> pkg/sql/colexec/hashmap_util/hashmap_util.go <ul><li>Add nil pointer checks in <code>Reset()</code> method for <code>hb.vecs</code> and <br><code>hb.UniqueJoinKeys</code> arrays<br> <li> Add nil pointer checks in <code>Free()</code> method for <code>hb.UniqueJoinKeys</code> array<br> <li> Introduce new <code>cleanupPartiallyCreatedVecs()</code> helper function to safely <br>free partially initialized vectors<br> <li> Call <code>cleanupPartiallyCreatedVecs()</code> in <code>evalJoinCondition()</code> when errors <br>occur during vector evaluation or duplication</ul> </details> </td> <td><a href="https://github.com/matrixorigin/matrixone/pull/23191/files#diff-4e472814399bbb0cac40fbad6d42daabca4111e950bebe1a2f5c4dbf8560bd74">+32/-4</a>&nbsp; &nbsp; </td> </tr> </table></td></tr><tr><td><strong>Tests</strong></td><td><table> <tr> <td> <details> <summary><strong>hashmap_util_test.go</strong><dd><code>Add comprehensive nil pointer regression tests</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> pkg/sql/colexec/hashmap_util/hashmap_util_test.go <ul><li>Add import for <code>vector</code> package<br> <li> Add <code>TestResetWithNilPointers()</code> regression test for nil pointer <br>handling in Reset with needDupVec=true<br> <li> Add <code>TestResetWithMixedNilAndValidPointers()</code> test for mixed nil and <br>valid vectors in Reset<br> <li> Add <code>TestFreeWithNilPointers()</code> regression test for nil pointer handling <br>in Free<br> <li> Add <code>TestFreeWithMixedNilAndValidPointers()</code> test for mixed nil and <br>valid vectors in Free</ul> </details> </td> <td><a href="https://github.com/matrixorigin/matrixone/pull/23191/files#diff-dc62011f0bc0fcb2eb8d9fe366c82ba5265a34cb8efc810e5a21b450763e1320">+95/-0</a>&nbsp; &nbsp; </td> </tr> <tr> <td> <details> <summary><strong>join.test</strong><dd><code>Add BVT test for LEFT JOIN type mismatch</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> test/distributed/cases/join/join.test <ul><li>Add BVT test case for LEFT JOIN with type mismatch (INT = VARCHAR)<br> <li> Test that type conversion error is returned instead of causing panic<br> <li> Include table creation, data insertion, and error validation</ul> </details> </td> <td><a href="https://github.com/matrixorigin/matrixone/pull/23191/files#diff-c786ef775fdeba111970aafa87ec9dc5c1086e298fc7cd6f3ae01896a2586b8b">+13/-0</a>&nbsp; &nbsp; </td> </tr> <tr> <td> <details> <summary><strong>join.result</strong><dd><code>Update test results with type mismatch error</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> test/distributed/cases/join/join.result <ul><li>Update expected output for existing JOIN test (whitespace formatting)<br> <li> Add expected error output for LEFT JOIN with type mismatch test case<br> <li> Include test cleanup statements for new test case</ul> </details> </td> <td><a href="https://github.com/matrixorigin/matrixone/pull/23191/files#diff-b989d5ea2130cdea874fcd458a930f745012401c8d4456f58ab1a51601e2106d">+11/-1</a>&nbsp; &nbsp; </td> </tr> </table></td></tr></tr></tbody></table> </details> ___
1 parent 182f5b9 commit b716774

File tree

4 files changed

+151
-5
lines changed

4 files changed

+151
-5
lines changed

pkg/sql/colexec/hashmap_util/hashmap_util.go

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -115,8 +115,12 @@ func (hb *HashmapBuilder) Reset(proc *process.Process, hashTableHasNotSent bool)
115115

116116
if hb.needDupVec {
117117
for i := range hb.vecs {
118-
for j := range hb.vecs[i] {
119-
hb.vecs[i][j].Free(proc.Mp())
118+
if hb.vecs[i] != nil {
119+
for j := range hb.vecs[i] {
120+
if hb.vecs[i][j] != nil {
121+
hb.vecs[i][j].Free(proc.Mp())
122+
}
123+
}
120124
}
121125
}
122126
}
@@ -126,7 +130,9 @@ func (hb *HashmapBuilder) Reset(proc *process.Process, hashTableHasNotSent bool)
126130
hb.StrHashMap = nil
127131
hb.vecs = nil
128132
for i := range hb.UniqueJoinKeys {
129-
hb.UniqueJoinKeys[i].Free(proc.Mp())
133+
if hb.UniqueJoinKeys[i] != nil {
134+
hb.UniqueJoinKeys[i].Free(proc.Mp())
135+
}
130136
}
131137
hb.UniqueJoinKeys = nil
132138
hb.MultiSels.Free()
@@ -151,7 +157,9 @@ func (hb *HashmapBuilder) Free(proc *process.Process) {
151157
hb.executors = nil
152158
hb.vecs = nil
153159
for i := range hb.UniqueJoinKeys {
154-
hb.UniqueJoinKeys[i].Free(proc.Mp())
160+
if hb.UniqueJoinKeys[i] != nil {
161+
hb.UniqueJoinKeys[i].Free(proc.Mp())
162+
}
155163
}
156164
hb.UniqueJoinKeys = nil
157165
}
@@ -168,18 +176,38 @@ func (hb *HashmapBuilder) FreeHashMapAndBatches(proc *process.Process) {
168176
hb.Batches.Clean(proc.Mp())
169177
}
170178

179+
// cleanupPartiallyCreatedVecs frees all vectors in hb.vecs that were successfully created.
180+
// This is used when an error occurs during evalJoinCondition to prevent memory leaks
181+
// and nil pointer dereferences in Reset.
182+
func (hb *HashmapBuilder) cleanupPartiallyCreatedVecs(proc *process.Process) {
183+
for i := 0; i < len(hb.vecs); i++ {
184+
if hb.vecs[i] != nil {
185+
for j := 0; j < len(hb.vecs[i]); j++ {
186+
if hb.vecs[i][j] != nil {
187+
hb.vecs[i][j].Free(proc.Mp())
188+
}
189+
}
190+
}
191+
}
192+
hb.vecs = nil
193+
}
194+
171195
func (hb *HashmapBuilder) evalJoinCondition(proc *process.Process) error {
172196
for idx1 := range hb.Batches.Buf {
173197
tmpVes := make([]*vector.Vector, len(hb.executors))
174198
hb.vecs = append(hb.vecs, tmpVes)
175199
for idx2 := range hb.executors {
176200
vec, err := hb.executors[idx2].Eval(proc, []*batch.Batch{hb.Batches.Buf[idx1]}, nil)
177201
if err != nil {
202+
// Clean up partially created vecs to prevent nil pointer issues in Reset
203+
hb.cleanupPartiallyCreatedVecs(proc)
178204
return err
179205
}
180206
if hb.needDupVec {
181207
hb.vecs[idx1][idx2], err = vec.Dup(proc.Mp())
182208
if err != nil {
209+
// Clean up partially created vecs to prevent nil pointer issues in Reset
210+
hb.cleanupPartiallyCreatedVecs(proc)
183211
return err
184212
}
185213
} else {

pkg/sql/colexec/hashmap_util/hashmap_util_test.go

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121

2222
"github.com/matrixorigin/matrixone/pkg/common/mpool"
2323
"github.com/matrixorigin/matrixone/pkg/container/types"
24+
"github.com/matrixorigin/matrixone/pkg/container/vector"
2425
"github.com/matrixorigin/matrixone/pkg/pb/plan"
2526
"github.com/matrixorigin/matrixone/pkg/testutil"
2627
"github.com/stretchr/testify/require"
@@ -86,3 +87,97 @@ func TestHashMapAllocAndFree(t *testing.T) {
8687
require.NoError(t, err)
8788
hb.IntHashMap.Free()
8889
}
90+
91+
// TestResetWithNilPointers tests that Reset() handles nil pointers gracefully
92+
// This is a regression test for the panic fix where Reset() would crash when
93+
// vecs or UniqueJoinKeys contained nil pointers.
94+
func TestResetWithNilPointers(t *testing.T) {
95+
proc := testutil.NewProcessWithMPool(t, "", mpool.MustNewZero())
96+
var hb HashmapBuilder
97+
98+
// Test case 1: vecs with nil pointers and needDupVec = true
99+
hb.needDupVec = true
100+
hb.vecs = make([][]*vector.Vector, 2)
101+
hb.vecs[0] = make([]*vector.Vector, 2)
102+
hb.vecs[1] = make([]*vector.Vector, 2)
103+
// Set some vectors to nil to simulate partial initialization
104+
hb.vecs[0][0] = nil
105+
hb.vecs[0][1] = nil
106+
hb.vecs[1][0] = nil
107+
hb.vecs[1][1] = nil
108+
109+
// Test case 2: vecs with nil slice
110+
hb.vecs = append(hb.vecs, nil)
111+
112+
// Test case 3: UniqueJoinKeys with nil pointers
113+
hb.UniqueJoinKeys = make([]*vector.Vector, 3)
114+
hb.UniqueJoinKeys[0] = nil
115+
hb.UniqueJoinKeys[1] = nil
116+
hb.UniqueJoinKeys[2] = nil
117+
118+
// Reset should not panic
119+
hb.Reset(proc, true)
120+
require.Nil(t, hb.vecs)
121+
require.Nil(t, hb.UniqueJoinKeys)
122+
require.Equal(t, int64(0), proc.Mp().CurrNB())
123+
}
124+
125+
// TestResetWithMixedNilAndValidPointers tests Reset() with a mix of nil and valid vectors
126+
func TestResetWithMixedNilAndValidPointers(t *testing.T) {
127+
proc := testutil.NewProcessWithMPool(t, "", mpool.MustNewZero())
128+
var hb HashmapBuilder
129+
130+
// Create some valid vectors
131+
vec1 := testutil.MakeInt32Vector([]int32{1, 2, 3}, nil)
132+
vec2 := testutil.MakeInt32Vector([]int32{4, 5, 6}, nil)
133+
134+
// Test case: vecs with mix of nil and valid vectors
135+
hb.needDupVec = true
136+
hb.vecs = make([][]*vector.Vector, 2)
137+
hb.vecs[0] = []*vector.Vector{vec1, nil}
138+
hb.vecs[1] = []*vector.Vector{nil, vec2}
139+
140+
// Test case: UniqueJoinKeys with mix of nil and valid vectors
141+
hb.UniqueJoinKeys = []*vector.Vector{vec1, nil, vec2}
142+
143+
// Reset should free valid vectors and not panic on nil
144+
hb.Reset(proc, true)
145+
require.Nil(t, hb.vecs)
146+
require.Nil(t, hb.UniqueJoinKeys)
147+
require.Equal(t, int64(0), proc.Mp().CurrNB())
148+
}
149+
150+
// TestFreeWithNilPointers tests that Free() handles nil pointers gracefully
151+
func TestFreeWithNilPointers(t *testing.T) {
152+
proc := testutil.NewProcessWithMPool(t, "", mpool.MustNewZero())
153+
var hb HashmapBuilder
154+
155+
// Test case: UniqueJoinKeys with nil pointers
156+
hb.UniqueJoinKeys = make([]*vector.Vector, 3)
157+
hb.UniqueJoinKeys[0] = nil
158+
hb.UniqueJoinKeys[1] = nil
159+
hb.UniqueJoinKeys[2] = nil
160+
161+
// Free should not panic
162+
hb.Free(proc)
163+
require.Nil(t, hb.UniqueJoinKeys)
164+
require.Equal(t, int64(0), proc.Mp().CurrNB())
165+
}
166+
167+
// TestFreeWithMixedNilAndValidPointers tests Free() with a mix of nil and valid vectors
168+
func TestFreeWithMixedNilAndValidPointers(t *testing.T) {
169+
proc := testutil.NewProcessWithMPool(t, "", mpool.MustNewZero())
170+
var hb HashmapBuilder
171+
172+
// Create some valid vectors
173+
vec1 := testutil.MakeInt32Vector([]int32{1, 2, 3}, nil)
174+
vec2 := testutil.MakeInt32Vector([]int32{4, 5, 6}, nil)
175+
176+
// Test case: UniqueJoinKeys with mix of nil and valid vectors
177+
hb.UniqueJoinKeys = []*vector.Vector{vec1, nil, vec2}
178+
179+
// Free should free valid vectors and not panic on nil
180+
hb.Free(proc)
181+
require.Nil(t, hb.UniqueJoinKeys)
182+
require.Equal(t, int64(0), proc.Mp().CurrNB())
183+
}

test/distributed/cases/join/join.result

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ drop table if exists t2;
198198
create table t2 (a int);
199199
insert into t2 values(1);
200200
select * from (t1 join t2 on t1.a = t2.a);
201-
a a
201+
a a
202202
1 1
203203
drop table if exists tt;
204204
drop table if exists tt2;
@@ -290,3 +290,13 @@ a b a b
290290
42 3 42 1
291291
drop table x1;
292292
drop table x2;
293+
drop table if exists t1;
294+
drop table if exists t2;
295+
CREATE TABLE t1 (id INT);
296+
CREATE TABLE t2 (name VARCHAR(100));
297+
INSERT INTO t1 VALUES (1), (2);
298+
INSERT INTO t2 VALUES ('abc'), ('123');
299+
SELECT * FROM t1 LEFT JOIN t2 ON t1.id = t2.name;
300+
invalid argument cast to int, bad value abc
301+
drop table if exists t1;
302+
drop table if exists t2;

test/distributed/cases/join/join.test

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,3 +201,16 @@ select count(*) from x2 inner join x1 on x1.a = x2.a;
201201
select * from x2 inner join x1 on x1.a = x2.a order by x1.a limit 10;
202202
drop table x1;
203203
drop table x2;
204+
205+
-- @case
206+
-- @desc:鲁棒性测试 - LEFT JOIN with type mismatch (INT = VARCHAR) should return error instead of panic
207+
-- @label:bvt
208+
drop table if exists t1;
209+
drop table if exists t2;
210+
CREATE TABLE t1 (id INT);
211+
CREATE TABLE t2 (name VARCHAR(100));
212+
INSERT INTO t1 VALUES (1), (2);
213+
INSERT INTO t2 VALUES ('abc'), ('123');
214+
SELECT * FROM t1 LEFT JOIN t2 ON t1.id = t2.name;
215+
drop table if exists t1;
216+
drop table if exists t2;

0 commit comments

Comments
 (0)