Skip to content

Commit 9bc0dc4

Browse files
rachavzRaul Chavez
authored andcommitted
Add null checks to prevent SIGSEGV when getTableByName returns null
When a table name stored in sqlite_master differs in case from the name in the CREATE TABLE statement (e.g. databases created by Turso), the getTableByName lookup could return a null TablePtr. Multiple call sites dereferenced the result without checking for null, causing SIGSEGV crashes. This complements the case-insensitive lookup fix (commit 222e430) by adding defensive null checks at all remaining unprotected call sites in the browse data path and related operations. Fixes #4110
1 parent 0db3147 commit 9bc0dc4

File tree

3 files changed

+48
-21
lines changed

3 files changed

+48
-21
lines changed

src/TableBrowser.cpp

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -897,15 +897,18 @@ void TableBrowser::applyViewportSettings(const BrowseDataTableSettings& storedDa
897897

898898
// Show/hide some menu options depending on whether this is a table or a view
899899
const auto table = db->getTableByName(tablename);
900-
if(!table->isView())
900+
if(table)
901901
{
902-
// Table
903-
ui->actionUnlockViewEditing->setVisible(false);
904-
ui->actionShowRowidColumn->setVisible(!table->withoutRowidTable());
905-
} else {
906-
// View
907-
ui->actionUnlockViewEditing->setVisible(true);
908-
ui->actionShowRowidColumn->setVisible(false);
902+
if(!table->isView())
903+
{
904+
// Table
905+
ui->actionUnlockViewEditing->setVisible(false);
906+
ui->actionShowRowidColumn->setVisible(!table->withoutRowidTable());
907+
} else {
908+
// View
909+
ui->actionUnlockViewEditing->setVisible(true);
910+
ui->actionShowRowidColumn->setVisible(false);
911+
}
909912
}
910913

911914
// Frozen columns
@@ -990,8 +993,11 @@ void TableBrowser::generateFilters()
990993
FilterTableHeader* filterHeader = qobject_cast<FilterTableHeader*>(ui->dataTable->horizontalHeader());
991994
bool oldState = filterHeader->blockSignals(true);
992995
auto obj = db->getTableByName(currentlyBrowsedTableName());
993-
for(auto filterIt=settings.filterValues.cbegin();filterIt!=settings.filterValues.cend();++filterIt)
994-
ui->dataTable->setFilter(sqlb::getFieldNumber(obj, filterIt->first) + 1, filterIt->second);
996+
if(obj)
997+
{
998+
for(auto filterIt=settings.filterValues.cbegin();filterIt!=settings.filterValues.cend();++filterIt)
999+
ui->dataTable->setFilter(sqlb::getFieldNumber(obj, filterIt->first) + 1, filterIt->second);
1000+
}
9951001
filterHeader->blockSignals(oldState);
9961002

9971003
ui->actionClearFilters->setEnabled(m_model->filterCount() > 0 || !ui->editGlobalFilter->text().isEmpty());
@@ -1447,7 +1453,10 @@ void TableBrowser::editDisplayFormat()
14471453
// column is always the rowid column. Ultimately, get the column name from the column object
14481454
sqlb::ObjectIdentifier current_table = currentlyBrowsedTableName();
14491455
size_t field_number = sender()->property("clicked_column").toUInt();
1450-
QString field_name = QString::fromStdString(db->getTableByName(current_table)->fields.at(field_number-1).name());
1456+
auto tbl = db->getTableByName(current_table);
1457+
if(!tbl || field_number < 1 || field_number > tbl->fields.size())
1458+
return;
1459+
QString field_name = QString::fromStdString(tbl->fields.at(field_number-1).name());
14511460

14521461
// Get the current display format of the field
14531462
QString current_displayformat = m_settings[current_table].displayFormats[field_number];
@@ -1548,7 +1557,10 @@ void TableBrowser::setDefaultTableEncoding()
15481557
void TableBrowser::copyColumnName(){
15491558
sqlb::ObjectIdentifier current_table = currentlyBrowsedTableName();
15501559
int col_index = ui->actionBrowseTableEditDisplayFormat->property("clicked_column").toInt();
1551-
QString field_name = QString::fromStdString(db->getTableByName(current_table)->fields.at(col_index - 1).name());
1560+
auto tbl = db->getTableByName(current_table);
1561+
if(!tbl || col_index < 1 || col_index > static_cast<int>(tbl->fields.size()))
1562+
return;
1563+
QString field_name = QString::fromStdString(tbl->fields.at(col_index - 1).name());
15521564

15531565
QClipboard *clipboard = QApplication::clipboard();
15541566
clipboard->setText(field_name);

src/sqlitedb.cpp

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1333,7 +1333,10 @@ bool DBBrowserDB::getRow(const sqlb::ObjectIdentifier& table, const QString& row
13331333
std::string query = "SELECT * FROM " + table.toString() + " WHERE ";
13341334

13351335
// For a single rowid column we can use a simple WHERE condition, for multiple rowid columns we have to use sqlb_make_single_value to decode the composed rowid values.
1336-
sqlb::StringVector pks = getTableByName(table)->rowidColumns();
1336+
auto tbl = getTableByName(table);
1337+
if(!tbl)
1338+
return false;
1339+
sqlb::StringVector pks = tbl->rowidColumns();
13371340
if(pks.size() == 1)
13381341
query += sqlb::escapeIdentifier(pks.front()) + "='" + rowid.toStdString() + "'";
13391342
else
@@ -1375,14 +1378,18 @@ unsigned long DBBrowserDB::max(const sqlb::ObjectIdentifier& tableName, const st
13751378
// If, however, there is a sequence table in this database and the given column is the primary key of the table, we try to look up a value in the sequence table
13761379
if(schemata.at(tableName.schema()).tables.count("sqlite_sequence"))
13771380
{
1378-
auto pk = getTableByName(tableName)->primaryKeyColumns();
1379-
if(pk.size() == 1 && pk.front().name() == field)
1381+
auto tbl = getTableByName(tableName);
1382+
if(tbl)
13801383
{
1381-
// This SQL statement tries to do two things in one statement: get the current sequence number for this table from the sqlite_sequence table or, if there is no record for the table, return the highest integer value in the given column.
1382-
// This works by querying the sqlite_sequence table and using an aggregate function (SUM in this case) to make sure to always get exactly one result row, no matter if there is a sequence record or not. We then let COALESCE decide
1383-
// whether to return that sequence value if there is one or fall back to the SELECT MAX statement from avove if there is no sequence value.
1384-
query = "SELECT COALESCE(SUM(seq), (" + query + ")) FROM sqlite_sequence WHERE name=" + sqlb::escapeString(tableName.name());
1385-
}
1384+
auto pk = tbl->primaryKeyColumns();
1385+
if(pk.size() == 1 && pk.front().name() == field)
1386+
{
1387+
// This SQL statement tries to do two things in one statement: get the current sequence number for this table from the sqlite_sequence table or, if there is no record for the table, return the highest integer value in the given column.
1388+
// This works by querying the sqlite_sequence table and using an aggregate function (SUM in this case) to make sure to always get exactly one result row, no matter if there is a sequence record or not. We then let COALESCE decide
1389+
// whether to return that sequence value if there is one or fall back to the SELECT MAX statement from avove if there is no sequence value.
1390+
query = "SELECT COALESCE(SUM(seq), (" + query + ")) FROM sqlite_sequence WHERE name=" + sqlb::escapeString(tableName.name());
1391+
}
1392+
}
13861393
}
13871394

13881395
return querySingleValueFromDb(query).toULong();
@@ -1789,7 +1796,9 @@ bool DBBrowserDB::alterTable(const sqlb::ObjectIdentifier& tablename, const sqlb
17891796
if(changed_something)
17901797
{
17911798
updateSchema();
1792-
old_table = *getTableByName(sqlb::ObjectIdentifier(tablename.schema(), new_table.name()));
1799+
auto newTbl = getTableByName(sqlb::ObjectIdentifier(tablename.schema(), new_table.name()));
1800+
if(newTbl)
1801+
old_table = *newTbl;
17931802
}
17941803

17951804
// Check if there's still more work to be done or if we are finished now

src/sqlitetablemodel.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,12 @@ void SqliteTableModel::setQuery(const sqlb::Query& query)
132132

133133
// Set the row id columns
134134
m_table_of_query = m_db.getTableByName(query.table());
135+
if(!m_table_of_query)
136+
{
137+
// Table not found in schema (e.g. case mismatch between sqlite_master and SQL statement, or table dropped).
138+
// Cannot proceed without table metadata.
139+
return;
140+
}
135141
if(!m_table_of_query->isView())
136142
{
137143
// It is a table

0 commit comments

Comments
 (0)