Dynamic: writing a plain System.Object value recurses forever and crashes the process with a stack overflow
Maintainers usually reply within 1 day
Assessment
This issue has not been assessed yet.
Description
Describe the bug
A binary insert into a Dynamic column crashes the whole process when a value's runtime type is exactly System.Object (for example new object()). DynamicType.Write calls itself with the same value until the stack overflows. .NET cannot catch a StackOverflowException, so a try/catch around the insert does not help: the process aborts.
Other unsupported .NET types fail in a recoverable way. For example, a StringBuilder value throws ClickHouseBulkCopySerializationException with the inner exception ArgumentOutOfRangeException: Unknown type: System.Text.StringBuilder.
The same crash occurs when the System.Object value is inside a collection that Dynamic writes, or inside a JSON column:
| Entry point | Value | Result |
|---|---|---|
InsertBinaryAsync, column Dynamic |
new object() |
stack overflow, exit code 134 |
ClickHouseBulkCopy.WriteToServerAsync, column Dynamic |
new object() |
stack overflow |
InsertBinaryAsync, column Dynamic |
new object[] { new object() } |
stack overflow |
InsertBinaryAsync, column Dynamic |
new List<object> { new object() } |
stack overflow |
InsertBinaryAsync, column Dynamic |
new Dictionary<string, object> { ["a"] = new object() } |
stack overflow |
InsertBinaryAsync, column Array(Dynamic) |
new object[] { new object() } |
stack overflow |
InsertBinaryAsync, column JSON, JsonWriteMode=Binary, registered POCO with an object[] property |
Items = new object[] { new object() } |
stack overflow |
InsertBinaryAsync, column Dynamic |
new StringBuilder("x") (contrast) |
catchable ClickHouseBulkCopySerializationException |
Values that only contain mapped types work: new object[] { 1, 2 } is stored as Array(Dynamic), and new Dictionary<string, object> { ["a"] = 1 } is stored as Map(String, Dynamic). Only an element whose runtime type is exactly System.Object causes the crash.
Steps to reproduce
CREATE TABLE t (id UInt32, d Dynamic) ENGINE = Memory- Insert one row whose
dvalue isnew object()withInsertBinaryAsync(see the code example). - The process aborts with
Stack overflow.
Expected behaviour
The insert throws a catchable exception, the same as for any other .NET type that has no ClickHouse mapping (ClickHouseBulkCopySerializationException with an inner ArgumentOutOfRangeException: Unknown type: System.Object). The process continues to run.
Code example
using var client = new ClickHouseClient("Host=localhost;Port=8123;Username=default;Password=");
await client.ExecuteNonQueryAsync("CREATE OR REPLACE TABLE t (id UInt32, d Dynamic) ENGINE = Memory");
try
{
await client.InsertBinaryAsync("t", new[] { "id", "d" }, new[] { new object[] { 1u, new object() } });
}
catch (Exception ex)
{
// Never reached: the process aborts before this.
Console.WriteLine(ex);
}
Error log
Stack overflow.
at ClickHouse.Driver.Types.BinaryTypeDescriptionWriter.WriteTypeHeader(ClickHouse.Driver.Formats.ExtendedBinaryWriter, ClickHouse.Driver.Types.ClickHouseType)
at ClickHouse.Driver.Types.DynamicType.Write(ClickHouse.Driver.Formats.ExtendedBinaryWriter, System.Object)
at ClickHouse.Driver.Types.DynamicType.Write(ClickHouse.Driver.Formats.ExtendedBinaryWriter, System.Object)
... (DynamicType.Write repeated 58129 times)
at ClickHouse.Driver.Copy.Serializer.RowBinarySerializer.Serialize(System.Object[], ClickHouse.Driver.Types.ClickHouseType[], ClickHouse.Driver.Formats.ExtendedBinaryWriter)
...
at ClickHouse.Driver.ClickHouseClient+<>c__DisplayClass62_0.<InsertBinaryAsync>b__0(ClickHouse.Driver.Copy.Batch, System.Threading.CancellationToken)
Process exit code: 134 (SIGABRT).
Root cause
TypeConverter.RegisterPlainType<DynamicType>()(ClickHouse.Driver/Types/TypeConverter.cs:131) addsDynamicType.FrameworkTypetoReverseMapping(TypeConverter.cs:217-219).DynamicType.FrameworkTypeistypeof(object)(ClickHouse.Driver/Types/DynamicType.cs:15), soTypeConverter.ToClickHouseType(typeof(object))returnsDynamicType. It does not throw "Unknown type".DynamicType.Writeinfers the type of a value withGetCachedInferredType(value.GetType())(DynamicType.cs:47). For aSystem.Objectvalue this isDynamicTypeitself.DynamicType.cs:49then writes aDynamictype header, andDynamicType.cs:50callsinferredType.Write(writer, value), which isDynamicType.Writewith the same value. This repeats with no end.- Containers reach the same code:
object[],List<object>andDictionary<string, object>infer asArray(Dynamic)/Map(String, Dynamic), so each element goes back intoDynamicType.Write.JsonType.WriteUnhintedValue(ClickHouse.Driver/Types/JsonType.cs:466-467) also delegates toDynamicType.Write.
Open PR #640 changes DynamicType.Write to call a new InferType, but that method still uses the type-keyed GetCachedInferredType(value.GetType()), so typeof(object) still maps to DynamicType there.
The native TCP client (ClickHouse.Driver.Tcp) has a separate inference (DynamicTypeInference), and I did not test it.
Suggested fix
In DynamicType.Write, do not write a value as Dynamic inside a Dynamic. If the inferred type for a non-null value is DynamicType, throw the same "Unknown type" exception as for other unmapped types. This must stay true for elements of an inferred Array(Dynamic) / Map(String, Dynamic).
Do not remove typeof(object) → DynamicType from ReverseMapping. object[] → Array(Dynamic) and Dictionary<string, object> → Map(String, Dynamic) depend on it, and the server accepts those values (contrast cases above).
Configuration
Environment
- Client version:
main@ 3e2f799 (the same code is in release 1.5.0) - Language version: C# (net10.0 test program)
- .NET version: 10.0.401
- OS: Ubuntu 24.04.5 LTS (Docker)
ClickHouse server
- ClickHouse Server version: 26.9.1.1629
- ClickHouse Server non-default settings, if any: none
CREATE TABLEstatements for tables involved:CREATE TABLE t (id UInt32, d Dynamic) ENGINE = Memory(alsod Array(Dynamic)andd JSON, see table above)- Sample data for all these tables: one row,
(1, new object())
Found by automated analysis of the client (Polyglot AI) while working on #638. Verified against a live server: each case in the table above was run in a separate process.
- Dominant language
- C#
- Stars
- 100
- Forks
- 26
- Avg merge
- 20h 24m
- Merged PRs (30d)
- 7
Getting set up
- No Dockerfile or Docker Compose file
- Has a pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from ClickHouse/clickhouse-cs
-
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
ClickHouse/clickhouse-cs#711 ·
Maintainers usually reply within 1 day
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
ClickHouse/clickhouse-cs#689 ·
Maintainers usually reply within 1 day
-
Every query fails with Code 164 (READONLY) for a readonly = 1 user: the driver always sends enable_http_compression and input_format_binary_read_json_as_string as settingsPossibly taken @polyglotAI-bot claimed this 28 days ago. Open
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
ClickHouse/clickhouse-cs#659 · 1 comment ·
Maintainers usually reply within 1 day
-
ClickHouseDecimal: Floor() rounds negative non-integers toward zero (-1.5 → -1, server floor gives -2)Possibly taken @polyglotAI-bot claimed this 28 days ago. Openbug
Difficulty 2/5 1-3 hours Newbie friendliness 88/100
ClickHouse/clickhouse-cs#645 · 1 assignee ·
Maintainers usually reply within 1 day
-
ClickHouseDecimal: GetHashCode depends on Scale, but Equals ignores ScalePossibly taken @bjsneto claimed this 11 days ago. Openbug
Difficulty 2/5 1-3 hours Newbie friendliness 88/100
ClickHouse/clickhouse-cs#639 ·
Maintainers usually reply within 1 day
All issues in ClickHouse/clickhouse-cs
Similar issues
-
The Recommended preset turns on Import Uno DSP, which only works with the Material themePossibly taken @agneszitte claimed this today. Open
Difficulty 1/5 Under an hour Newbie friendliness 92/100
unoplatform/uno.templates#2388 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 77/100
eyalgolan/statecraft-israel#2732 ·
Maintainers usually reply within 1 day
-
要トリアージ
Difficulty 2/5 1-3 hours Newbie friendliness 64/100
Maintainers usually reply within 1 day
-
kind/chore status/draft
Difficulty 2/5 1-3 hours Newbie friendliness 65/100
Altinn/altinn-events#1158 ·
Maintainers usually reply within 1 day
-
S: Untriaged
Difficulty 2/5 1-3 hours Newbie friendliness 62/100
Trauma-Station/Trauma-Station#5285 ·
Maintainers usually reply within 1 day