Hacktoberfest 2026: los issues que los mantenedores marcaron para octubre, abiertos y aptos para principiantes. Explorar issues de Hacktoberfest

Rust local Row::get_value truncates embedded NUL TEXT and panics on malformed UTF-8 (0.9.30)

Abierto Apto para principiantes
#2,285 0 comentarios 0 reacciones 0 asignados Ver en GitHub

@Sanjays2402 ya está trabajando en esto.

Desde el 12/10/2026.

  • #2288 de @Sanjays2402 — abierto

Evaluación

Dificultad
2/5
Tiempo estimado
1-3 horas
Aptitud para principiantes
82/100
Tipo de issue
Error
Claridad
Bien especificado
Estado de actividad
Activo
Stack tecnológico
rust, sqlite
Área
databases

Línea de trabajo

Empieza en libsql/src/local/rows.rs, comparando Row::get_value con la ruta get_ref existente que conserva la longitud exacta en bytes. Verifica que los valores TEXT propios conserven los bytes NUL incrustados y devuelvan un error para UTF-8 no válido, mientras que los valores NULL, numéricos, BLOB, vacíos y Unicode mantengan sus tipos esperados; ejecuta las pruebas locales relevantes de Rust o añade regresiones si falta cobertura.

Escrito por el modelo de indexación a partir del texto del issue.

Descripción

The local Rust driver in libsql 0.9.30 silently truncates owned TEXT values at an embedded NUL, and panics rather than returning an error when the stored TEXT contains malformed UTF-8. The SQLite value remains intact; the loss happens in the owned-value conversion.

Reproduced on macOS arm64 with Rust 1.98.1, using registry libsql/libsql-sys/libsql-ffi 0.9.30 and the local core feature. There is no downstream patch in this reproduction.

Cargo.toml:

[package]
name = "libsql-owned-text-report"
version = "0.1.0"
edition = "2024"

[dependencies]
libsql = { version = "=0.9.30", default-features = false, features = ["core"] }
tokio = { version = "=1.47.1", features = ["rt", "macros"] }

src/main.rs:

use libsql::{Builder, Value};

#[tokio::main(flavor = "current_thread")]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let database = Builder::new_local(":memory:").build().await?;
    let connection = database.connect()?;
    let mut rows = connection.query("SELECT ?1", ["a\0b"]).await?;
    let row = rows.next().await?.unwrap();
    println!("expected {:?}, actual {:?}",
        Value::Text("a\0b".into()), row.get_value(0)?);
    drop(row);
    drop(rows);

    let mut rows = connection.query("SELECT CAST(x'80' AS TEXT)", ()).await?;
    let row = rows.next().await?.unwrap();
    let result = std::panic::catch_unwind(
        std::panic::AssertUnwindSafe(|| row.get_value(0))
    );
    println!("malformed UTF-8 panicked={}", result.is_err());
    Ok(())
}

Observed:

expected Text("a\0b"), actual Text("a")
... value.rs:237:40: called Result::unwrap() on an Err value: Utf8Error ...
malformed UTF-8 panicked=true

Expected: the first owned value preserves all three bytes; the second returns a decoding error through Result instead of panicking. Truncation also makes distinct persisted TEXT keys appear identical to code reading owned values.

The local Row::get_value path converts sqlite3_column_text using a NUL-terminated CStr, then the owned FromValue conversion unwraps UTF-8 decoding. The existing Row::get_ref path already obtains the exact byte length. A downstream correction detaches those ValueRef values, preserving TEXT length and returning InvalidColumnType on failed std::str::from_utf8. NULL, integer, real and BLOB retain their storage types; empty TEXT/BLOB and Unicode remain supported.

Our downstream regressions cover embedded NUL/Unicode, empty values, malformed UTF-8 refusal, detached owned values and subsequent connection reuse. This report is independent of the connection teardown issue #2251.

Proposed local-driver patch (the SQL engine is unchanged):

--- a/libsql/src/local/rows.rs
+++ b/libsql/src/local/rows.rs
@@ -127,8 +127,17 @@
     }
 
     pub fn get_value(&self, idx: i32) -> Result<Value> {
-        let val = self.stmt.inner.column_value(idx);
-        <crate::Value as FromValue>::from_sql(val)
+        // Use the exact byte length, preserve NULs,
+        // and reject invalid UTF-8 instead of truncating or panicking.
+        match self.get_ref(idx)? {
+            ValueRef::Null => Ok(Value::Null),
+            ValueRef::Integer(n) => Ok(Value::Integer(n)),
+            ValueRef::Real(n) => Ok(Value::Real(n)),
+            ValueRef::Text(bytes) => std::str::from_utf8(bytes)
+                .map(|text| Value::Text(text.to_owned()))
+                .map_err(|_| Error::InvalidColumnType),
+            ValueRef::Blob(bytes) => Ok(Value::Blob(bytes.to_vec())),
+        }
     }
 
     pub fn column_type(&self, idx: i32) -> Result<ValueType> {
Lenguaje dominante
C
Estrellas
17.3k
Forks
545
Métricas de merge de PR
Sin PR fusionados en 30 d

Preparar el entorno

Primeros pasos

  1. Lee el issue completo y luego la guía de contribución del proyecto.
  2. Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
  3. Haz un fork del repositorio y trabaja en una rama.
  4. Abre un pull request que haga referencia al número del issue.

Más de tursodatabase/libsql

Todos los issues de tursodatabase/libsql

Issues similares

Más issues de C

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.