sqlite3-ocaml icon indicating copy to clipboard operation
sqlite3-ocaml copied to clipboard

Fix memory alloc in caml_sqlite3_backup_init()

Open mtelvers opened this issue 3 years ago • 5 comments

This PR addresses the segmentation fault exception in Sqlite3.Backup.init at line 1677 Sqlite3_backup_val(v_res) = res;

mtelvers avatar Nov 21 '22 22:11 mtelvers

I hit the same problem! But I worry that your patch is not GC safe and would suggest to instead do

diff --git a/src/sqlite3_stubs.c b/src/sqlite3_stubs.c
index 38092c9..b38dec1 100644
--- a/src/sqlite3_stubs.c
+++ b/src/sqlite3_stubs.c
@@ -180,7 +180,6 @@ static inline void maybe_raise_user_exception(int rc)
 
 #define Sqlite3_val(x) (*((db_wrap **) Data_custom_val(x)))
 #define Sqlite3_stmtw_val(x) (*((stmt_wrap **) Data_custom_val(x)))
-#define Sqlite3_backup_val(x) (*((sqlite3_backup **) Data_custom_val(x)))
 
 
 /* Exceptions */
@@ -1643,6 +1642,7 @@ CAMLprim value caml_sqlite3_changes_bc(value v_db)
 
 
 /* Backup functionality */
+#define Sqlite3_backup_val(x) (*((sqlite3_backup **) Data_abstract_val(x)))
 
 CAMLprim value caml_sqlite3_backup_init(
     value v_dst, value v_dst_name, value v_src, value v_src_name)
@@ -1674,6 +1674,7 @@ CAMLprim value caml_sqlite3_backup_init(
 
   if (NULL == res) raise_sqlite3_current(dst->db, "backup_init");
 
+  v_res = caml_alloc(1,Abstract_tag);
   Sqlite3_backup_val(v_res) = res;
 
   CAMLreturn(v_res);

pirbo avatar Jan 06 '23 13:01 pirbo

Apparently, this is not enough... It still segfault for me but this time the gdb backtrace is more mysterious

#0  0x0000555556cfa610 in ?? ()
#1  0x00007ffff7edd48e in ?? () from /lib/x86_64-linux-gnu/libsqlite3.so.0
#2  0x00007ffff7eea20a in sqlite3_backup_step () from /lib/x86_64-linux-gnu/libsqlite3.so.0
#3  0x00005555564ad8e3 in caml_sqlite3_backup_step (v_backup=<optimized out>, pagecount=5) at sqlite3_stubs.c:1691
#4  0x00005555561c6039 in camlSqlite3__anon_fn_2259 () at vendors/sqlite3.5.1.0/src/sqlite3.ml:433

pirbo avatar Jan 07 '23 09:01 pirbo

For my application, sqlite3-backup, either version works. Is there something in your use case which is different to mine?

mtelvers avatar Jan 09 '23 19:01 mtelvers

@klakplok saved me! The problem is that Ocaml GC mustn't garbage collect the src and dst databases as long as the backup object is alive! (And because I forgot to close the dst database in my code there were no more reference to it after the backup_init and it was GCed...) His suggestion is to do (either on the Ocaml side or the C side)

diff --git a/src/sqlite3.ml b/src/sqlite3.ml
index e835d67..c2781da 100644
--- a/src/sqlite3.ml
+++ b/src/sqlite3.ml
@@ -519,24 +519,42 @@ module Aggregate = struct
 end  (* Aggregate *)
 
 module Backup = struct
-  type t
+  module Raw = struct
+    type t
 
-  external init :
-    dst : db -> dst_name : string ->
-    src : db -> src_name : string -> t = "caml_sqlite3_backup_init"
+    external init :
+      dst : db -> dst_name : string ->
+      src : db -> src_name : string -> t = "caml_sqlite3_backup_init"
 
-  external step : t -> (int [@untagged]) -> Rc.t
-    = "caml_sqlite3_backup_step_bc" "caml_sqlite3_backup_step"
+    external step : t -> (int [@untagged]) -> Rc.t
+      = "caml_sqlite3_backup_step_bc" "caml_sqlite3_backup_step"
 
-  external finish : t -> Rc.t = "caml_sqlite3_backup_finish"
+    external finish : t -> Rc.t = "caml_sqlite3_backup_finish"
 
-  external remaining : t -> (int [@untagged])
-    = "caml_sqlite3_backup_remaining_bc" "caml_sqlite3_backup_remaining"
+    external remaining : t -> (int [@untagged])
+      = "caml_sqlite3_backup_remaining_bc" "caml_sqlite3_backup_remaining"
     [@@noalloc]
 
-  external pagecount : t -> (int [@untagged])
-    = "caml_sqlite3_backup_pagecount_bc" "caml_sqlite3_backup_pagecount"
+    external pagecount : t -> (int [@untagged])
+      = "caml_sqlite3_backup_pagecount_bc" "caml_sqlite3_backup_pagecount"
     [@@noalloc]
+  end
+
+  type t = (Raw.t*db*db)
+  (* the databases mustn't be garbaged collected before the backup
+     object (which have references to them) so we bind their lifetime
+     together. *)
+
+  let init ~dst ~dst_name ~src ~src_name =
+    (Raw.init ~dst ~dst_name ~src ~src_name,dst,src)
+
+  let step (b,_,_) i = Raw.step b i
+
+  let finish (b,_,_) = Raw.finish b
+
+  let remaining (b,_,_) = Raw.remaining b
+
+  let pagecount (b,_,_) = Raw.pagecount b
 end  (* Backup *)
 
 (* Initialisation *)

pirbo avatar Jan 18 '23 14:01 pirbo

Thanks @pirbo and @klakplok.

I have updated the PR with these changes.

mtelvers avatar Jan 27 '23 11:01 mtelvers

Thanks for the contribution!

mmottl avatar Aug 01 '24 00:08 mmottl