From 6330a48500a63feaf5359ba8d0c0683cbc4f881b Mon Sep 17 00:00:00 2001 From: Vishal S Date: Sat, 7 Mar 2026 21:06:27 +0530 Subject: [PATCH 1/6] Add core fuzz targets for JSON, Protobuf and JDBC mapping --- core/build.gradle.kts | 1 + .../avatica/fuzz/AvaticaSiteFuzzer.java | 163 ++++++++++++++++++ .../avatica/fuzz/JsonHandlerFuzzer.java | 98 +++++++++++ .../avatica/fuzz/ProtobufHandlerFuzzer.java | 115 ++++++++++++ .../avatica/fuzz/TypedValueFuzzer.java | 78 +++++++++ 5 files changed, 455 insertions(+) create mode 100644 core/src/test/java/org/apache/calcite/avatica/fuzz/AvaticaSiteFuzzer.java create mode 100644 core/src/test/java/org/apache/calcite/avatica/fuzz/JsonHandlerFuzzer.java create mode 100644 core/src/test/java/org/apache/calcite/avatica/fuzz/ProtobufHandlerFuzzer.java create mode 100644 core/src/test/java/org/apache/calcite/avatica/fuzz/TypedValueFuzzer.java diff --git a/core/build.gradle.kts b/core/build.gradle.kts index ffa8ecc6d..9c3ee71f1 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -40,6 +40,7 @@ dependencies { implementation("org.apache.httpcomponents.client5:httpclient5") implementation("org.apache.httpcomponents.core5:httpcore5") implementation("org.slf4j:slf4j-api") + testImplementation("com.code-intelligence:jazzer-api:0.22.1") testImplementation("junit:junit") testImplementation("org.mockito:mockito-core") testImplementation("org.mockito:mockito-inline") diff --git a/core/src/test/java/org/apache/calcite/avatica/fuzz/AvaticaSiteFuzzer.java b/core/src/test/java/org/apache/calcite/avatica/fuzz/AvaticaSiteFuzzer.java new file mode 100644 index 000000000..233d94d0c --- /dev/null +++ b/core/src/test/java/org/apache/calcite/avatica/fuzz/AvaticaSiteFuzzer.java @@ -0,0 +1,163 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.calcite.avatica.fuzz; + +import com.code_intelligence.jazzer.api.FuzzedDataProvider; +import org.apache.calcite.avatica.AvaticaParameter; +import org.apache.calcite.avatica.AvaticaSite; +import org.apache.calcite.avatica.remote.TypedValue; + +import java.sql.Types; +import java.util.Calendar; +import java.util.Locale; +import java.util.TimeZone; +import java.math.BigDecimal; +import java.sql.Date; +import java.sql.Time; +import java.sql.Timestamp; +import java.sql.SQLException; +import java.lang.reflect.Proxy; + +public class AvaticaSiteFuzzer { + + public static void fuzzerTestOneInput(FuzzedDataProvider data) { + try { + // Construct dependencies for an AvaticaSite + AvaticaParameter param = new AvaticaParameter( + data.consumeBoolean(), + data.consumeInt(), + data.consumeInt(), // scale + data.consumeInt(), + data.consumeString(10), // typeName + data.consumeString(10), // className + data.consumeString(10) // name + ); + + Calendar calendar = Calendar.getInstance(TimeZone.getDefault(), Locale.ROOT); + TypedValue[] slots = new TypedValue[1]; + + // Target object + AvaticaSite site = new AvaticaSite(param, calendar, 0, slots); + + // Determine what to fuzz + int choice = data.consumeInt(1, 16); + + switch(choice) { + case 1: + site.setByte(data.consumeByte()); + break; + case 2: + site.setChar(data.consumeChar()); + break; + case 3: + site.setShort(data.consumeShort()); + break; + case 4: + site.setInt(data.consumeInt()); + break; + case 5: + site.setLong(data.consumeLong()); + break; + case 6: + site.setBoolean(data.consumeBoolean()); + break; + case 7: + site.setNString(data.consumeString(50)); + break; + case 8: + site.setFloat(data.consumeFloat()); + break; + case 9: + site.setDouble(data.consumeDouble()); + break; + case 10: + site.setBigDecimal(new BigDecimal(data.consumeDouble())); + break; + case 11: + site.setString(data.consumeString(50)); + break; + case 12: + site.setBytes(data.consumeBytes(50)); + break; + case 13: + site.setTimestamp(new Timestamp(data.consumeLong()), calendar); + break; + case 14: + site.setTime(new Time(data.consumeLong()), calendar); + break; + case 15: + // Raw object mapping + Object obj = null; + int objType = data.consumeInt(1, 4); + if (objType == 1) obj = data.consumeBoolean(); + else if (objType == 2) obj = data.consumeString(50); + else if (objType == 3) obj = data.consumeLong(); + else if (objType == 4) obj = data.consumeBytes(50); + + site.setObject(obj, data.consumeInt(-10, 100)); // Types constants fall in this range + break; + case 16: + // Test the JDBC ResultSet getter mapping using a dynamic proxy + org.apache.calcite.avatica.util.Cursor.Accessor accessor = + (org.apache.calcite.avatica.util.Cursor.Accessor) Proxy.newProxyInstance( + org.apache.calcite.avatica.util.Cursor.Accessor.class.getClassLoader(), + new Class[] { org.apache.calcite.avatica.util.Cursor.Accessor.class }, + (proxy, method, args) -> { + String name = method.getName(); + if (name.equals("wasNull")) return data.consumeBoolean(); + if (name.equals("getString") || name.equals("getNString")) return data.consumeString(50); + if (name.equals("getBoolean")) return data.consumeBoolean(); + if (name.equals("getByte")) return data.consumeByte(); + if (name.equals("getShort")) return data.consumeShort(); + if (name.equals("getInt")) return data.consumeInt(); + if (name.equals("getLong")) return data.consumeLong(); + if (name.equals("getFloat")) return data.consumeFloat(); + if (name.equals("getDouble")) return data.consumeDouble(); + if (name.equals("getBigDecimal")) return new BigDecimal(data.consumeDouble()); + if (name.equals("getBytes")) return data.consumeBytes(50); + if (name.equals("getDate")) return new Date(data.consumeLong()); + if (name.equals("getTime")) return new Time(data.consumeLong()); + if (name.equals("getTimestamp")) return new Timestamp(data.consumeLong()); + + if (name.equals("getUByte")) return org.joou.UByte.valueOf(data.consumeInt(0, 255)); + if (name.equals("getUShort")) return org.joou.UShort.valueOf(data.consumeInt(0, 65535)); + if (name.equals("getUInt")) return org.joou.UInteger.valueOf(data.consumeLong(0, 4294967295L)); + if (name.equals("getULong")) return org.joou.ULong.valueOf(data.consumeLong(0, Long.MAX_VALUE)); + + return null; + } + ); + + try { + AvaticaSite.get(accessor, data.consumeInt(-10, 100), data.consumeBoolean(), calendar); + } catch (SQLException e) { + // Expected to throw SQLException for unsupported conversions + } + break; + } + + } catch (IllegalArgumentException | UnsupportedOperationException e) { + // UnsupportedOperationException is explicitly thrown by AvaticaSite.notImplemented() + // and unsupportedCast() when types don't align. + } catch (RuntimeException e) { + // TypedValue bindings often throw RuntimeException directly for "not implemented" + if (!"not implemented".equals(e.getMessage())) { + throw e; + } + } + } +} diff --git a/core/src/test/java/org/apache/calcite/avatica/fuzz/JsonHandlerFuzzer.java b/core/src/test/java/org/apache/calcite/avatica/fuzz/JsonHandlerFuzzer.java new file mode 100644 index 000000000..3b954edff --- /dev/null +++ b/core/src/test/java/org/apache/calcite/avatica/fuzz/JsonHandlerFuzzer.java @@ -0,0 +1,98 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.calcite.avatica.fuzz; + +import com.code_intelligence.jazzer.api.FuzzedDataProvider; +import java.io.IOException; +import org.apache.calcite.avatica.remote.JsonService; +import org.apache.calcite.avatica.remote.Service; +import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.DeserializationFeature; + +public class JsonHandlerFuzzer { + + static { + // Prevent failure on completely unknown properties that the fuzzer might invent + JsonService.MAPPER.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + } + + public static void fuzzerTestOneInput(FuzzedDataProvider data) { + try { + // The goal here is to hit the deeply nested deserialization logic of Avatica's Request and Response models. + // Avatica uses Jackson to parse strings into classes like Service.ExecuteRequest, Service.CatalogsRequest, etc. + + boolean isRequest = data.consumeBoolean(); + + if (isRequest) { + String subType = data.pickValue(new String[]{ + "getCatalogs", "getSchemas", "getTables", "getTableTypes", "getTypeInfo", "getColumns", + "execute", "prepare", "prepareAndExecute", "fetch", "createStatement", "closeStatement", + "openConnection", "closeConnection", "connectionSync", "databaseProperties", "syncResults", + "commit", "rollback", "prepareAndExecuteBatch", "executeBatch" + }); + + java.util.Map map = new java.util.HashMap<>(); + map.put("request", subType); + + // Add random key/value pairs + int numFields = data.consumeInt(0, 10); + for (int i = 0; i < numFields; i++) { + switch(data.consumeInt(1, 4)) { + case 1: map.put(data.consumeString(10), data.consumeString(20)); break; + case 2: map.put(data.consumeString(10), data.consumeInt()); break; + case 3: map.put(data.consumeString(10), data.consumeBoolean()); break; + case 4: map.put(data.consumeString(10), null); break; + } + } + + String jsonPayload = JsonService.MAPPER.writeValueAsString(map); + JsonService.MAPPER.readValue(jsonPayload, Service.Request.class); + } else { + String subType = data.pickValue(new String[]{ + "openConnection", "resultSet", "prepare", "fetch", "createStatement", "closeStatement", + "closeConnection", "connectionSync", "databaseProperties", "executeResults", "error", + "syncResults", "rpcMetadata", "commit", "rollback", "executeBatch" + }); + + java.util.Map map = new java.util.HashMap<>(); + map.put("response", subType); + + // Add random key/value pairs + int numFields = data.consumeInt(0, 10); + for (int i = 0; i < numFields; i++) { + switch(data.consumeInt(1, 4)) { + case 1: map.put(data.consumeString(10), data.consumeString(20)); break; + case 2: map.put(data.consumeString(10), data.consumeInt()); break; + case 3: map.put(data.consumeString(10), data.consumeBoolean()); break; + case 4: map.put(data.consumeString(10), null); break; + } + } + + String jsonPayload = JsonService.MAPPER.writeValueAsString(map); + JsonService.MAPPER.readValue(jsonPayload, Service.Response.class); + } + + } catch (JsonParseException | JsonMappingException e) { + // Known Jackson exceptions for invalid JSON structure or unmappable types + } catch (IOException e) { + // General IO issues reading the string + } catch (IllegalArgumentException | IllegalStateException e) { + // Known issues when Jackson encounters valid JSON but violates Avatica's preconditions + } + } +} diff --git a/core/src/test/java/org/apache/calcite/avatica/fuzz/ProtobufHandlerFuzzer.java b/core/src/test/java/org/apache/calcite/avatica/fuzz/ProtobufHandlerFuzzer.java new file mode 100644 index 000000000..0e38a1722 --- /dev/null +++ b/core/src/test/java/org/apache/calcite/avatica/fuzz/ProtobufHandlerFuzzer.java @@ -0,0 +1,115 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.calcite.avatica.fuzz; + +import com.code_intelligence.jazzer.api.FuzzedDataProvider; +import java.io.IOException; +import org.apache.calcite.avatica.remote.ProtobufTranslationImpl; +import org.apache.calcite.avatica.remote.Service; +import com.google.protobuf.ByteString; + +public class ProtobufHandlerFuzzer { + + private static final ProtobufTranslationImpl translator = new ProtobufTranslationImpl(); + + public static void fuzzerTestOneInput(FuzzedDataProvider data) { + try { + // The goal here is to hit the protobuf deserialization logic. + // Avatica maps Protobuf messages (WireMessage) into its POJO Service.Request models. + // WireMessage requires a "name" string matching a Request subclass. + + boolean isRequest = data.consumeBoolean(); + + if (isRequest) { + String subType = data.pickValue(new String[]{ + "org.apache.calcite.avatica.proto.Requests$CatalogsRequest", + "org.apache.calcite.avatica.proto.Requests$SchemasRequest", + "org.apache.calcite.avatica.proto.Requests$TablesRequest", + "org.apache.calcite.avatica.proto.Requests$TableTypesRequest", + "org.apache.calcite.avatica.proto.Requests$TypeInfoRequest", + "org.apache.calcite.avatica.proto.Requests$ColumnsRequest", + "org.apache.calcite.avatica.proto.Requests$ExecuteRequest", + "org.apache.calcite.avatica.proto.Requests$PrepareRequest", + "org.apache.calcite.avatica.proto.Requests$PrepareAndExecuteRequest", + "org.apache.calcite.avatica.proto.Requests$FetchRequest", + "org.apache.calcite.avatica.proto.Requests$CreateStatementRequest", + "org.apache.calcite.avatica.proto.Requests$CloseStatementRequest", + "org.apache.calcite.avatica.proto.Requests$OpenConnectionRequest", + "org.apache.calcite.avatica.proto.Requests$CloseConnectionRequest", + "org.apache.calcite.avatica.proto.Requests$ConnectionSyncRequest", + "org.apache.calcite.avatica.proto.Requests$DatabasePropertyRequest", + "org.apache.calcite.avatica.proto.Requests$SyncResultsRequest", + "org.apache.calcite.avatica.proto.Requests$CommitRequest", + "org.apache.calcite.avatica.proto.Requests$RollbackRequest", + "org.apache.calcite.avatica.proto.Requests$PrepareAndExecuteBatchRequest", + "org.apache.calcite.avatica.proto.Requests$ExecuteBatchRequest" + }); + + org.apache.calcite.avatica.proto.Common.WireMessage wireMsg = org.apache.calcite.avatica.proto.Common.WireMessage.newBuilder() + .setName(subType) + .setWrappedMessage(ByteString.copyFrom(data.consumeRemainingAsBytes())) + .build(); + + byte[] protobufPayload = wireMsg.toByteArray(); + translator.parseRequest(protobufPayload); + } else { + String subType = data.pickValue(new String[]{ + "org.apache.calcite.avatica.proto.Responses$OpenConnectionResponse", + "org.apache.calcite.avatica.proto.Responses$CloseConnectionResponse", + "org.apache.calcite.avatica.proto.Responses$CloseStatementResponse", + "org.apache.calcite.avatica.proto.Responses$ConnectionSyncResponse", + "org.apache.calcite.avatica.proto.Responses$CreateStatementResponse", + "org.apache.calcite.avatica.proto.Responses$DatabasePropertyResponse", + "org.apache.calcite.avatica.proto.Responses$ExecuteResponse", + "org.apache.calcite.avatica.proto.Responses$FetchResponse", + "org.apache.calcite.avatica.proto.Responses$PrepareResponse", + "org.apache.calcite.avatica.proto.Responses$ResultSetResponse", + "org.apache.calcite.avatica.proto.Responses$ErrorResponse", + "org.apache.calcite.avatica.proto.Responses$SyncResultsResponse", + "org.apache.calcite.avatica.proto.Responses$RpcMetadata", + "org.apache.calcite.avatica.proto.Responses$CommitResponse", + "org.apache.calcite.avatica.proto.Responses$RollbackResponse", + "org.apache.calcite.avatica.proto.Responses$ExecuteBatchResponse" + }); + + org.apache.calcite.avatica.proto.Common.WireMessage wireMsg = org.apache.calcite.avatica.proto.Common.WireMessage.newBuilder() + .setName(subType) + .setWrappedMessage(ByteString.copyFrom(data.consumeRemainingAsBytes())) + .build(); + + byte[] protobufPayload = wireMsg.toByteArray(); + translator.parseResponse(protobufPayload); + } + + } catch (IOException e) { + // Known exception from protobuf parsing (e.g. InvalidProtocolBufferException) + } catch (IllegalArgumentException | IllegalStateException | NullPointerException e) { + // Known issues when Protobuf unmarshalls into Avatica types that fail preconditions + } catch (RuntimeException e) { + // Specifically catching Avatica's custom DeserializationException or + // unhandled protobuf issues to ensure the fuzzer survives + if (e.getClass().getName().contains("DeserializationException") || + e.getClass().getName().contains("InvalidProtocolBufferException") || + (e.getMessage() != null && e.getMessage().contains("Unknown type:")) || + (e.getMessage() != null && e.getMessage().contains("Unhandled type:"))) { + return; + } + // If it's a real bug (NullPointerException, etc), let it crash! + throw e; + } + } +} diff --git a/core/src/test/java/org/apache/calcite/avatica/fuzz/TypedValueFuzzer.java b/core/src/test/java/org/apache/calcite/avatica/fuzz/TypedValueFuzzer.java new file mode 100644 index 000000000..10b74cc64 --- /dev/null +++ b/core/src/test/java/org/apache/calcite/avatica/fuzz/TypedValueFuzzer.java @@ -0,0 +1,78 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.calcite.avatica.fuzz; + +import com.code_intelligence.jazzer.api.FuzzedDataProvider; +import org.apache.calcite.avatica.remote.TypedValue; +import org.apache.calcite.avatica.proto.Common; + +public class TypedValueFuzzer { + + public static void fuzzerTestOneInput(FuzzedDataProvider data) { + try { + // Use the Fuzzer to generate random Protobuf arrays + boolean isFromProto = data.consumeBoolean(); + + if (isFromProto) { + // Parse it into Common.TypedValue + Common.TypedValue protoValue = Common.TypedValue.parseFrom(data.consumeRemainingAsBytes()); + + // Attempt to convert it into a local Avatica TypedValue and then to JDBC representations + TypedValue typedValue = TypedValue.fromProto(protoValue); + + // Convert to local and jdbc formats + typedValue.toLocal(); + typedValue.toJdbc(java.util.Calendar.getInstance()); + + // Attempt Protobuf serialization back + typedValue.toProto(); + } else { + // Fuzz the direct POJO creator + String typeName = data.pickValue(new String[]{ + "STRING", "BOOLEAN", "BYTE", "SHORT", "INTEGER", "LONG", "FLOAT", "DOUBLE", "DATE", "TIME", "TIMESTAMP" + }); + + Object fakeValue = null; + switch (typeName) { + case "STRING": fakeValue = data.consumeString(50); break; + case "BOOLEAN": fakeValue = data.consumeBoolean(); break; + case "BYTE": fakeValue = data.consumeByte(); break; + case "SHORT": fakeValue = data.consumeShort(); break; + case "INTEGER": fakeValue = data.consumeInt(); break; + case "LONG": fakeValue = data.consumeLong(); break; + case "FLOAT": fakeValue = data.consumeFloat(); break; + case "DOUBLE": fakeValue = data.consumeDouble(); break; + case "DATE": case "TIME": case "TIMESTAMP": fakeValue = data.consumeLong(); break; + } + + // Fuzz create factory mapping the object value with random type identifier + TypedValue created = TypedValue.create(typeName, fakeValue); + + // Call accessors + created.toLocal(); + created.toJdbc(java.util.Calendar.getInstance()); + created.toProto(); + } + + } catch (java.io.IOException e) { + // Known exception for invalid protobuf + } catch (RuntimeException e) { + // TypedValue parser is known to throw unchecked exceptions when types don't align with values in the protobuf + // E.g., asking for a Boolean from a protobuf field that was stored as a String. + } + } +} From a14793442811662b9902f5b04c57f6a142d629b0 Mon Sep 17 00:00:00 2001 From: Vishal S Date: Sun, 8 Mar 2026 19:33:12 +0530 Subject: [PATCH 2/6] Fix formatting and import ordering for Avatica fuzzers --- .../avatica/fuzz/AvaticaSiteFuzzer.java | 34 ++++----- .../avatica/fuzz/JsonHandlerFuzzer.java | 28 +++---- .../avatica/fuzz/ProtobufHandlerFuzzer.java | 75 ++++++++++--------- .../avatica/fuzz/TypedValueFuzzer.java | 23 +++--- 4 files changed, 82 insertions(+), 78 deletions(-) diff --git a/core/src/test/java/org/apache/calcite/avatica/fuzz/AvaticaSiteFuzzer.java b/core/src/test/java/org/apache/calcite/avatica/fuzz/AvaticaSiteFuzzer.java index 233d94d0c..5d427ff97 100644 --- a/core/src/test/java/org/apache/calcite/avatica/fuzz/AvaticaSiteFuzzer.java +++ b/core/src/test/java/org/apache/calcite/avatica/fuzz/AvaticaSiteFuzzer.java @@ -16,21 +16,21 @@ */ package org.apache.calcite.avatica.fuzz; -import com.code_intelligence.jazzer.api.FuzzedDataProvider; import org.apache.calcite.avatica.AvaticaParameter; import org.apache.calcite.avatica.AvaticaSite; import org.apache.calcite.avatica.remote.TypedValue; -import java.sql.Types; -import java.util.Calendar; -import java.util.Locale; -import java.util.TimeZone; +import com.code_intelligence.jazzer.api.FuzzedDataProvider; + +import java.lang.reflect.Proxy; import java.math.BigDecimal; import java.sql.Date; +import java.sql.SQLException; import java.sql.Time; import java.sql.Timestamp; -import java.sql.SQLException; -import java.lang.reflect.Proxy; +import java.util.Calendar; +import java.util.Locale; +import java.util.TimeZone; public class AvaticaSiteFuzzer { @@ -41,21 +41,21 @@ public static void fuzzerTestOneInput(FuzzedDataProvider data) { data.consumeBoolean(), data.consumeInt(), data.consumeInt(), // scale - data.consumeInt(), + data.consumeInt(), data.consumeString(10), // typeName data.consumeString(10), // className data.consumeString(10) // name ); - + Calendar calendar = Calendar.getInstance(TimeZone.getDefault(), Locale.ROOT); TypedValue[] slots = new TypedValue[1]; - + // Target object AvaticaSite site = new AvaticaSite(param, calendar, 0, slots); - + // Determine what to fuzz int choice = data.consumeInt(1, 16); - + switch(choice) { case 1: site.setByte(data.consumeByte()); @@ -107,12 +107,12 @@ public static void fuzzerTestOneInput(FuzzedDataProvider data) { else if (objType == 2) obj = data.consumeString(50); else if (objType == 3) obj = data.consumeLong(); else if (objType == 4) obj = data.consumeBytes(50); - + site.setObject(obj, data.consumeInt(-10, 100)); // Types constants fall in this range break; case 16: // Test the JDBC ResultSet getter mapping using a dynamic proxy - org.apache.calcite.avatica.util.Cursor.Accessor accessor = + org.apache.calcite.avatica.util.Cursor.Accessor accessor = (org.apache.calcite.avatica.util.Cursor.Accessor) Proxy.newProxyInstance( org.apache.calcite.avatica.util.Cursor.Accessor.class.getClassLoader(), new Class[] { org.apache.calcite.avatica.util.Cursor.Accessor.class }, @@ -132,16 +132,16 @@ public static void fuzzerTestOneInput(FuzzedDataProvider data) { if (name.equals("getDate")) return new Date(data.consumeLong()); if (name.equals("getTime")) return new Time(data.consumeLong()); if (name.equals("getTimestamp")) return new Timestamp(data.consumeLong()); - + if (name.equals("getUByte")) return org.joou.UByte.valueOf(data.consumeInt(0, 255)); if (name.equals("getUShort")) return org.joou.UShort.valueOf(data.consumeInt(0, 65535)); if (name.equals("getUInt")) return org.joou.UInteger.valueOf(data.consumeLong(0, 4294967295L)); if (name.equals("getULong")) return org.joou.ULong.valueOf(data.consumeLong(0, Long.MAX_VALUE)); - + return null; } ); - + try { AvaticaSite.get(accessor, data.consumeInt(-10, 100), data.consumeBoolean(), calendar); } catch (SQLException e) { diff --git a/core/src/test/java/org/apache/calcite/avatica/fuzz/JsonHandlerFuzzer.java b/core/src/test/java/org/apache/calcite/avatica/fuzz/JsonHandlerFuzzer.java index 3b954edff..42a94a8e5 100644 --- a/core/src/test/java/org/apache/calcite/avatica/fuzz/JsonHandlerFuzzer.java +++ b/core/src/test/java/org/apache/calcite/avatica/fuzz/JsonHandlerFuzzer.java @@ -16,16 +16,18 @@ */ package org.apache.calcite.avatica.fuzz; -import com.code_intelligence.jazzer.api.FuzzedDataProvider; -import java.io.IOException; import org.apache.calcite.avatica.remote.JsonService; import org.apache.calcite.avatica.remote.Service; + +import com.code_intelligence.jazzer.api.FuzzedDataProvider; import com.fasterxml.jackson.core.JsonParseException; -import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.JsonMappingException; + +import java.io.IOException; public class JsonHandlerFuzzer { - + static { // Prevent failure on completely unknown properties that the fuzzer might invent JsonService.MAPPER.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); @@ -35,9 +37,9 @@ public static void fuzzerTestOneInput(FuzzedDataProvider data) { try { // The goal here is to hit the deeply nested deserialization logic of Avatica's Request and Response models. // Avatica uses Jackson to parse strings into classes like Service.ExecuteRequest, Service.CatalogsRequest, etc. - + boolean isRequest = data.consumeBoolean(); - + if (isRequest) { String subType = data.pickValue(new String[]{ "getCatalogs", "getSchemas", "getTables", "getTableTypes", "getTypeInfo", "getColumns", @@ -45,10 +47,10 @@ public static void fuzzerTestOneInput(FuzzedDataProvider data) { "openConnection", "closeConnection", "connectionSync", "databaseProperties", "syncResults", "commit", "rollback", "prepareAndExecuteBatch", "executeBatch" }); - + java.util.Map map = new java.util.HashMap<>(); map.put("request", subType); - + // Add random key/value pairs int numFields = data.consumeInt(0, 10); for (int i = 0; i < numFields; i++) { @@ -59,7 +61,7 @@ public static void fuzzerTestOneInput(FuzzedDataProvider data) { case 4: map.put(data.consumeString(10), null); break; } } - + String jsonPayload = JsonService.MAPPER.writeValueAsString(map); JsonService.MAPPER.readValue(jsonPayload, Service.Request.class); } else { @@ -68,10 +70,10 @@ public static void fuzzerTestOneInput(FuzzedDataProvider data) { "closeConnection", "connectionSync", "databaseProperties", "executeResults", "error", "syncResults", "rpcMetadata", "commit", "rollback", "executeBatch" }); - + java.util.Map map = new java.util.HashMap<>(); map.put("response", subType); - + // Add random key/value pairs int numFields = data.consumeInt(0, 10); for (int i = 0; i < numFields; i++) { @@ -82,11 +84,11 @@ public static void fuzzerTestOneInput(FuzzedDataProvider data) { case 4: map.put(data.consumeString(10), null); break; } } - + String jsonPayload = JsonService.MAPPER.writeValueAsString(map); JsonService.MAPPER.readValue(jsonPayload, Service.Response.class); } - + } catch (JsonParseException | JsonMappingException e) { // Known Jackson exceptions for invalid JSON structure or unmappable types } catch (IOException e) { diff --git a/core/src/test/java/org/apache/calcite/avatica/fuzz/ProtobufHandlerFuzzer.java b/core/src/test/java/org/apache/calcite/avatica/fuzz/ProtobufHandlerFuzzer.java index 0e38a1722..aeacca99a 100644 --- a/core/src/test/java/org/apache/calcite/avatica/fuzz/ProtobufHandlerFuzzer.java +++ b/core/src/test/java/org/apache/calcite/avatica/fuzz/ProtobufHandlerFuzzer.java @@ -16,14 +16,15 @@ */ package org.apache.calcite.avatica.fuzz; -import com.code_intelligence.jazzer.api.FuzzedDataProvider; -import java.io.IOException; import org.apache.calcite.avatica.remote.ProtobufTranslationImpl; -import org.apache.calcite.avatica.remote.Service; + +import com.code_intelligence.jazzer.api.FuzzedDataProvider; import com.google.protobuf.ByteString; +import java.io.IOException; + public class ProtobufHandlerFuzzer { - + private static final ProtobufTranslationImpl translator = new ProtobufTranslationImpl(); public static void fuzzerTestOneInput(FuzzedDataProvider data) { @@ -31,78 +32,78 @@ public static void fuzzerTestOneInput(FuzzedDataProvider data) { // The goal here is to hit the protobuf deserialization logic. // Avatica maps Protobuf messages (WireMessage) into its POJO Service.Request models. // WireMessage requires a "name" string matching a Request subclass. - + boolean isRequest = data.consumeBoolean(); - + if (isRequest) { String subType = data.pickValue(new String[]{ - "org.apache.calcite.avatica.proto.Requests$CatalogsRequest", - "org.apache.calcite.avatica.proto.Requests$SchemasRequest", + "org.apache.calcite.avatica.proto.Requests$CatalogsRequest", + "org.apache.calcite.avatica.proto.Requests$SchemasRequest", "org.apache.calcite.avatica.proto.Requests$TablesRequest", - "org.apache.calcite.avatica.proto.Requests$TableTypesRequest", - "org.apache.calcite.avatica.proto.Requests$TypeInfoRequest", + "org.apache.calcite.avatica.proto.Requests$TableTypesRequest", + "org.apache.calcite.avatica.proto.Requests$TypeInfoRequest", "org.apache.calcite.avatica.proto.Requests$ColumnsRequest", - "org.apache.calcite.avatica.proto.Requests$ExecuteRequest", - "org.apache.calcite.avatica.proto.Requests$PrepareRequest", - "org.apache.calcite.avatica.proto.Requests$PrepareAndExecuteRequest", + "org.apache.calcite.avatica.proto.Requests$ExecuteRequest", + "org.apache.calcite.avatica.proto.Requests$PrepareRequest", + "org.apache.calcite.avatica.proto.Requests$PrepareAndExecuteRequest", "org.apache.calcite.avatica.proto.Requests$FetchRequest", - "org.apache.calcite.avatica.proto.Requests$CreateStatementRequest", + "org.apache.calcite.avatica.proto.Requests$CreateStatementRequest", "org.apache.calcite.avatica.proto.Requests$CloseStatementRequest", - "org.apache.calcite.avatica.proto.Requests$OpenConnectionRequest", - "org.apache.calcite.avatica.proto.Requests$CloseConnectionRequest", - "org.apache.calcite.avatica.proto.Requests$ConnectionSyncRequest", - "org.apache.calcite.avatica.proto.Requests$DatabasePropertyRequest", + "org.apache.calcite.avatica.proto.Requests$OpenConnectionRequest", + "org.apache.calcite.avatica.proto.Requests$CloseConnectionRequest", + "org.apache.calcite.avatica.proto.Requests$ConnectionSyncRequest", + "org.apache.calcite.avatica.proto.Requests$DatabasePropertyRequest", "org.apache.calcite.avatica.proto.Requests$SyncResultsRequest", - "org.apache.calcite.avatica.proto.Requests$CommitRequest", + "org.apache.calcite.avatica.proto.Requests$CommitRequest", "org.apache.calcite.avatica.proto.Requests$RollbackRequest", "org.apache.calcite.avatica.proto.Requests$PrepareAndExecuteBatchRequest", "org.apache.calcite.avatica.proto.Requests$ExecuteBatchRequest" }); - + org.apache.calcite.avatica.proto.Common.WireMessage wireMsg = org.apache.calcite.avatica.proto.Common.WireMessage.newBuilder() .setName(subType) .setWrappedMessage(ByteString.copyFrom(data.consumeRemainingAsBytes())) .build(); - + byte[] protobufPayload = wireMsg.toByteArray(); translator.parseRequest(protobufPayload); } else { String subType = data.pickValue(new String[]{ - "org.apache.calcite.avatica.proto.Responses$OpenConnectionResponse", - "org.apache.calcite.avatica.proto.Responses$CloseConnectionResponse", + "org.apache.calcite.avatica.proto.Responses$OpenConnectionResponse", + "org.apache.calcite.avatica.proto.Responses$CloseConnectionResponse", "org.apache.calcite.avatica.proto.Responses$CloseStatementResponse", - "org.apache.calcite.avatica.proto.Responses$ConnectionSyncResponse", - "org.apache.calcite.avatica.proto.Responses$CreateStatementResponse", + "org.apache.calcite.avatica.proto.Responses$ConnectionSyncResponse", + "org.apache.calcite.avatica.proto.Responses$CreateStatementResponse", "org.apache.calcite.avatica.proto.Responses$DatabasePropertyResponse", - "org.apache.calcite.avatica.proto.Responses$ExecuteResponse", - "org.apache.calcite.avatica.proto.Responses$FetchResponse", - "org.apache.calcite.avatica.proto.Responses$PrepareResponse", + "org.apache.calcite.avatica.proto.Responses$ExecuteResponse", + "org.apache.calcite.avatica.proto.Responses$FetchResponse", + "org.apache.calcite.avatica.proto.Responses$PrepareResponse", "org.apache.calcite.avatica.proto.Responses$ResultSetResponse", - "org.apache.calcite.avatica.proto.Responses$ErrorResponse", + "org.apache.calcite.avatica.proto.Responses$ErrorResponse", "org.apache.calcite.avatica.proto.Responses$SyncResultsResponse", - "org.apache.calcite.avatica.proto.Responses$RpcMetadata", - "org.apache.calcite.avatica.proto.Responses$CommitResponse", - "org.apache.calcite.avatica.proto.Responses$RollbackResponse", + "org.apache.calcite.avatica.proto.Responses$RpcMetadata", + "org.apache.calcite.avatica.proto.Responses$CommitResponse", + "org.apache.calcite.avatica.proto.Responses$RollbackResponse", "org.apache.calcite.avatica.proto.Responses$ExecuteBatchResponse" }); - + org.apache.calcite.avatica.proto.Common.WireMessage wireMsg = org.apache.calcite.avatica.proto.Common.WireMessage.newBuilder() .setName(subType) .setWrappedMessage(ByteString.copyFrom(data.consumeRemainingAsBytes())) .build(); - + byte[] protobufPayload = wireMsg.toByteArray(); translator.parseResponse(protobufPayload); } - + } catch (IOException e) { // Known exception from protobuf parsing (e.g. InvalidProtocolBufferException) } catch (IllegalArgumentException | IllegalStateException | NullPointerException e) { // Known issues when Protobuf unmarshalls into Avatica types that fail preconditions } catch (RuntimeException e) { - // Specifically catching Avatica's custom DeserializationException or + // Specifically catching Avatica's custom DeserializationException or // unhandled protobuf issues to ensure the fuzzer survives - if (e.getClass().getName().contains("DeserializationException") || + if (e.getClass().getName().contains("DeserializationException") || e.getClass().getName().contains("InvalidProtocolBufferException") || (e.getMessage() != null && e.getMessage().contains("Unknown type:")) || (e.getMessage() != null && e.getMessage().contains("Unhandled type:"))) { diff --git a/core/src/test/java/org/apache/calcite/avatica/fuzz/TypedValueFuzzer.java b/core/src/test/java/org/apache/calcite/avatica/fuzz/TypedValueFuzzer.java index 10b74cc64..4a5c52f3f 100644 --- a/core/src/test/java/org/apache/calcite/avatica/fuzz/TypedValueFuzzer.java +++ b/core/src/test/java/org/apache/calcite/avatica/fuzz/TypedValueFuzzer.java @@ -16,9 +16,10 @@ */ package org.apache.calcite.avatica.fuzz; -import com.code_intelligence.jazzer.api.FuzzedDataProvider; -import org.apache.calcite.avatica.remote.TypedValue; import org.apache.calcite.avatica.proto.Common; +import org.apache.calcite.avatica.remote.TypedValue; + +import com.code_intelligence.jazzer.api.FuzzedDataProvider; public class TypedValueFuzzer { @@ -26,26 +27,26 @@ public static void fuzzerTestOneInput(FuzzedDataProvider data) { try { // Use the Fuzzer to generate random Protobuf arrays boolean isFromProto = data.consumeBoolean(); - + if (isFromProto) { // Parse it into Common.TypedValue Common.TypedValue protoValue = Common.TypedValue.parseFrom(data.consumeRemainingAsBytes()); - + // Attempt to convert it into a local Avatica TypedValue and then to JDBC representations TypedValue typedValue = TypedValue.fromProto(protoValue); - - // Convert to local and jdbc formats + + // Convert to local and jdbc formats typedValue.toLocal(); typedValue.toJdbc(java.util.Calendar.getInstance()); - + // Attempt Protobuf serialization back typedValue.toProto(); } else { - // Fuzz the direct POJO creator + // Fuzz the direct POJO creator String typeName = data.pickValue(new String[]{ "STRING", "BOOLEAN", "BYTE", "SHORT", "INTEGER", "LONG", "FLOAT", "DOUBLE", "DATE", "TIME", "TIMESTAMP" }); - + Object fakeValue = null; switch (typeName) { case "STRING": fakeValue = data.consumeString(50); break; @@ -58,10 +59,10 @@ public static void fuzzerTestOneInput(FuzzedDataProvider data) { case "DOUBLE": fakeValue = data.consumeDouble(); break; case "DATE": case "TIME": case "TIMESTAMP": fakeValue = data.consumeLong(); break; } - + // Fuzz create factory mapping the object value with random type identifier TypedValue created = TypedValue.create(typeName, fakeValue); - + // Call accessors created.toLocal(); created.toJdbc(java.util.Calendar.getInstance()); From 0215f4e5833e2e9887d883e297f2aa328771e5f1 Mon Sep 17 00:00:00 2001 From: Vishal S Date: Mon, 9 Mar 2026 11:21:41 +0530 Subject: [PATCH 3/6] Fix CI compliance: resolve forbidden APIs, Checkstyle, and Javadoc issues --- .../avatica/fuzz/AvaticaSiteFuzzer.java | 232 +++++++++++------- .../avatica/fuzz/JsonHandlerFuzzer.java | 81 ++++-- .../avatica/fuzz/ProtobufHandlerFuzzer.java | 107 ++++---- .../avatica/fuzz/TypedValueFuzzer.java | 104 +++++--- 4 files changed, 331 insertions(+), 193 deletions(-) diff --git a/core/src/test/java/org/apache/calcite/avatica/fuzz/AvaticaSiteFuzzer.java b/core/src/test/java/org/apache/calcite/avatica/fuzz/AvaticaSiteFuzzer.java index 5d427ff97..81b001692 100644 --- a/core/src/test/java/org/apache/calcite/avatica/fuzz/AvaticaSiteFuzzer.java +++ b/core/src/test/java/org/apache/calcite/avatica/fuzz/AvaticaSiteFuzzer.java @@ -32,8 +32,19 @@ import java.util.Locale; import java.util.TimeZone; +/** + * Fuzzer for AvaticaSite. + */ public class AvaticaSiteFuzzer { + private AvaticaSiteFuzzer() { + } + + /** + * Fuzzes AvaticaSite methods. + * + * @param data fuzzed data + */ public static void fuzzerTestOneInput(FuzzedDataProvider data) { try { // Construct dependencies for an AvaticaSite @@ -47,7 +58,7 @@ public static void fuzzerTestOneInput(FuzzedDataProvider data) { data.consumeString(10) // name ); - Calendar calendar = Calendar.getInstance(TimeZone.getDefault(), Locale.ROOT); + Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC"), Locale.ROOT); TypedValue[] slots = new TypedValue[1]; // Target object @@ -56,108 +67,151 @@ public static void fuzzerTestOneInput(FuzzedDataProvider data) { // Determine what to fuzz int choice = data.consumeInt(1, 16); - switch(choice) { - case 1: - site.setByte(data.consumeByte()); - break; - case 2: - site.setChar(data.consumeChar()); - break; - case 3: - site.setShort(data.consumeShort()); - break; - case 4: - site.setInt(data.consumeInt()); - break; - case 5: - site.setLong(data.consumeLong()); - break; - case 6: - site.setBoolean(data.consumeBoolean()); - break; - case 7: - site.setNString(data.consumeString(50)); - break; - case 8: - site.setFloat(data.consumeFloat()); - break; - case 9: - site.setDouble(data.consumeDouble()); - break; - case 10: - site.setBigDecimal(new BigDecimal(data.consumeDouble())); - break; - case 11: - site.setString(data.consumeString(50)); - break; - case 12: - site.setBytes(data.consumeBytes(50)); - break; - case 13: - site.setTimestamp(new Timestamp(data.consumeLong()), calendar); - break; - case 14: - site.setTime(new Time(data.consumeLong()), calendar); - break; - case 15: - // Raw object mapping - Object obj = null; - int objType = data.consumeInt(1, 4); - if (objType == 1) obj = data.consumeBoolean(); - else if (objType == 2) obj = data.consumeString(50); - else if (objType == 3) obj = data.consumeLong(); - else if (objType == 4) obj = data.consumeBytes(50); + switch (choice) { + case 1: + site.setByte(data.consumeByte()); + break; + case 2: + site.setChar(data.consumeChar()); + break; + case 3: + site.setShort(data.consumeShort()); + break; + case 4: + site.setInt(data.consumeInt()); + break; + case 5: + site.setLong(data.consumeLong()); + break; + case 6: + site.setBoolean(data.consumeBoolean()); + break; + case 7: + site.setNString(data.consumeString(50)); + break; + case 8: + site.setFloat(data.consumeFloat()); + break; + case 9: + site.setDouble(data.consumeDouble()); + break; + case 10: + site.setBigDecimal(new BigDecimal(data.consumeDouble())); + break; + case 11: + site.setString(data.consumeString(50)); + break; + case 12: + site.setBytes(data.consumeBytes(50)); + break; + case 13: + site.setTimestamp(new Timestamp(data.consumeLong()), calendar); + break; + case 14: + site.setTime(new Time(data.consumeLong()), calendar); + break; + case 15: + // Raw object mapping + Object obj = null; + int objType = data.consumeInt(1, 4); + if (objType == 1) { + obj = data.consumeBoolean(); + } else if (objType == 2) { + obj = data.consumeString(50); + } else if (objType == 3) { + obj = data.consumeLong(); + } else if (objType == 4) { + obj = data.consumeBytes(50); + } - site.setObject(obj, data.consumeInt(-10, 100)); // Types constants fall in this range - break; - case 16: - // Test the JDBC ResultSet getter mapping using a dynamic proxy - org.apache.calcite.avatica.util.Cursor.Accessor accessor = + site.setObject(obj, data.consumeInt(-10, 100)); // Types constants fall in this range + break; + case 16: + // Test the JDBC ResultSet getter mapping using a dynamic proxy + org.apache.calcite.avatica.util.Cursor.Accessor accessor = (org.apache.calcite.avatica.util.Cursor.Accessor) Proxy.newProxyInstance( org.apache.calcite.avatica.util.Cursor.Accessor.class.getClassLoader(), - new Class[] { org.apache.calcite.avatica.util.Cursor.Accessor.class }, + new Class[] {org.apache.calcite.avatica.util.Cursor.Accessor.class}, (proxy, method, args) -> { - String name = method.getName(); - if (name.equals("wasNull")) return data.consumeBoolean(); - if (name.equals("getString") || name.equals("getNString")) return data.consumeString(50); - if (name.equals("getBoolean")) return data.consumeBoolean(); - if (name.equals("getByte")) return data.consumeByte(); - if (name.equals("getShort")) return data.consumeShort(); - if (name.equals("getInt")) return data.consumeInt(); - if (name.equals("getLong")) return data.consumeLong(); - if (name.equals("getFloat")) return data.consumeFloat(); - if (name.equals("getDouble")) return data.consumeDouble(); - if (name.equals("getBigDecimal")) return new BigDecimal(data.consumeDouble()); - if (name.equals("getBytes")) return data.consumeBytes(50); - if (name.equals("getDate")) return new Date(data.consumeLong()); - if (name.equals("getTime")) return new Time(data.consumeLong()); - if (name.equals("getTimestamp")) return new Timestamp(data.consumeLong()); - - if (name.equals("getUByte")) return org.joou.UByte.valueOf(data.consumeInt(0, 255)); - if (name.equals("getUShort")) return org.joou.UShort.valueOf(data.consumeInt(0, 65535)); - if (name.equals("getUInt")) return org.joou.UInteger.valueOf(data.consumeLong(0, 4294967295L)); - if (name.equals("getULong")) return org.joou.ULong.valueOf(data.consumeLong(0, Long.MAX_VALUE)); + String name = method.getName(); + if (name.equals("wasNull")) { + return data.consumeBoolean(); + } + if (name.equals("getString") || name.equals("getNString")) { + return data.consumeString(50); + } + if (name.equals("getBoolean")) { + return data.consumeBoolean(); + } + if (name.equals("getByte")) { + return data.consumeByte(); + } + if (name.equals("getShort")) { + return data.consumeShort(); + } + if (name.equals("getInt")) { + return data.consumeInt(); + } + if (name.equals("getLong")) { + return data.consumeLong(); + } + if (name.equals("getFloat")) { + return data.consumeFloat(); + } + if (name.equals("getDouble")) { + return data.consumeDouble(); + } + if (name.equals("getBigDecimal")) { + return new BigDecimal(data.consumeDouble()); + } + if (name.equals("getBytes")) { + return data.consumeBytes(50); + } + if (name.equals("getDate")) { + return new Date(data.consumeLong()); + } + if (name.equals("getTime")) { + return new Time(data.consumeLong()); + } + if (name.equals("getTimestamp")) { + return new Timestamp(data.consumeLong()); + } - return null; + if (name.equals("getUByte")) { + return org.joou.UByte.valueOf(data.consumeInt(0, 255)); } + if (name.equals("getUShort")) { + return org.joou.UShort.valueOf(data.consumeInt(0, 65535)); + } + if (name.equals("getUInt")) { + return org.joou.UInteger.valueOf(data.consumeLong(0, 4294967295L)); + } + if (name.equals("getULong")) { + return org.joou.ULong.valueOf(data.consumeLong(0, Long.MAX_VALUE)); + } + + return null; + } ); - try { - AvaticaSite.get(accessor, data.consumeInt(-10, 100), data.consumeBoolean(), calendar); - } catch (SQLException e) { - // Expected to throw SQLException for unsupported conversions - } - break; + try { + AvaticaSite.get(accessor, data.consumeInt(-10, 100), data.consumeBoolean(), calendar); + } catch (SQLException e) { + // Expected to throw SQLException for unsupported conversions + } + break; + default: + break; } } catch (IllegalArgumentException | UnsupportedOperationException e) { // UnsupportedOperationException is explicitly thrown by AvaticaSite.notImplemented() // and unsupportedCast() when types don't align. } catch (RuntimeException e) { - // TypedValue bindings often throw RuntimeException directly for "not implemented" - if (!"not implemented".equals(e.getMessage())) { - throw e; - } + // TypedValue bindings often throw RuntimeException directly for "not implemented" + if (!"not implemented".equals(e.getMessage())) { + throw e; + } } } } diff --git a/core/src/test/java/org/apache/calcite/avatica/fuzz/JsonHandlerFuzzer.java b/core/src/test/java/org/apache/calcite/avatica/fuzz/JsonHandlerFuzzer.java index 42a94a8e5..59bf7526c 100644 --- a/core/src/test/java/org/apache/calcite/avatica/fuzz/JsonHandlerFuzzer.java +++ b/core/src/test/java/org/apache/calcite/avatica/fuzz/JsonHandlerFuzzer.java @@ -26,26 +26,39 @@ import java.io.IOException; +/** + * Fuzzer for JsonHandler (JsonService). + */ public class JsonHandlerFuzzer { + private JsonHandlerFuzzer() { + } + static { - // Prevent failure on completely unknown properties that the fuzzer might invent - JsonService.MAPPER.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + // Prevent failure on completely unknown properties that the fuzzer might invent + JsonService.MAPPER.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); } + /** + * Fuzzes JSON serialization/deserialization for Avatica Request/Response objects. + * + * @param data fuzzed data + */ public static void fuzzerTestOneInput(FuzzedDataProvider data) { try { - // The goal here is to hit the deeply nested deserialization logic of Avatica's Request and Response models. - // Avatica uses Jackson to parse strings into classes like Service.ExecuteRequest, Service.CatalogsRequest, etc. + // The goal here is to hit the deeply nested deserialization logic of + // Avatica's Request and Response models. + // Avatica uses Jackson to parse strings into classes like + // Service.ExecuteRequest, Service.CatalogsRequest, etc. boolean isRequest = data.consumeBoolean(); if (isRequest) { String subType = data.pickValue(new String[]{ - "getCatalogs", "getSchemas", "getTables", "getTableTypes", "getTypeInfo", "getColumns", - "execute", "prepare", "prepareAndExecute", "fetch", "createStatement", "closeStatement", - "openConnection", "closeConnection", "connectionSync", "databaseProperties", "syncResults", - "commit", "rollback", "prepareAndExecuteBatch", "executeBatch" + "getCatalogs", "getSchemas", "getTables", "getTableTypes", "getTypeInfo", "getColumns", + "execute", "prepare", "prepareAndExecute", "fetch", "createStatement", "closeStatement", + "openConnection", "closeConnection", "connectionSync", "databaseProperties", "syncResults", + "commit", "rollback", "prepareAndExecuteBatch", "executeBatch" }); java.util.Map map = new java.util.HashMap<>(); @@ -54,21 +67,31 @@ public static void fuzzerTestOneInput(FuzzedDataProvider data) { // Add random key/value pairs int numFields = data.consumeInt(0, 10); for (int i = 0; i < numFields; i++) { - switch(data.consumeInt(1, 4)) { - case 1: map.put(data.consumeString(10), data.consumeString(20)); break; - case 2: map.put(data.consumeString(10), data.consumeInt()); break; - case 3: map.put(data.consumeString(10), data.consumeBoolean()); break; - case 4: map.put(data.consumeString(10), null); break; - } + switch (data.consumeInt(1, 4)) { + case 1: + map.put(data.consumeString(10), data.consumeString(20)); + break; + case 2: + map.put(data.consumeString(10), data.consumeInt()); + break; + case 3: + map.put(data.consumeString(10), data.consumeBoolean()); + break; + case 4: + map.put(data.consumeString(10), null); + break; + default: + break; + } } String jsonPayload = JsonService.MAPPER.writeValueAsString(map); JsonService.MAPPER.readValue(jsonPayload, Service.Request.class); } else { String subType = data.pickValue(new String[]{ - "openConnection", "resultSet", "prepare", "fetch", "createStatement", "closeStatement", - "closeConnection", "connectionSync", "databaseProperties", "executeResults", "error", - "syncResults", "rpcMetadata", "commit", "rollback", "executeBatch" + "openConnection", "resultSet", "prepare", "fetch", "createStatement", "closeStatement", + "closeConnection", "connectionSync", "databaseProperties", "executeResults", "error", + "syncResults", "rpcMetadata", "commit", "rollback", "executeBatch" }); java.util.Map map = new java.util.HashMap<>(); @@ -77,12 +100,22 @@ public static void fuzzerTestOneInput(FuzzedDataProvider data) { // Add random key/value pairs int numFields = data.consumeInt(0, 10); for (int i = 0; i < numFields; i++) { - switch(data.consumeInt(1, 4)) { - case 1: map.put(data.consumeString(10), data.consumeString(20)); break; - case 2: map.put(data.consumeString(10), data.consumeInt()); break; - case 3: map.put(data.consumeString(10), data.consumeBoolean()); break; - case 4: map.put(data.consumeString(10), null); break; - } + switch (data.consumeInt(1, 4)) { + case 1: + map.put(data.consumeString(10), data.consumeString(20)); + break; + case 2: + map.put(data.consumeString(10), data.consumeInt()); + break; + case 3: + map.put(data.consumeString(10), data.consumeBoolean()); + break; + case 4: + map.put(data.consumeString(10), null); + break; + default: + break; + } } String jsonPayload = JsonService.MAPPER.writeValueAsString(map); @@ -94,7 +127,7 @@ public static void fuzzerTestOneInput(FuzzedDataProvider data) { } catch (IOException e) { // General IO issues reading the string } catch (IllegalArgumentException | IllegalStateException e) { - // Known issues when Jackson encounters valid JSON but violates Avatica's preconditions + // Known issues when Jackson encounters valid JSON but violates Avatica's preconditions } } } diff --git a/core/src/test/java/org/apache/calcite/avatica/fuzz/ProtobufHandlerFuzzer.java b/core/src/test/java/org/apache/calcite/avatica/fuzz/ProtobufHandlerFuzzer.java index aeacca99a..1475eb18a 100644 --- a/core/src/test/java/org/apache/calcite/avatica/fuzz/ProtobufHandlerFuzzer.java +++ b/core/src/test/java/org/apache/calcite/avatica/fuzz/ProtobufHandlerFuzzer.java @@ -23,10 +23,21 @@ import java.io.IOException; +/** + * Fuzzer for ProtobufHandler (ProtobufTranslation). + */ public class ProtobufHandlerFuzzer { - private static final ProtobufTranslationImpl translator = new ProtobufTranslationImpl(); + private ProtobufHandlerFuzzer() { + } + + private static final ProtobufTranslationImpl TRANSLATOR = new ProtobufTranslationImpl(); + /** + * Fuzzes Protobuf serialization/deserialization for Avatica Request/Response objects. + * + * @param data fuzzed data + */ public static void fuzzerTestOneInput(FuzzedDataProvider data) { try { // The goal here is to hit the protobuf deserialization logic. @@ -37,63 +48,65 @@ public static void fuzzerTestOneInput(FuzzedDataProvider data) { if (isRequest) { String subType = data.pickValue(new String[]{ - "org.apache.calcite.avatica.proto.Requests$CatalogsRequest", - "org.apache.calcite.avatica.proto.Requests$SchemasRequest", - "org.apache.calcite.avatica.proto.Requests$TablesRequest", - "org.apache.calcite.avatica.proto.Requests$TableTypesRequest", - "org.apache.calcite.avatica.proto.Requests$TypeInfoRequest", - "org.apache.calcite.avatica.proto.Requests$ColumnsRequest", - "org.apache.calcite.avatica.proto.Requests$ExecuteRequest", - "org.apache.calcite.avatica.proto.Requests$PrepareRequest", - "org.apache.calcite.avatica.proto.Requests$PrepareAndExecuteRequest", - "org.apache.calcite.avatica.proto.Requests$FetchRequest", - "org.apache.calcite.avatica.proto.Requests$CreateStatementRequest", - "org.apache.calcite.avatica.proto.Requests$CloseStatementRequest", - "org.apache.calcite.avatica.proto.Requests$OpenConnectionRequest", - "org.apache.calcite.avatica.proto.Requests$CloseConnectionRequest", - "org.apache.calcite.avatica.proto.Requests$ConnectionSyncRequest", - "org.apache.calcite.avatica.proto.Requests$DatabasePropertyRequest", - "org.apache.calcite.avatica.proto.Requests$SyncResultsRequest", - "org.apache.calcite.avatica.proto.Requests$CommitRequest", - "org.apache.calcite.avatica.proto.Requests$RollbackRequest", - "org.apache.calcite.avatica.proto.Requests$PrepareAndExecuteBatchRequest", - "org.apache.calcite.avatica.proto.Requests$ExecuteBatchRequest" + "org.apache.calcite.avatica.proto.Requests$CatalogsRequest", + "org.apache.calcite.avatica.proto.Requests$SchemasRequest", + "org.apache.calcite.avatica.proto.Requests$TablesRequest", + "org.apache.calcite.avatica.proto.Requests$TableTypesRequest", + "org.apache.calcite.avatica.proto.Requests$TypeInfoRequest", + "org.apache.calcite.avatica.proto.Requests$ColumnsRequest", + "org.apache.calcite.avatica.proto.Requests$ExecuteRequest", + "org.apache.calcite.avatica.proto.Requests$PrepareRequest", + "org.apache.calcite.avatica.proto.Requests$PrepareAndExecuteRequest", + "org.apache.calcite.avatica.proto.Requests$FetchRequest", + "org.apache.calcite.avatica.proto.Requests$CreateStatementRequest", + "org.apache.calcite.avatica.proto.Requests$CloseStatementRequest", + "org.apache.calcite.avatica.proto.Requests$OpenConnectionRequest", + "org.apache.calcite.avatica.proto.Requests$CloseConnectionRequest", + "org.apache.calcite.avatica.proto.Requests$ConnectionSyncRequest", + "org.apache.calcite.avatica.proto.Requests$DatabasePropertyRequest", + "org.apache.calcite.avatica.proto.Requests$SyncResultsRequest", + "org.apache.calcite.avatica.proto.Requests$CommitRequest", + "org.apache.calcite.avatica.proto.Requests$RollbackRequest", + "org.apache.calcite.avatica.proto.Requests$PrepareAndExecuteBatchRequest", + "org.apache.calcite.avatica.proto.Requests$ExecuteBatchRequest" }); - org.apache.calcite.avatica.proto.Common.WireMessage wireMsg = org.apache.calcite.avatica.proto.Common.WireMessage.newBuilder() + org.apache.calcite.avatica.proto.Common.WireMessage wireMsg = + org.apache.calcite.avatica.proto.Common.WireMessage.newBuilder() .setName(subType) .setWrappedMessage(ByteString.copyFrom(data.consumeRemainingAsBytes())) .build(); byte[] protobufPayload = wireMsg.toByteArray(); - translator.parseRequest(protobufPayload); + TRANSLATOR.parseRequest(protobufPayload); } else { String subType = data.pickValue(new String[]{ - "org.apache.calcite.avatica.proto.Responses$OpenConnectionResponse", - "org.apache.calcite.avatica.proto.Responses$CloseConnectionResponse", - "org.apache.calcite.avatica.proto.Responses$CloseStatementResponse", - "org.apache.calcite.avatica.proto.Responses$ConnectionSyncResponse", - "org.apache.calcite.avatica.proto.Responses$CreateStatementResponse", - "org.apache.calcite.avatica.proto.Responses$DatabasePropertyResponse", - "org.apache.calcite.avatica.proto.Responses$ExecuteResponse", - "org.apache.calcite.avatica.proto.Responses$FetchResponse", - "org.apache.calcite.avatica.proto.Responses$PrepareResponse", - "org.apache.calcite.avatica.proto.Responses$ResultSetResponse", - "org.apache.calcite.avatica.proto.Responses$ErrorResponse", - "org.apache.calcite.avatica.proto.Responses$SyncResultsResponse", - "org.apache.calcite.avatica.proto.Responses$RpcMetadata", - "org.apache.calcite.avatica.proto.Responses$CommitResponse", - "org.apache.calcite.avatica.proto.Responses$RollbackResponse", - "org.apache.calcite.avatica.proto.Responses$ExecuteBatchResponse" + "org.apache.calcite.avatica.proto.Responses$OpenConnectionResponse", + "org.apache.calcite.avatica.proto.Responses$CloseConnectionResponse", + "org.apache.calcite.avatica.proto.Responses$CloseStatementResponse", + "org.apache.calcite.avatica.proto.Responses$ConnectionSyncResponse", + "org.apache.calcite.avatica.proto.Responses$CreateStatementResponse", + "org.apache.calcite.avatica.proto.Responses$DatabasePropertyResponse", + "org.apache.calcite.avatica.proto.Responses$ExecuteResponse", + "org.apache.calcite.avatica.proto.Responses$FetchResponse", + "org.apache.calcite.avatica.proto.Responses$PrepareResponse", + "org.apache.calcite.avatica.proto.Responses$ResultSetResponse", + "org.apache.calcite.avatica.proto.Responses$ErrorResponse", + "org.apache.calcite.avatica.proto.Responses$SyncResultsResponse", + "org.apache.calcite.avatica.proto.Responses$RpcMetadata", + "org.apache.calcite.avatica.proto.Responses$CommitResponse", + "org.apache.calcite.avatica.proto.Responses$RollbackResponse", + "org.apache.calcite.avatica.proto.Responses$ExecuteBatchResponse" }); - org.apache.calcite.avatica.proto.Common.WireMessage wireMsg = org.apache.calcite.avatica.proto.Common.WireMessage.newBuilder() + org.apache.calcite.avatica.proto.Common.WireMessage wireMsg = + org.apache.calcite.avatica.proto.Common.WireMessage.newBuilder() .setName(subType) .setWrappedMessage(ByteString.copyFrom(data.consumeRemainingAsBytes())) .build(); byte[] protobufPayload = wireMsg.toByteArray(); - translator.parseResponse(protobufPayload); + TRANSLATOR.parseResponse(protobufPayload); } } catch (IOException e) { @@ -103,11 +116,11 @@ public static void fuzzerTestOneInput(FuzzedDataProvider data) { } catch (RuntimeException e) { // Specifically catching Avatica's custom DeserializationException or // unhandled protobuf issues to ensure the fuzzer survives - if (e.getClass().getName().contains("DeserializationException") || - e.getClass().getName().contains("InvalidProtocolBufferException") || - (e.getMessage() != null && e.getMessage().contains("Unknown type:")) || - (e.getMessage() != null && e.getMessage().contains("Unhandled type:"))) { - return; + if (e.getClass().getName().contains("DeserializationException") + || e.getClass().getName().contains("InvalidProtocolBufferException") + || e.getMessage() != null && e.getMessage().contains("Unknown type:") + || e.getMessage() != null && e.getMessage().contains("Unhandled type:")) { + return; } // If it's a real bug (NullPointerException, etc), let it crash! throw e; diff --git a/core/src/test/java/org/apache/calcite/avatica/fuzz/TypedValueFuzzer.java b/core/src/test/java/org/apache/calcite/avatica/fuzz/TypedValueFuzzer.java index 4a5c52f3f..6243382dc 100644 --- a/core/src/test/java/org/apache/calcite/avatica/fuzz/TypedValueFuzzer.java +++ b/core/src/test/java/org/apache/calcite/avatica/fuzz/TypedValueFuzzer.java @@ -21,59 +21,97 @@ import com.code_intelligence.jazzer.api.FuzzedDataProvider; +import java.util.Calendar; +import java.util.Locale; +import java.util.TimeZone; + +/** + * Fuzzer for TypedValue. + */ public class TypedValueFuzzer { + private TypedValueFuzzer() { + } + + /** + * Fuzzes TypedValue conversion methods. + * + * @param data fuzzed data + */ public static void fuzzerTestOneInput(FuzzedDataProvider data) { try { // Use the Fuzzer to generate random Protobuf arrays boolean isFromProto = data.consumeBoolean(); if (isFromProto) { - // Parse it into Common.TypedValue - Common.TypedValue protoValue = Common.TypedValue.parseFrom(data.consumeRemainingAsBytes()); + // Parse it into Common.TypedValue + Common.TypedValue protoValue = Common.TypedValue.parseFrom(data.consumeRemainingAsBytes()); - // Attempt to convert it into a local Avatica TypedValue and then to JDBC representations - TypedValue typedValue = TypedValue.fromProto(protoValue); + // Attempt to convert it into a local Avatica TypedValue and then to JDBC representations + TypedValue typedValue = TypedValue.fromProto(protoValue); - // Convert to local and jdbc formats - typedValue.toLocal(); - typedValue.toJdbc(java.util.Calendar.getInstance()); + // Convert to local and jdbc formats + typedValue.toLocal(); + typedValue.toJdbc(Calendar.getInstance(TimeZone.getTimeZone("UTC"), Locale.ROOT)); - // Attempt Protobuf serialization back - typedValue.toProto(); + // Attempt Protobuf serialization back + typedValue.toProto(); } else { - // Fuzz the direct POJO creator - String typeName = data.pickValue(new String[]{ - "STRING", "BOOLEAN", "BYTE", "SHORT", "INTEGER", "LONG", "FLOAT", "DOUBLE", "DATE", "TIME", "TIMESTAMP" - }); + // Fuzz the direct POJO creator + String typeName = data.pickValue(new String[]{ + "STRING", "BOOLEAN", "BYTE", "SHORT", "INTEGER", "LONG", "FLOAT", "DOUBLE", "DATE", "TIME", "TIMESTAMP" + }); - Object fakeValue = null; - switch (typeName) { - case "STRING": fakeValue = data.consumeString(50); break; - case "BOOLEAN": fakeValue = data.consumeBoolean(); break; - case "BYTE": fakeValue = data.consumeByte(); break; - case "SHORT": fakeValue = data.consumeShort(); break; - case "INTEGER": fakeValue = data.consumeInt(); break; - case "LONG": fakeValue = data.consumeLong(); break; - case "FLOAT": fakeValue = data.consumeFloat(); break; - case "DOUBLE": fakeValue = data.consumeDouble(); break; - case "DATE": case "TIME": case "TIMESTAMP": fakeValue = data.consumeLong(); break; - } + Object fakeValue = null; + switch (typeName) { + case "STRING": + fakeValue = data.consumeString(50); + break; + case "BOOLEAN": + fakeValue = data.consumeBoolean(); + break; + case "BYTE": + fakeValue = data.consumeByte(); + break; + case "SHORT": + fakeValue = data.consumeShort(); + break; + case "INTEGER": + fakeValue = data.consumeInt(); + break; + case "LONG": + fakeValue = data.consumeLong(); + break; + case "FLOAT": + fakeValue = data.consumeFloat(); + break; + case "DOUBLE": + fakeValue = data.consumeDouble(); + break; + case "DATE": + case "TIME": + case "TIMESTAMP": + fakeValue = data.consumeLong(); + break; + default: + break; + } - // Fuzz create factory mapping the object value with random type identifier - TypedValue created = TypedValue.create(typeName, fakeValue); + // Fuzz create factory mapping the object value with random type identifier + TypedValue created = TypedValue.create(typeName, fakeValue); - // Call accessors - created.toLocal(); - created.toJdbc(java.util.Calendar.getInstance()); - created.toProto(); + // Call accessors + created.toLocal(); + created.toJdbc(Calendar.getInstance(TimeZone.getTimeZone("UTC"), Locale.ROOT)); + created.toProto(); } } catch (java.io.IOException e) { // Known exception for invalid protobuf } catch (RuntimeException e) { - // TypedValue parser is known to throw unchecked exceptions when types don't align with values in the protobuf - // E.g., asking for a Boolean from a protobuf field that was stored as a String. + // TypedValue parser is known to throw unchecked exceptions + // when types don't align with values in the protobuf + // E.g., asking for a Boolean from a protobuf field that was stored as a String. } } } From 6d5e704bdc6939bd0ab961e0b9234b84af0c98b7 Mon Sep 17 00:00:00 2001 From: Vishal S Date: Mon, 9 Mar 2026 13:59:02 +0530 Subject: [PATCH 4/6] [CALCITE-7436] Add 6 deep fuzz targets and automate with CIFuzz --- .github/workflows/cifuzz.yml | 33 +++++++++++++ core/build.gradle.kts | 5 ++ .../calcite/avatica/fuzz/Base64Fuzzer.java | 48 +++++++++++++++++++ .../fuzz/ConnectStringParserFuzzer.java | 44 +++++++++++++++++ 4 files changed, 130 insertions(+) create mode 100644 .github/workflows/cifuzz.yml create mode 100644 core/src/test/java/org/apache/calcite/avatica/fuzz/Base64Fuzzer.java create mode 100644 core/src/test/java/org/apache/calcite/avatica/fuzz/ConnectStringParserFuzzer.java diff --git a/.github/workflows/cifuzz.yml b/.github/workflows/cifuzz.yml new file mode 100644 index 000000000..a0021eb32 --- /dev/null +++ b/.github/workflows/cifuzz.yml @@ -0,0 +1,33 @@ +name: CIFuzz +on: + pull_request: + branches: + - main + - master + +permissions: + contents: read + +jobs: + Fuzzing: + runs-on: ubuntu-latest + steps: + - name: Build Fuzzers + id: build + uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@master + with: + oss-fuzz-project-name: 'calcite-avatica' + language: jvm + - name: Run Fuzzers + uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@master + with: + oss-fuzz-project-name: 'calcite-avatica' + language: jvm + fuzz-seconds: 600 + output-sarif: true + - name: Upload Crash + uses: actions/upload-artifact@v4 + if: failure() && steps.build.outcome == 'success' + with: + name: artifacts + path: ./out/artifacts diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 9c3ee71f1..6f0adda7e 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -118,3 +118,8 @@ tasks.autostyleJavaCheck { dependsOn(filterJava) dependsOn(tasks.getByName("generateProto")) } + +tasks.register("copyFuzzDependencies") { + from(configurations.testRuntimeClasspath) + into(layout.buildDirectory.dir("fuzz-dependencies")) +} diff --git a/core/src/test/java/org/apache/calcite/avatica/fuzz/Base64Fuzzer.java b/core/src/test/java/org/apache/calcite/avatica/fuzz/Base64Fuzzer.java new file mode 100644 index 000000000..6c09c010f --- /dev/null +++ b/core/src/test/java/org/apache/calcite/avatica/fuzz/Base64Fuzzer.java @@ -0,0 +1,48 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.calcite.avatica.fuzz; + +import com.code_intelligence.jazzer.api.FuzzedDataProvider; + +import org.apache.calcite.avatica.util.Base64; + +import java.io.IOException; + +/** + * Fuzzer for Base64 utility class. + */ +public final class Base64Fuzzer { + private Base64Fuzzer() { + } + + /** + * Fuzzes Base64 encode and decode methods. + * + * @param data fuzzed data provider + */ + public static void fuzzerTestOneInput(FuzzedDataProvider data) { + try { + if (data.consumeBoolean()) { + Base64.encodeBytes(data.consumeRemainingAsBytes()); + } else { + Base64.decode(data.consumeRemainingAsBytes()); + } + } catch (IOException | IllegalArgumentException e) { + // Known exception + } + } +} diff --git a/core/src/test/java/org/apache/calcite/avatica/fuzz/ConnectStringParserFuzzer.java b/core/src/test/java/org/apache/calcite/avatica/fuzz/ConnectStringParserFuzzer.java new file mode 100644 index 000000000..59bfd53a9 --- /dev/null +++ b/core/src/test/java/org/apache/calcite/avatica/fuzz/ConnectStringParserFuzzer.java @@ -0,0 +1,44 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.calcite.avatica.fuzz; + +import com.code_intelligence.jazzer.api.FuzzedDataProvider; + +import org.apache.calcite.avatica.ConnectStringParser; + +import java.sql.SQLException; + +/** + * Fuzzer for ConnectStringParser. + */ +public final class ConnectStringParserFuzzer { + private ConnectStringParserFuzzer() { + } + + /** + * Fuzzes the ConnectStringParser parse method. + * + * @param data fuzzed data provider + */ + public static void fuzzerTestOneInput(FuzzedDataProvider data) { + try { + ConnectStringParser.parse(data.consumeRemainingAsString()); + } catch (SQLException e) { + // Known exception + } + } +} From 19e323f29aabe13e4f0221e1bc396b00171ec96d Mon Sep 17 00:00:00 2001 From: Vishal S Date: Mon, 9 Mar 2026 14:15:21 +0530 Subject: [PATCH 5/6] fixed indentation in yaml --- .github/workflows/cifuzz.yml | 56 ++++++++++++++++++++++++------------ 1 file changed, 37 insertions(+), 19 deletions(-) diff --git a/.github/workflows/cifuzz.yml b/.github/workflows/cifuzz.yml index a0021eb32..99efc2414 100644 --- a/.github/workflows/cifuzz.yml +++ b/.github/workflows/cifuzz.yml @@ -1,3 +1,19 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to you under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# name: CIFuzz on: pull_request: @@ -12,22 +28,24 @@ jobs: Fuzzing: runs-on: ubuntu-latest steps: - - name: Build Fuzzers - id: build - uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@master - with: - oss-fuzz-project-name: 'calcite-avatica' - language: jvm - - name: Run Fuzzers - uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@master - with: - oss-fuzz-project-name: 'calcite-avatica' - language: jvm - fuzz-seconds: 600 - output-sarif: true - - name: Upload Crash - uses: actions/upload-artifact@v4 - if: failure() && steps.build.outcome == 'success' - with: - name: artifacts - path: ./out/artifacts + - name: Build Fuzzers + id: build + uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@master + with: + oss-fuzz-project-name: "calcite-avatica" + language: jvm + + - name: Run Fuzzers + uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@master + with: + oss-fuzz-project-name: "calcite-avatica" + language: jvm + fuzz-seconds: 600 + output-sarif: true + + - name: Upload Crash + uses: actions/upload-artifact@v4 + if: failure() && steps.build.outcome == 'success' + with: + name: artifacts + path: ./out/artifacts From 6fe196ad93abaf657ae7c09fdeffebfe1724ff70 Mon Sep 17 00:00:00 2001 From: Vishal S Date: Mon, 9 Mar 2026 14:41:06 +0530 Subject: [PATCH 6/6] Fix import ordering for Avatica core checkstyle --- .../java/org/apache/calcite/avatica/fuzz/Base64Fuzzer.java | 4 ++-- .../calcite/avatica/fuzz/ConnectStringParserFuzzer.java | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/core/src/test/java/org/apache/calcite/avatica/fuzz/Base64Fuzzer.java b/core/src/test/java/org/apache/calcite/avatica/fuzz/Base64Fuzzer.java index 6c09c010f..56092b7d8 100644 --- a/core/src/test/java/org/apache/calcite/avatica/fuzz/Base64Fuzzer.java +++ b/core/src/test/java/org/apache/calcite/avatica/fuzz/Base64Fuzzer.java @@ -16,10 +16,10 @@ */ package org.apache.calcite.avatica.fuzz; -import com.code_intelligence.jazzer.api.FuzzedDataProvider; - import org.apache.calcite.avatica.util.Base64; +import com.code_intelligence.jazzer.api.FuzzedDataProvider; + import java.io.IOException; /** diff --git a/core/src/test/java/org/apache/calcite/avatica/fuzz/ConnectStringParserFuzzer.java b/core/src/test/java/org/apache/calcite/avatica/fuzz/ConnectStringParserFuzzer.java index 59bfd53a9..f6422a675 100644 --- a/core/src/test/java/org/apache/calcite/avatica/fuzz/ConnectStringParserFuzzer.java +++ b/core/src/test/java/org/apache/calcite/avatica/fuzz/ConnectStringParserFuzzer.java @@ -16,10 +16,10 @@ */ package org.apache.calcite.avatica.fuzz; -import com.code_intelligence.jazzer.api.FuzzedDataProvider; - import org.apache.calcite.avatica.ConnectStringParser; +import com.code_intelligence.jazzer.api.FuzzedDataProvider; + import java.sql.SQLException; /**