Skip to content

Commit ca6211d

Browse files
committed
#894 Fix negative buffer size on Firebird 2.5 if information response is 32KiB or greater
1 parent 98a62ed commit ca6211d

3 files changed

Lines changed: 75 additions & 3 deletions

File tree

src/docs/asciidoc/release_notes.adoc

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,18 @@ Changes per Jaybird 5 release.
2929
See also <<whats-new-in-jaybird-5>>.
3030
For known issues, consult <<known-issues>>.
3131

32+
[#jaybird-5-0-10-changelog]
33+
=== Jaybird 5.0.10
34+
35+
The following has been changed or fixed since Jaybird 5.0.9:
36+
37+
* Fixed: Negative buffer size on Firebird 2.5 if information response is 32KiB or greater (https://github.com/FirebirdSQL/jaybird/issues/894[#894])
38+
+
39+
The error occurs when preparing or executing statements with a lot of columns, especially select statements with columns that have long names and/or aliases for columns, tables and table owners.
40+
Symptoms include unexpected closed connections, and errors like "`__Unsupported or unexpected operation code <number> in processOperation__`" (with varying numbers) and "`__java.lang.NegativeArraySizeException: -1__`" (and other negative numbers).
41+
+
42+
Only Firebird 2.5 is affected.
43+
3244
[#jaybird-5-0-9-changelog]
3345
=== Jaybird 5.0.9
3446

src/main/org/firebirdsql/gds/impl/wire/XdrInputStream.java

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -118,13 +118,18 @@ public int skipFully(int numbytes) throws IOException {
118118
* underlying input stream
119119
*/
120120
public byte[] readBuffer() throws IOException {
121-
int len = readInt();
122-
byte[] buffer = new byte[len];
123-
readFully(buffer, 0, len);
121+
int len = fixupLength(readInt());
122+
byte[] buffer = readRawBuffer(len);
124123
skipPadding(len);
125124
return buffer;
126125
}
127126

127+
private static int fixupLength(int len) {
128+
// Older Firebird versions may return a 32-bit value that is a sign-extended 16-bit value.
129+
// Firebird does something similar in remote/protocol.cpp (xdr_cstring_with_limit).
130+
return len >>> 16 == 0xFFFF ? len & 0xFFFF : len;
131+
}
132+
128133
/**
129134
* Read in a raw array of bytes.
130135
*

src/test/org/firebirdsql/jdbc/FBPreparedStatementTest.java

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
import org.firebirdsql.common.DataGenerator;
2222
import org.firebirdsql.common.FBTestProperties;
23+
import org.firebirdsql.common.extension.DatabaseUserExtension;
2324
import org.firebirdsql.common.extension.UsesDatabaseExtension;
2425
import org.firebirdsql.gds.ISCConstants;
2526
import org.firebirdsql.jaybird.props.PropertyNames;
@@ -44,6 +45,7 @@
4445
import java.sql.*;
4546
import java.util.*;
4647
import java.util.concurrent.atomic.AtomicBoolean;
48+
import java.util.stream.IntStream;
4749
import java.util.stream.Stream;
4850

4951
import static org.firebirdsql.common.DdlHelper.executeCreateTable;
@@ -68,6 +70,8 @@ class FBPreparedStatementTest {
6870

6971
@RegisterExtension
7072
static final UsesDatabaseExtension.UsesDatabaseForAll usesDatabase = UsesDatabaseExtension.usesDatabaseForAll();
73+
@RegisterExtension
74+
final DatabaseUserExtension databaseUsers = DatabaseUserExtension.withDatabaseUser();
7175

7276
//@formatter:off
7377
private static final String DROP_GENERATOR = "DROP GENERATOR test_generator";
@@ -1550,6 +1554,57 @@ void executeQueryWithExceptionShouldEndTransactionInAutocommit() throws Exceptio
15501554
}
15511555
}
15521556

1557+
/**
1558+
* Prepare a statement where the info size exceeds 32KiB.
1559+
* <p>
1560+
* Rationale: <a href="https://github.com/FirebirdSQL/jaybird/issues/894">#894</a>
1561+
* </p>
1562+
*/
1563+
@Test
1564+
void prepareStatementWithInfoExceeding32K() throws Exception {
1565+
final String owner = "U234567890123456789012345678901";
1566+
databaseUsers.createUser(owner, owner);
1567+
if (getDefaultSupportInfo().isVersionEqualOrAbove(3, 0)) {
1568+
try (Statement stmt = con.createStatement()) {
1569+
stmt.execute("grant create table to user " + owner);
1570+
}
1571+
}
1572+
Map<String, String> connectionProps = new HashMap<>();
1573+
connectionProps.put("user", owner);
1574+
connectionProps.put("password", owner);
1575+
// On Firebird 3+, this results in an info response of ~256KiB;
1576+
// on Firebird 2.5 it requires multiple round trips
1577+
final int columnCount = 1500;
1578+
try (Connection connection = getConnectionViaDriverManager(connectionProps)) {
1579+
final String tableName = "T234567890123456789012345678901";
1580+
try (Statement stmt = connection.createStatement()) {
1581+
StringBuilder sb = new StringBuilder(50 + columnCount * 41);
1582+
sb.append("create table ").append(tableName).append('(');
1583+
IntStream.rangeClosed(1, columnCount).forEach(idx ->
1584+
sb.append(String.format("C%030d smallint,", idx)));
1585+
// remove last comma
1586+
sb.setLength(sb.length() - 1);
1587+
sb.append(')');
1588+
stmt.execute(sb.toString());
1589+
}
1590+
1591+
try (PreparedStatement pstmt = connection.prepareStatement("select * from " + tableName)) {
1592+
ResultSetMetaData rsmd = pstmt.getMetaData();
1593+
assertEquals(columnCount, rsmd.getColumnCount(), "columnCount");
1594+
IntStream.rangeClosed(1, columnCount).forEach(idx -> {
1595+
try {
1596+
assertEquals(tableName, rsmd.getTableName(idx), "tableName");
1597+
final String expectedName = String.format("C%030d", idx);
1598+
assertEquals(expectedName, rsmd.getColumnName(idx), "columnName");
1599+
assertEquals(expectedName, rsmd.getColumnLabel(idx), "columnLabel");
1600+
} catch (SQLException e) {
1601+
fail(e);
1602+
}
1603+
});
1604+
}
1605+
}
1606+
}
1607+
15531608
private void prepareTestData() throws SQLException {
15541609
con.setAutoCommit(false);
15551610
try (PreparedStatement pstmt = con.prepareStatement(INSERT_DATA)) {

0 commit comments

Comments
 (0)