Repository: segmentio/parquet-go (go)

Issue:
Panic (nil dereference) when writing to GenericBuffer[any] without schema
As the title states, the library panics with a nil pointer dereference when attempting to write to a `GenericBuffer[any]` if no schema is provided. 

```
# ./parquet_panic 
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x20 pc=0x6cf160]

goroutine 1 [running]:
github.com/segmentio/parquet-go.(*Schema).Deconstruct(0x0, {0x0?, 0x0, 0x0}, {0x71bd00?, 0xc0001191b0?})
	/go/pkg/mod/github.com/segmentio/parquet-go@v0.0.0-20220914222423-67dbe8d21ca5/schema.go:231 +0x120
github.com/segmentio/parquet-go.(*GenericBuffer[...]).writeRows(0xc000122090, {0xc0001191b0, 0x1, 0x0})
	/go/pkg/mod/github.com/segmentio/parquet-go@v0.0.0-20220914222423-67dbe8d21ca5/buffer_go18.go:154 +0x1b2
github.com/segmentio/parquet-go.(*GenericBuffer[...]).Write(0x0?, {0xc0001191b0?, 0x0?, 0x721c60?})
	/go/pkg/mod/github.com/segmentio/parquet-go@v0.0.0-20220914222423-67dbe8d21ca5/buffer_go18.go:125 +0x47
main.main()
	/parquet_panic/main.go:14 +0x91
```

Minimal reproduction code:
```go
package main

import (
	parquet "github.com/segmentio/parquet-go"
)

type TestType struct{}

func main() {
	buffer := parquet.NewGenericBuffer[any]()

	data := make([]any, 1)
	data[0] = TestType{}
	_, _ = buffer.Write(data)
}
```

Pull request description:
buffer_go18: enforce schema presence at instantiation
We could infer the schema from the first element in a slice lazily in some cases, but error handling in subsequent calls to functions that depend on the schema may be difficult in the cases where the generic buffer was instantiated with an interface type parameter.

If there's a compelling use case for that, we may add the functionality down the road.

Fixes #347 

Patch:
diff --git a/buffer_go18.go b/buffer_go18.go
index c073abf..ff90540 100644
--- a/buffer_go18.go
+++ b/buffer_go18.go
@@ -22,7 +22,8 @@ type GenericBuffer[T any] struct {
 // The type parameter T should be a map, struct, or any. Any other types will
 // cause a panic at runtime. Type checking is a lot more effective when the
 // generic parameter is a struct type, using map and interface types is somewhat
-// similar to using a Writer.
+// similar to using a Writer.  If using an interface type for the type parameter,
+// then providing a schema at instantiation is required.
 //
 // If the option list may explicitly declare a schema, it must be compatible
 // with the schema generated from T.
@@ -37,6 +38,10 @@ func NewGenericBuffer[T any](options ...RowGroupOption) *GenericBuffer[T] {
 		config.Schema = schemaOf(dereference(t))
 	}
 
+	if config.Schema == nil {
+		panic("generic buffer must be instantiated with schema or concrete type.")
+	}
+
 	buf := &GenericBuffer[T]{
 		base: Buffer{config: config},
 	}
diff --git a/writer_go18.go b/writer_go18.go
index b1f9598..92e0d0e 100644
--- a/writer_go18.go
+++ b/writer_go18.go
@@ -78,6 +78,10 @@ func NewGenericWriter[T any](output io.Writer, options ...WriterOption) *Generic
 		config.Schema = schema
 	}
 
+	if config.Schema == nil {
+		panic("generic writer must be instantiated with schema or concrete type.")
+	}
+
 	return &GenericWriter[T]{
 		base: Writer{
 			output: output,


Test patch:
diff --git a/buffer_go18_test.go b/buffer_go18_test.go
index 038f6df..aea08f9 100644
--- a/buffer_go18_test.go
+++ b/buffer_go18_test.go
@@ -182,11 +182,6 @@ func TestIssue327(t *testing.T) {
 	})
 }
 
-func TestIssue343(t *testing.T) {
-	// must not panic
-	_ = parquet.NewGenericBuffer[any]()
-}
-
 func TestIssue346(t *testing.T) {
 	type TestType struct {
 		Key int
@@ -199,3 +194,24 @@ func TestIssue346(t *testing.T) {
 	data[0] = TestType{Key: 0}
 	_, _ = buffer.Write(data)
 }
+
+func TestIssue347(t *testing.T) {
+	type TestType struct {
+		Key int
+	}
+
+	// instantiating with concrete type shouldn't panic
+	_ = parquet.NewGenericBuffer[TestType]()
+
+	// instantiating with schema and interface type parameter shouldn't panic
+	schema := parquet.SchemaOf(TestType{})
+	_ = parquet.NewGenericBuffer[any](schema)
+
+	defer func() {
+		if r := recover(); r == nil {
+			t.Errorf("instantiating generic buffer without schema and with interface " +
+				"type parameter should panic")
+		}
+	}()
+	_ = parquet.NewGenericBuffer[any]()
+}
diff --git a/writer_go18_test.go b/writer_go18_test.go
index 9036b91..c26cb97 100644
--- a/writer_go18_test.go
+++ b/writer_go18_test.go
@@ -293,3 +293,25 @@ func TestIssue302(t *testing.T) {
 		t.Run(test.name, test.fn)
 	}
 }
+
+func TestIssue347Writer(t *testing.T) {
+	type TestType struct {
+		Key int
+	}
+
+	b := new(bytes.Buffer)
+	// instantiating with concrete type shouldn't panic
+	_ = parquet.NewGenericWriter[TestType](b)
+
+	// instantiating with schema and interface type parameter shouldn't panic
+	schema := parquet.SchemaOf(TestType{})
+	_ = parquet.NewGenericWriter[any](b, schema)
+
+	defer func() {
+		if r := recover(); r == nil {
+			t.Errorf("instantiating generic buffer without schema and with interface " +
+				"type parameter should panic")
+		}
+	}()
+	_ = parquet.NewGenericWriter[any](b)
+}