Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package ocaml for openSUSE:Factory checked in at 2026-11-01 22:21:16 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/ocaml (Old) and /work/SRC/openSUSE:Factory/.ocaml.new.1255 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "ocaml" Sun Nov 1 22:21:16 2026 rev:98 rq:1393968 version:5.5.2 Changes: -------- --- /work/SRC/openSUSE:Factory/ocaml/ocaml.changes 2026-09-28 10:37:23.243172534 +0200 +++ /work/SRC/openSUSE:Factory/.ocaml.new.1255/ocaml.changes 2026-11-01 22:21:24.637005957 +0100 @@ -1,0 +2,6 @@ +Fri Oct 23 23:23:23 UTC 2026 - [email protected] + +- update to version 5.5.2 + See included Changes for benefits and breaking changes + +------------------------------------------------------------------- Old: ---- ocaml-5.5.1.tar.xz New: ---- ocaml-5.5.2.tar.xz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ ocaml.spec ++++++ --- /var/tmp/diff_new_pack.nCilQ3/_old 2026-11-01 22:21:25.910058644 +0100 +++ /var/tmp/diff_new_pack.nCilQ3/_new 2026-11-01 22:21:25.912058727 +0100 @@ -45,7 +45,7 @@ %bcond_without suse_ocaml_use_rpm_license_macro Name: ocaml -Version: 5.5.1 +Version: 5.5.2 Release: 0 Summary: OCaml Compiler and Programming Environment %if %{with suse_ocaml_use_rpm_license_macro} ++++++ _service ++++++ --- /var/tmp/diff_new_pack.nCilQ3/_old 2026-11-01 22:21:25.949060258 +0100 +++ /var/tmp/diff_new_pack.nCilQ3/_new 2026-11-01 22:21:25.953060423 +0100 @@ -166,7 +166,7 @@ <param name="exclude">utils/HACKING.adoc</param> <param name="exclude">utils/dune</param> <param name="filename">ocaml</param> - <param name="revision">46e1c324fb9e9f21f52c9cc3399020f7ef8b9b91</param> + <param name="revision">c7b6787eb4c2b4a89e2c190e7ffdfa25082d1a6d</param> <param name="scm">git</param> <param name="submodules">disable</param> <param name="url">https://github.com/ocaml/ocaml.git</param> ++++++ ocaml-5.5.1.tar.xz -> ocaml-5.5.2.tar.xz ++++++ ++++ 2026 lines of diff (skipped) ++++ retrying with extended exclude list diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/ocaml-5.5.1/Changes new/ocaml-5.5.2/Changes --- old/ocaml-5.5.1/Changes 2026-09-04 13:16:34.000000000 +0200 +++ new/ocaml-5.5.2/Changes 2026-10-23 13:52:23.000000000 +0200 @@ -1,3 +1,48 @@ +OCaml 5.5.2 +----------- + +### Bug fixes: + +- #15045: Atomic loads whose results are unused should not be removed + during dead-code elimination. + (Xavier Clerc, review by Steven Dolan and KC Sivaramakrishnan) + +- #15067, #15071: Fix detection of functions available in the future on macOS. + (Antonin Décimo, review by Nicolás Ojeda Bär and Damien Doligez) + +- #15112, OSEC-2026-22: `Unix.map_file`: protect against overflow + when computing the data size. + (Xavier Leroy and Nicolás Ojeda Bär, review by Nicolás Ojeda Bär + and Edwin Török) + +- #15113, OSEC-2026-21: `Str.string_partial_match`: make sure captured groups + are valid. + (Xavier Leroy, review by Nicolás Ojeda Bär and Edwin Török) + +- #15115, OSEC-2026-23: Fix multiple issues in demarshaling + (`Marshal.from_{channel,string,bytes}`) + (Xavier Leroy, review by Nicolás Ojeda Bär) + +- #15133, OSEC 2026-24: Multiple issues in command quoting on Windows + (Xavier Leroy and Nicolás Ojeda Bär, review by Nicolás Ojeda Bär) + +- #15119, OSEC-2026-25: Harden decoding of runtime events messages from + ring buffers. + (Sadiq Jaffer, report by Hannes Mehnert, review by Olivier Nicole and + Edwin Török) + +- #15177, OSEC-2026-26: Maintain custom events cache integrity. + (Sadiq Jaffer, report by Hannes Mehnert, review by Edwin Török) + +- #15043, #15076: runtime events library, fix an infinite loop when a user + event is larger than half the ring buffer. + (Tim McGilchrist, review by Sadiq Jaffer) + +- #15185, #15187: fix a pattern-matching miscompilation on certain + combinations of immediate ranges and redundant patterns + (Gabriel Scherer, report by Alperer Keleş, + review by Vincent Laviron and Florian Angeletti) + OCaml 5.5.1 (4 September 2026) ------------------------------ @@ -61,8 +106,8 @@ artefacts) as well as the debug info packed into str.a and unix.a. (Bernhard M. Wiedemann, review by David Allsopp and Stefan Muenzel) -- #15019: `Marshal.from_{string,bytes}`: guard against overflow in - the computation of the total data length. +- #15019, #15030, OSEC-2026-18: `Marshal.from_{string,bytes}`: guard against + overflow in the computation of the total data length. (Xavier Leroy, report by Akshay Singh, review by Nicolás Ojeda Bär and Antonin Décimo) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/ocaml-5.5.1/VERSION new/ocaml-5.5.2/VERSION --- old/ocaml-5.5.1/VERSION 2026-09-04 13:16:34.000000000 +0200 +++ new/ocaml-5.5.2/VERSION 2026-10-23 13:52:23.000000000 +0200 @@ -1,4 +1,4 @@ -5.5.1 +5.5.2 # Starting with OCaml 4.14, although the version string that appears above is # still correct and this file can thus still be used to figure it out, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/ocaml-5.5.1/asmcomp/mach.ml new/ocaml-5.5.2/asmcomp/mach.ml --- old/ocaml-5.5.1/asmcomp/mach.ml 2026-09-04 13:16:34.000000000 +0200 +++ new/ocaml-5.5.2/asmcomp/mach.ml 2026-10-23 13:52:23.000000000 +0200 @@ -155,7 +155,8 @@ | Icall_ind | Icall_imm _ | Itailcall_ind | Itailcall_imm _ | Iextcall _ | Istackoffset _ | Istore _ | Ialloc _ | Ipoll _ | Idls_get - | Iintop(Icheckbound) | Iintop_imm(Icheckbound, _) | Iopaque -> false + | Iintop(Icheckbound) | Iintop_imm(Icheckbound, _) | Iopaque + | Iload { is_atomic = true; _ } -> false | Ispecific sop -> Arch.operation_is_pure sop | _ -> true Binary files old/ocaml-5.5.1/boot/ocamlc and new/ocaml-5.5.2/boot/ocamlc differ Binary files old/ocaml-5.5.1/boot/ocamllex and new/ocaml-5.5.2/boot/ocamllex differ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/ocaml-5.5.1/build-aux/ocaml_version.m4 new/ocaml-5.5.2/build-aux/ocaml_version.m4 --- old/ocaml-5.5.1/build-aux/ocaml_version.m4 2026-09-04 13:16:34.000000000 +0200 +++ new/ocaml-5.5.2/build-aux/ocaml_version.m4 2026-10-23 13:52:23.000000000 +0200 @@ -35,7 +35,7 @@ m4_define([OCAML__VERSION_MAJOR], [5]) m4_define([OCAML__VERSION_MINOR], [5]) m4_define([OCAML__RELEASE_NUMBER], [21]) -m4_define([OCAML__VERSION_PATCHLEVEL], [1]) +m4_define([OCAML__VERSION_PATCHLEVEL], [2]) # Note that the OCAML__VERSION_EXTRA string defined below is always empty # for officially-released versions of OCaml. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/ocaml-5.5.1/configure.ac new/ocaml-5.5.2/configure.ac --- old/ocaml-5.5.1/configure.ac 2026-09-04 13:16:34.000000000 +0200 +++ new/ocaml-5.5.2/configure.ac 2026-10-23 13:52:23.000000000 +0200 @@ -1151,6 +1151,12 @@ internal_cflags="$cc_warnings"], [common_cflags="-O"]) +AS_CASE([$target], + [*-apple-darwin*], + [AX_CHECK_COMPILE_FLAG([-Werror=unguarded-availability-new], + [common_cflags="-Werror=unguarded-availability-new $common_cflags"], [], + [$warn_error_flag])]) + # Enable SSE2 on x86 mingw to avoid using 80-bit registers. # Enable 64-bit time_t in time.h AS_CASE([$target], @@ -2551,11 +2557,17 @@ ## dup3 -AC_CHECK_FUNC([dup3], [AC_DEFINE([HAS_DUP3], [1])]) +OCAML_CHECK_DECL_MACOS([dup3], + [AC_CHECK_FUNC([dup3], [AC_DEFINE([HAS_DUP3], [1])])], [], + [#define _GNU_SOURCE + AC_INCLUDES_DEFAULT]) ## pipe2 -AC_CHECK_FUNC([pipe2], [AC_DEFINE([HAS_PIPE2], [1])]) +OCAML_CHECK_DECL_MACOS([pipe2], + [AC_CHECK_FUNC([pipe2], [AC_DEFINE([HAS_PIPE2], [1])])], [], + [#define _GNU_SOURCE + AC_INCLUDES_DEFAULT]) ## accept4 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/ocaml-5.5.1/lambda/matching.ml new/ocaml-5.5.2/lambda/matching.ml --- old/ocaml-5.5.1/lambda/matching.ml 2026-09-04 13:16:34.000000000 +0200 +++ new/ocaml-5.5.2/lambda/matching.ml 2026-10-23 13:52:23.000000000 +0200 @@ -2795,6 +2795,23 @@ | [] -> (low, high) | (x, _) :: _ -> (x, last high l) +(** [as_interval_canfail fail ~low ~high] takes a sorted list of + (integer key, action) pairs, for example + 0 -> act0 + 1 -> act1 + 2 -> act1 + 4 -> act1 + 5 -> act1 + and returns a sorted list of contiguous intervals partitioning + [low; high], for example + [low; -1] -> fail + [0] -> act0 + [1; 2] -> act1 + [3] -> fail + [4; 5] -> act1 + [6; high] -> fail + with missing keys mapped to the [fail] action. +*) let as_interval_canfail fail ~low ~high l = let store = StoreExp.mk_store () in let do_store _tag act = @@ -2804,7 +2821,22 @@ *) i in - let rec nofail_rec cur_low cur_high cur_act = function + (* cover [low; high] with the provided mapping *) + let rec init_rec = function + | [] -> [ (low, high, 0) ] + | (i, act_i) :: rem -> + let index = do_store "INIT" act_i in + if index = 0 then + fail_rec low rem + else if low < i then + (low, i - 1, 0) :: nofail_rec i i index rem + else + nofail_rec i i index rem + + (* cover [cur_low; high] with the provided mapping, assuming that: + - the keys are all within [cur_low; high] + - [cur_low; cur_high] goes to [cur_act] *) + and nofail_rec cur_low cur_high cur_act = function | [] -> if cur_high = high then [ (cur_low, cur_high, cur_act) ] @@ -2816,42 +2848,49 @@ if act_index = cur_act then nofail_rec cur_low i cur_act rem else if act_index = 0 then - (cur_low, i - 1, cur_act) :: fail_rec i i rem + (cur_low, i - 1, cur_act) :: fail_rec i rem else (cur_low, i - 1, cur_act) :: nofail_rec i i act_index rem else if act_index = 0 then (cur_low, cur_high, cur_act) - :: fail_rec (cur_high + 1) (cur_high + 1) all + :: fail_rec (cur_high + 1) all else (cur_low, cur_high, cur_act) :: (cur_high + 1, i - 1, 0) :: nofail_rec i i act_index rem - and fail_rec cur_low cur_high = function - | [] -> [ (cur_low, cur_high, 0) ] + + (* cover [cur_low; high] with the provided mapping, assuming that: + - the keys are all within [cur_low; high] + - [cur_low] goes to [fail] *) + and fail_rec cur_low = function + | [] -> [ (cur_low, high, 0) ] | (i, act_i) :: rem -> let index = do_store "YES" act_i in if index = 0 then - fail_rec cur_low i rem + fail_rec cur_low rem else (cur_low, i - 1, 0) :: nofail_rec i i index rem in - let init_rec = function - | [] -> [ (low, high, 0) ] - | (i, act_i) :: rem -> - let index = do_store "INIT" act_i in - if index = 0 then - fail_rec low i rem - else if low < i then - (low, i - 1, 0) :: nofail_rec i i index rem - else - nofail_rec i i index rem - in - assert (do_store "FAIL" fail = 0); - - (* fail has action index 0 *) + let fail_index = do_store "FAIL" fail in + assert (fail_index = 0); let r = init_rec l in (Array.of_list r, store) +(** [as_interval_nofail] takes a sorted list of + (integer key, action) pairs, for example: + -1 -> act0 + 1 -> act0 + 2 -> act1 + 4 -> act1 + 5 -> act1 + and returns a sorted list of contiguous intervals, for example + [-1; 1] -> act0 + [2] -> act1 + [3] -> act0 + [4; 5] -> act1 + with the first action (here [act0]) used for missing keys in the + convex hull of the input keys. +*) let as_interval_nofail l = let store = StoreExp.mk_store () in let rec some_hole = function diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/ocaml-5.5.1/ocaml-variants.opam new/ocaml-5.5.2/ocaml-variants.opam --- old/ocaml-5.5.1/ocaml-variants.opam 2026-09-04 13:16:34.000000000 +0200 +++ new/ocaml-5.5.2/ocaml-variants.opam 2026-10-23 13:52:23.000000000 +0200 @@ -1,7 +1,7 @@ opam-version: "2.0" -version: "5.5.1" +version: "5.5.2+trunk" license: "LGPL-2.1-or-later WITH OCaml-LGPL-linking-exception" -synopsis: "Official release of OCaml 5.5.1" +synopsis: "Official OCaml 5.5.2 release" maintainer: "[email protected]" authors: [ "Xavier Leroy" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/ocaml-5.5.1/otherlibs/runtime_events/runtime_events_consumer.c new/ocaml-5.5.2/otherlibs/runtime_events/runtime_events_consumer.c --- old/ocaml-5.5.1/otherlibs/runtime_events/runtime_events_consumer.c 2026-09-04 13:16:34.000000000 +0200 +++ new/ocaml-5.5.2/otherlibs/runtime_events/runtime_events_consumer.c 2026-10-23 13:52:23.000000000 +0200 @@ -534,9 +534,15 @@ continue; } + /* Every message carries a header and a timestamp, except runtime + padding (EV_INTERNAL) which is header only. EV_INTERNAL and + EV_USER_MSG_TYPE_UNIT share the same type value, so we need to + make sure it's EV_INTERNAL and a runtime (rather than user) + message. */ if (!msg_length || (msg_length < 2 - && RUNTIME_EVENTS_ITEM_TYPE(header) != EV_INTERNAL)) { + && !(RUNTIME_EVENTS_ITEM_IS_RUNTIME(header) + && RUNTIME_EVENTS_ITEM_TYPE(header) == EV_INTERNAL))) { atomic_store(&cursor->cursor_in_poll, 0); return E_CORRUPT_STREAM; } @@ -577,7 +583,8 @@ break; case EV_ALLOC: if (cursor->alloc) { - if (msg_length < 3) { + /* the alloc callback reads every bucket */ + if (msg_length < 2 + RUNTIME_EVENTS_NUM_ALLOC_BUCKETS) { atomic_store(&cursor->cursor_in_poll, 0); return E_CORRUPT_STREAM; } @@ -660,7 +667,11 @@ break; default: // custom if (cursor->user_custom) { - /* msg_length could be genuinely 2 here */ + /* we have to have a message length of at least 3 */ + if (msg_length < 3) { + atomic_store(&cursor->cursor_in_poll, 0); + return E_CORRUPT_STREAM; + } if( !cursor->user_custom(domain_num, callback_data, buf[1], event_id, event_name, msg_length - 2, &buf[2]) ) { @@ -717,6 +728,11 @@ value* callbacks_val; value* exception; value* wrapper; + /* set by a callback that detects a malformed event, since callbacks can + only stop the read loop and cannot return E_CORRUPT_STREAM themselves. + The exception field in this struct is only for carrying exceptions + raised by callbacks we call. */ + bool corrupt_stream; }; static int ml_runtime_begin(int domain_id, void *callback_data, @@ -950,12 +966,17 @@ CAMLreturnT(int, 1); } +/* Returns Val_none if the event is unknown and of type + EV_USER_ML_TYPE_CUSTOM (see caml_runtime_events_user_resolve), or if the + resolved event's type does not match event_type, in which case + *corrupt_stream is also set. */ static value caml_runtime_events_user_resolve_cached( value wrapper_root, uintnat event_id, char* event_name, - ev_user_ml_type event_type) + ev_user_ml_type event_type, bool* corrupt_stream) { CAMLparam1(wrapper_root); - CAMLlocal3(event, cache_resized, cache); + CAMLlocal4(event, cache_resized, cache, resolved_type); + bool cache_hit = false; cache = Field(wrapper_root, 2); @@ -977,15 +998,36 @@ uintnat len = Wosize_val(cache); - if (event_id < len) { - if (Is_block(Field(cache, event_id))) { - // cache hit ! - CAMLreturn (Field(cache, event_id)); + if (event_id < len && Is_block(Field(cache, event_id))) { + // cache hit ! + event = Field(cache, event_id); + cache_hit = true; + } else { + // we never encountered this ID + event = caml_runtime_events_user_resolve(event_name, event_type); + } + + // The event is resolved by name, or by a previously cached ID, independently + // of the message type read from the ring, so a malformed stream can pair a + // message of one type with an event declared with another. In that case + // the stream is corrupt. see the comment in + // user_events_find_callback_list_for_event_type: the resolved type is a + // block iff it is a custom event. + if (Is_block(event)) { + resolved_type = Field(event, 2); + bool type_matches = event_type == EV_USER_ML_TYPE_CUSTOM + ? Is_block(resolved_type) + : resolved_type == Val_int(event_type); + + if (!type_matches) { + *corrupt_stream = true; + CAMLreturn(Val_none); } } - // we never encountered this ID - event = caml_runtime_events_user_resolve(event_name, event_type); + if (cache_hit) { + CAMLreturn(event); + } if (event_id >= len) { // we grow the cache to fit the event @@ -1028,7 +1070,13 @@ event = caml_runtime_events_user_resolve_cached(wrapper_root, event_id, event_name, - EV_USER_ML_TYPE_UNIT); + EV_USER_ML_TYPE_UNIT, + &holder->corrupt_stream); + + // Val_none is only returned for a corrupt stream here + if (event == Val_none) { + CAMLreturnT(int, 0); + } callback_list = user_events_find_callback_list_for_event_type(callbacks_root, event); @@ -1066,7 +1114,13 @@ event = caml_runtime_events_user_resolve_cached(wrapper_root, event_id, event_name, - EV_USER_ML_TYPE_SPAN); + EV_USER_ML_TYPE_SPAN, + &holder->corrupt_stream); + + // Val_none is only returned for a corrupt stream here + if (event == Val_none) { + CAMLreturnT(int, 0); + } callback_list = user_events_find_callback_list_for_event_type(callbacks_root, event); @@ -1103,7 +1157,13 @@ event = caml_runtime_events_user_resolve_cached(wrapper_root, event_id, event_name, - EV_USER_ML_TYPE_INT); + EV_USER_ML_TYPE_INT, + &holder->corrupt_stream); + + // Val_none is only returned for a corrupt stream here + if (event == Val_none) { + CAMLreturnT(int, 0); + } callback_list = user_events_find_callback_list_for_event_type(callbacks_root, event); @@ -1142,13 +1202,15 @@ event = caml_runtime_events_user_resolve_cached(wrapper_root, event_id, event_name, - EV_USER_ML_TYPE_CUSTOM); + EV_USER_ML_TYPE_CUSTOM, + &holder->corrupt_stream); // the function may return Val_none if the event is unknown - // (see caml_runtime_events_user_resolve) + // (see caml_runtime_events_user_resolve), in which case we skip it, or if + // the stream is corrupt, in which case we stop if (event == Val_none) { CAMLdrop; - return 1; + return !holder->corrupt_stream; } event_type = Field(event, 2); @@ -1161,12 +1223,24 @@ // deserialize the value and prepare the callback payload const char* data_str = (const char*) event_data; + + // caml_runtime_events_read_poll has already checked this + CAMLassert(event_data_len >= 1); + uintnat string_len = event_data_len * sizeof(uint64_t) - 1; // because the ring buffer is 64-bits aligned, the whole ocaml value is // transferred, including the padding bytes and the last byte containing // the number of padding bytes. This information is crucial to determine // the true size of the string. - uintnat caml_string_len = string_len - data_str[string_len]; + uintnat caml_string_len = + string_len - (unsigned char)data_str[string_len]; + + // if the string length is over the max msg length, the stream is + // corrupt. + if (caml_string_len > RUNTIME_EVENTS_MAX_MSG_LENGTH) { + holder->corrupt_stream = 1; + CAMLreturnT(int, 0); + } record = Field(event_type, 0); deserializer = Field(record, 1); @@ -1299,7 +1373,7 @@ runtime_events_error res; struct callbacks_exception_holder holder = { - &callbacks_val, &exception, &wrapper }; + &callbacks_val, &exception, &wrapper, 0 }; exception = Val_unit; if (cursor == NULL) { @@ -1318,6 +1392,11 @@ caml_raise(exception); } + /* Check if a callback early exited because of a malformed event */ + if (holder.corrupt_stream) { + res = E_CORRUPT_STREAM; + } + if (res != E_SUCCESS) { switch (res) { case E_CURSOR_POLL_BUSY: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/ocaml-5.5.1/otherlibs/str/strstubs.c new/ocaml-5.5.2/otherlibs/str/strstubs.c --- old/ocaml-5.5.1/otherlibs/str/strstubs.c 2026-09-04 13:16:34.000000000 +0200 +++ new/ocaml-5.5.2/otherlibs/str/strstubs.c 2026-10-23 13:52:23.000000000 +0200 @@ -134,7 +134,8 @@ res = caml_alloc(n * 2, 0); for (int i = 0; i < n; i++) { group = &(groups[i]); - if (group->start == NULL || group->end == NULL) { + if (group->start == NULL || group->end == NULL + || group->start > group->end) { Field(res, i * 2) = Val_int(-1); Field(res, i * 2 + 1) = Val_int(-1); } else { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/ocaml-5.5.1/otherlibs/unix/mmap_unix.c new/ocaml-5.5.2/otherlibs/unix/mmap_unix.c --- old/ocaml-5.5.1/otherlibs/unix/mmap_unix.c 2026-09-04 13:16:34.000000000 +0200 +++ new/ocaml-5.5.2/otherlibs/unix/mmap_unix.c 2026-10-23 13:52:23.000000000 +0200 @@ -130,7 +130,12 @@ dimension if that dimension wasn't specified) */ array_size = caml_ba_element_size[flags & CAML_BA_KIND_MASK]; for (intnat i = 0; i < num_dims; i++) - if (dim[i] != -1) array_size *= dim[i]; + if (dim[i] != -1) { + if (caml_umul_overflow(array_size, dim[i], &array_size)) { + caml_leave_blocking_section(); + caml_failwith("Unix.map_file: array too big to fit in memory"); + } + } /* Check if the major dimension is unknown */ if (dim[major_dim] == -1) { /* Determine major dimension from file size */ @@ -138,17 +143,27 @@ caml_leave_blocking_section(); caml_failwith("Unix.map_file: file position exceeds file size"); } - data_size = file_size - startpos; - dim[major_dim] = (uintnat) (data_size / array_size); - array_size = dim[major_dim] * array_size; - if (array_size != data_size) { - caml_leave_blocking_section(); - caml_failwith("Unix.map_file: file size doesn't match array dimensions"); + if (array_size == 0) { + dim[major_dim] = 0; + } else { + data_size = file_size - startpos; + dim[major_dim] = (uintnat) (data_size / array_size); + if (caml_umul_overflow(array_size, dim[major_dim], &array_size) + || array_size != data_size) { + caml_leave_blocking_section(); + caml_failwith("Unix.map_file: file size doesn't match " + "array dimensions"); + } } } else { /* Check that file is large enough, and grow it otherwise */ - if (file_size < startpos + array_size) { - if (caml_grow_file(fd, startpos + array_size) == -1) { /* PR#5543 */ + file_offset min_file_size = startpos + array_size; + if (min_file_size < 0 || min_file_size < startpos) { + caml_leave_blocking_section(); + caml_failwith("Unix.map_file: array too big to fit in the file"); + } + if (file_size < min_file_size) { + if (caml_grow_file(fd, min_file_size) == -1) { /* PR#5543 */ caml_leave_blocking_section(); caml_uerror("map_file", Nothing); } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/ocaml-5.5.1/otherlibs/unix/unix.mli new/ocaml-5.5.2/otherlibs/unix/unix.mli --- old/ocaml-5.5.1/otherlibs/unix/unix.mli 2026-09-04 13:16:34.000000000 +0200 +++ new/ocaml-5.5.2/otherlibs/unix/unix.mli 2026-10-23 13:52:23.000000000 +0200 @@ -896,7 +896,14 @@ the redirection and causes the new process to have the same standard output as the current process. The executable file [prog] is searched in the path. The new process has the same environment as the current - process. *) + process. + + @raise Failure on Windows if the program [prog] is a [.bat] or [.cmd] + script and either [prog] or [args] contain characters that cannot be + securely passed to a script, such as [" | & , ; < = > ^ % "]. + If you really need to pass these characters to a script, consider + using {!Unix.system} instead. + *) val create_process_env : string -> string array -> string array -> file_descr -> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/ocaml-5.5.1/otherlibs/unix/unixLabels.mli new/ocaml-5.5.2/otherlibs/unix/unixLabels.mli --- old/ocaml-5.5.1/otherlibs/unix/unixLabels.mli 2026-09-04 13:16:34.000000000 +0200 +++ new/ocaml-5.5.2/otherlibs/unix/unixLabels.mli 2026-10-23 13:52:23.000000000 +0200 @@ -896,7 +896,14 @@ the redirection and causes the new process to have the same standard output as the current process. The executable file [prog] is searched in the path. The new process has the same environment as the current - process. *) + process. + + @raise Failure on Windows if the program [prog] is a [.bat] or [.cmd] + script and either [prog] or [args] contain characters that cannot be + securely passed to a script, such as [" | & , ; < = > ^ % "]. + If you really need to pass these characters to a script, consider + using {!Unix.system} instead. + *) val create_process_env : prog:string -> args:string array -> env:string array -> stdin:file_descr -> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/ocaml-5.5.1/otherlibs/unix/unix_unix.ml new/ocaml-5.5.2/otherlibs/unix/unix_unix.ml --- old/ocaml-5.5.1/otherlibs/unix/unix_unix.ml 2026-09-04 13:16:34.000000000 +0200 +++ new/ocaml-5.5.2/otherlibs/unix/unix_unix.ml 2026-10-23 13:52:23.000000000 +0200 @@ -400,6 +400,7 @@ = "caml_unix_map_file_bytecode" "caml_unix_map_file" let map_file fd ?(pos=0L) kind layout shared dims = + if pos < 0L then invalid_arg "Unix.map_file: negative position"; map_internal fd kind layout shared dims pos (* File permissions and ownership *) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/ocaml-5.5.1/otherlibs/unix/unix_win32.ml new/ocaml-5.5.2/otherlibs/unix/unix_win32.ml --- old/ocaml-5.5.1/otherlibs/unix/unix_win32.ml 2026-09-04 13:16:34.000000000 +0200 +++ new/ocaml-5.5.2/otherlibs/unix/unix_win32.ml 2026-10-23 13:52:23.000000000 +0200 @@ -436,6 +436,7 @@ = "caml_unix_map_file_bytecode" "caml_unix_map_file" let map_file fd ?(pos=0L) kind layout shared dims = + if pos < 0L then invalid_arg "Unix.map_file: negative position"; map_internal fd kind layout shared dims pos (* File permissions and ownership *) @@ -995,8 +996,52 @@ file_descr -> file_descr -> file_descr -> int = "caml_unix_create_process" "caml_unix_create_process_native" -let make_cmdline args = - String.concat " " (List.map maybe_quote (Array.to_list args)) +(* Some arguments cannot be safely passed to .bat/.cmd scripts. + Those that can be safely passed use different quoting conventions + than those of [maybe_quote], which are for passing arguments to + .exe binaries. *) + +let check_safe_for_script s = + if String.exists + (function '\n' | '\r' | '"' | '%' | '&' | ',' + | ';' | '<' | '=' | '>' | '^' | '|' + -> true + | _ -> false) + s + then failwith ("Unix.create_process: unquotable argument to script: " ^ s) + +let double_quote s = + "\"" ^ s ^ "\"" + +let maybe_quote_for_script s = + if s = "" || String.exists (function ' ' | '\t' -> true | _ -> false) s + then double_quote s + else s + +(* Windows ignores trailing spaces and dots in filenames. + This complicates the detection of the file extension. *) + +let filename_extension s = + let n = ref (String.length s) in + while !n > 0 && (s.[!n-1] = '.' || s.[!n-1] = ' ') do decr n done; + Filename.extension (String.sub s 0 !n) + +let make_cmdline prog args = + let ext = String.lowercase_ascii (filename_extension prog) in + if ext = ".bat" || ext = ".cmd" then begin + (* args.(0) is replaced by the script name. *) + (* Forward slashes in the script name can be confused for cmd.exe flags. *) + let arg0 = String.map (function '/' -> '\\' | c -> c) prog + and arg1n = match Array.to_list args with [] -> [] | _ :: t -> t in + check_safe_for_script arg0; + List.iter check_safe_for_script arg1n; + (* Always quote the script name to avoid possible mis-parsing of + "prog.bat arg1.bat" as a script called with zero arguments. *) + double_quote + (String.concat " " + (double_quote arg0 :: List.map maybe_quote_for_script arg1n)) + end else + String.concat " " (List.map maybe_quote (Array.to_list args)) let make_process_env env = Array.iter @@ -1005,10 +1050,10 @@ String.concat "\000" (Array.to_list env) ^ "\000" let create_process prog args fd1 fd2 fd3 = - create_process_stub prog (make_cmdline args) None fd1 fd2 fd3 + create_process_stub prog (make_cmdline prog args) None fd1 fd2 fd3 let create_process_env prog args env fd1 fd2 fd3 = - create_process_stub prog (make_cmdline args) + create_process_stub prog (make_cmdline prog args) (Some(make_process_env env)) fd1 fd2 fd3 @@ -1105,13 +1150,13 @@ (inchan, outchan, errchan) let open_process_args_in prog args = - open_process_cmdline_in prog (make_cmdline args) + open_process_cmdline_in prog (make_cmdline prog args) let open_process_args_out prog args = - open_process_cmdline_out prog (make_cmdline args) + open_process_cmdline_out prog (make_cmdline prog args) let open_process_args prog args = - open_process_cmdline prog (make_cmdline args) + open_process_cmdline prog (make_cmdline prog args) let open_process_args_full prog args = - open_process_cmdline_full prog (make_cmdline args) + open_process_cmdline_full prog (make_cmdline prog args) let open_process_shell fn cmd = let shell = diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/ocaml-5.5.1/runtime/intern.c new/ocaml-5.5.2/runtime/intern.c --- old/ocaml-5.5.1/runtime/intern.c 2026-09-04 13:16:34.000000000 +0200 +++ new/ocaml-5.5.2/runtime/intern.c 2026-10-23 13:52:23.000000000 +0200 @@ -21,6 +21,7 @@ #include <string.h> #include <stdio.h> +#include <stdbool.h> #include "caml/alloc.h" #include "caml/callback.h" #include "caml/codefrag.h" @@ -42,10 +43,12 @@ struct intern_item { volatile value * dest; intnat arg; - enum { + enum intern_operation { OReadItems, /* read arg items and store them in dest[0], dest[1], ... */ + OReadItemsOrCodePointers, /* likewise, but code pointers are allowed */ OFreshOID, /* generate a fresh OID and store it in *dest */ - OShift /* offset *dest by arg */ + OShift, /* offset *dest by arg */ + OCheckClosure /* check that dest is a well-formed closure */ } op; }; @@ -265,6 +268,50 @@ s->intern_src += len; } +/* Well-formedness checks on a closure */ + +static bool well_formed_closure(value v) +{ + asize_t sz, envofs, i; + value cinfo; +#define CHECK(b) if (! (b)) return false + sz = Wosize_val(v); + CHECK(sz >= 2); + // Check the first closure + cinfo = Closinfo_val(v); + CHECK(Is_long(cinfo)); + envofs = Start_env_closinfo(cinfo); + CHECK(envofs >= 2 && envofs <= sz); + // Check the infix closures that can follow + i = 0; + while (1) { + // Skip to the next closure + int arity = Arity_closinfo(cinfo); + if (arity == 0 || arity == 1) + i += 3; // bytecode closure or native closure with arity 1 + else + i += 4; // native closure with arity != 1: one extra entry points + if (i >= envofs) break; + if (i + 1 >= sz) return false; + // Check infix header and closure info + header_t h = (header_t) Field(v, i - 1); + CHECK(Tag_hd(h) == Infix_tag && Wosize_hd(h) == i); + cinfo = Field(v, i + 1); + CHECK(Is_long(cinfo)); + CHECK(Start_env_closinfo(cinfo) + i == envofs); + } + i -= 1; + CHECK(i == envofs); + // Check that the environment part of the closure contains no code pointers + for (; i < sz; i++) { + value vi = Field(v, i); + if (Is_block(vi)) + CHECK(caml_find_code_fragment_by_pc((char *) vi) == NULL); + } + return true; +#undef CHECK +} + static void intern_init(struct caml_intern_state* s, const void * src, uintnat len, void * input) { @@ -298,6 +345,7 @@ caml_stat_free(s->intern_obj_table); s->intern_obj_table = NULL; s->intern_num_objects = 0; + s->obj_counter = 0; } s->intern_dest = NULL; s->intern_dest_end = NULL; @@ -427,20 +475,20 @@ } /* Convenience macros for requesting operation on the stack */ -#define PushItem(s) \ - do { \ - sp++; \ - if (sp >= s->intern_stack_limit) sp = intern_resize_stack(s, sp); \ + +#define PushOperation(s,sp,_op,_dest,_arg) \ + do { \ + sp++; \ + if (sp >= s->intern_stack_limit) sp = intern_resize_stack(s, sp); \ + sp->op = _op; \ + sp->dest = _dest; \ + sp->arg = _arg; \ } while(0) -#define ReadItems(s,_dest,_n) \ - do { \ - if (_n > 0) { \ - PushItem(s); \ - sp->op = OReadItems; \ - sp->dest = _dest; \ - sp->arg = _n; \ - } \ +#define ReadItems(s,sp,_dest,_n) \ + do { \ + CAMLassert((_n) > 0); \ + PushOperation(s, sp, OReadItems, _dest, _n); \ } while(0) static void intern_alloc_storage(struct caml_intern_state* s, mlsize_t whsize, @@ -449,6 +497,7 @@ mlsize_t wosize; value v; + s->obj_counter = 0; if (whsize == 0) { CAMLassert (s->intern_obj_table == NULL); return; @@ -465,7 +514,6 @@ CAMLassert (s->intern_dest == NULL); } s->intern_num_objects = num_objects; - s->obj_counter = 0; if (num_objects > 0) { s->intern_obj_table = (value *) caml_stat_calloc_noexc(num_objects, sizeof(value)); @@ -476,8 +524,6 @@ } else { CAMLassert(s->intern_obj_table == NULL); } - - return; } static value intern_alloc_obj(struct caml_intern_state* s, caml_domain_state* d, @@ -530,19 +576,21 @@ struct custom_operations * ops; char * codeptr; struct intern_item * sp; + enum intern_operation iop; caml_domain_state * d = Caml_state; sp = s->intern_stack; /* Initially let's try to read the first object from the stream */ - ReadItems(s, dest, 1); + ReadItems(s, sp, dest, 1); /* The un-marshaler loop, the recursion is unrolled */ while(sp != s->intern_stack) { /* Interpret next item on the stack */ + iop = sp->op; dest = sp->dest; - switch (sp->op) { + switch (iop) { case OFreshOID: /* Refresh the object ID */ /* but do not do it for predefined exception slots */ @@ -567,7 +615,16 @@ /* Pop item and iterate */ sp--; break; + case OCheckClosure: + /* Check that the closure is well-formed */ + if (! well_formed_closure((value) dest)) { + intern_cleanup_failwith2(s, fun_name, "ill-formed function closure"); + } + /* Pop item and iterate */ + sp--; + break; case OReadItems: + case OReadItemsOrCodePointers: /* Pop item */ sp->dest++; if (--(sp->arg) == 0) sp--; @@ -579,27 +636,40 @@ tag = code & 0xF; size = (code >> 4) & 0x7; read_block: - if (size == 0) { + if (CAMLunlikely(size == 0)) { + if (CAMLunlikely(tag >= Forcing_tag)) + intern_cleanup_failwith2(s, fun_name, "bad atom tag"); v = Atom(tag); } else { v = intern_alloc_obj (s, d, size, tag); intern_record_obj(s, v); - /* For objects, we need to freshen the oid */ - if (tag == Object_tag) { + switch(tag) { + default: // below Forcing_tag + Forcing_tag, Lazy_tag, Forward_tag + /* Read the contents of the block */ + ReadItems(s, sp, &Field(v, 0), size); + break; + case Closure_tag: + /* For closures, we need to validate the closure after reading it */ + PushOperation(s, sp, OCheckClosure, (value*) v, 0); + /* Code pointers are allowed in a closure block */ + PushOperation(s, sp, OReadItemsOrCodePointers, &Field(v, 0), size); + break; + case Object_tag: + /* For objects, we need to freshen the oid */ if (CAMLunlikely(size < 2)) intern_cleanup_failwith2(s, fun_name, "bad object block"); /* Request to read rest of the elements of the block */ - ReadItems(s, &Field(v, 2), size - 2); + if (size > 2) ReadItems(s, sp, &Field(v, 2), size - 2); /* Request freshing OID */ - PushItem(s); - sp->op = OFreshOID; - sp->dest = (value*) v; - sp->arg = 1; + PushOperation(s, sp, OFreshOID, (value*) v, 0); /* Finally read first two block elements: method table and old OID */ - ReadItems(s, &Field(v, 0), 2); - } else - /* If it's not an object then read the contents of the block */ - ReadItems(s, &Field(v, 0), size); + ReadItems(s, sp, &Field(v, 0), 2); + break; + case Infix_tag: case Cont_tag: case Abstract_tag: case String_tag: + case Double_tag: case Double_array_tag: case Custom_tag: + intern_cleanup_failwith2(s, fun_name, "bad block tag"); + break; + } } } else { /* Small integer */ @@ -716,6 +786,8 @@ goto read_double_array; #endif case CODE_CODEPOINTER: + if (iop != OReadItemsOrCodePointers) + intern_cleanup_failwith2(s, fun_name, "code pointer outside closure"); ofs = read32u(s); readblock(s, digest, 16); codeptr = intern_resolve_code_pointer(digest, ofs); @@ -736,11 +808,8 @@ case CODE_INFIXPOINTER: ofs = read32u(s); /* Read a value to *dest, then offset *dest by ofs */ - PushItem(s); - sp->dest = dest; - sp->op = OShift; - sp->arg = ofs; - ReadItems(s, dest, 1); + PushOperation(s, sp, OShift, dest, ofs); + ReadItems(s, sp, dest, 1); continue; /* with next iteration of main loop, skipping *dest = v */ case OLD_CODE_CUSTOM: intern_cleanup_failwith2(s, fun_name, "custom blocks serialized with " @@ -783,6 +852,9 @@ s->intern_src += 8; } #endif + if (expected_size > Bsize_wsize(Max_wosize - 1)) { + intern_cleanup_failwith2(s, fun_name, "custom block size too large"); + } temp_size = 1 + (expected_size + sizeof(value) - 1) / sizeof(value); v = intern_alloc_obj(s, d, temp_size, Abstract_tag); Custom_ops_val(v) = ops; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/ocaml-5.5.1/runtime/runtime_events.c new/ocaml-5.5.2/runtime/runtime_events.c --- old/ocaml-5.5.1/runtime/runtime_events.c 2026-09-04 13:16:34.000000000 +0200 +++ new/ocaml-5.5.2/runtime/runtime_events.c 2026-10-23 13:52:23.000000000 +0200 @@ -520,6 +520,28 @@ /* account for header and timestamp (which are both uint64) */ uint64_t length_with_header_ts = event_length + 2; + /* We must fatal error if the event cannot fit in the ring buffer. + Otherwise the loop below loops forever. In practice only the + alloc event can hit this, which is emitted by the instrumented + runtime. But users can also create arbitrarily sized events. + Closes #13335. + + An event is only written whole, so one that would straddle the end of + the ring is preceded by padding and written at the start instead. The + loop below therefore has to fit the event plus that padding, and the + padding can be one word short of the event itself. Half the ring is the + largest event that always fits. Closes #15043. */ + if (2 * length_with_header_ts > ring_size_words) { + int min_e = 0; + while ((1ULL << min_e) < 2 * length_with_header_ts) min_e++; + caml_fatal_error( + "runtime_events: event of %" PRIu64 " words does not fit in the ring " + "buffer (%d words, from OCAMLRUNPARAM=e=%" CAML_PRIuNAT "). " + "Use OCAMLRUNPARAM=e=%d or higher.", + length_with_header_ts, ring_size_words, + caml_params->runtime_events_log_wsize, min_e); + } + /* there is a ring buffer (made up of header and data) for each domain */ struct runtime_events_buffer_header *domain_ring_header = get_ring_buffer_by_domain_id(Caml_state->id); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/ocaml-5.5.1/stdlib/filename.ml new/ocaml-5.5.2/stdlib/filename.ml --- old/ocaml-5.5.1/stdlib/filename.ml 2026-09-04 13:16:34.000000000 +0200 +++ new/ocaml-5.5.2/stdlib/filename.ml 2026-10-23 13:52:23.000000000 +0200 @@ -191,7 +191,9 @@ String.iter (fun c -> match c with - | '(' | ')' | '!' | '^' | '%' | '\"' | '<' | '>' | '&' | '|' -> + | '\n' | '\r' -> + failwith ("Filename.quote_command: cannot escape newline in " ^ s) + | '(' | ')' | '!' | '^' | '%' | ':' | '\"' | '<' | '>' | '&' | '|' -> Buffer.add_char b '^'; Buffer.add_char b c | _ -> Buffer.add_char b c) @@ -205,10 +207,13 @@ String.map (function '/' -> '\\' | c -> c) f else f in - if String.exists (function '\"' | '%' -> true | _ -> false) f then + if String.exists (function + | '\n' | '\r' | '\"' | '%' -> true + | _ -> false) f then failwith ("Filename.quote_command: bad file name " ^ f) else if String.exists (function - | ' ' | '(' | ')' | '!' | '^' | '&' -> true + | ' ' | '\t' | '(' | ')' | '^' | '<' | '>' | '&' | '|' | '!' + -> true | _ -> false) f then String.concat "" ["\""; f; "\""] else diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/ocaml-5.5.1/testsuite/tests/basic-more/pr15185.ml new/ocaml-5.5.2/testsuite/tests/basic-more/pr15185.ml --- old/ocaml-5.5.1/testsuite/tests/basic-more/pr15185.ml 1970-01-01 01:00:00.000000000 +0100 +++ new/ocaml-5.5.2/testsuite/tests/basic-more/pr15185.ml 2026-10-23 13:52:23.000000000 +0200 @@ -0,0 +1,22 @@ +(* TEST + bytecode; +*) + +(* This is a regression test for a miscompilation in the + pattern-matching compiler, which would only handle [x] values in + the interval [min_int; 3], despite the match being correctly + detected as partial. +*) +let[@warning "-partial-match"][@warning "-redundant-case"] f x y = match x, y with + | _, true -> 0 + | (0 | 2), _ -> 1 + | 3, true -> 2 + | -1, _ -> 3 + +(* The switcher then generates an exhaustive-switch instruction which + is passed below an input outside its input range, leading to + a segfault. *) +let () = + match f 4 false with + | (n : int) -> () + | exception Match_failure _ -> () diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/ocaml-5.5.1/testsuite/tests/lib-bigarray-file/mapfile.ml new/ocaml-5.5.2/testsuite/tests/lib-bigarray-file/mapfile.ml --- old/ocaml-5.5.1/testsuite/tests/lib-bigarray-file/mapfile.ml 2026-09-04 13:16:34.000000000 +0200 +++ new/ocaml-5.5.2/testsuite/tests/lib-bigarray-file/mapfile.ml 2026-10-23 13:52:23.000000000 +0200 @@ -106,18 +106,43 @@ Printf.eprintf "Unexpected error %s\n%!" (Unix.error_message err); false end; - Unix.close fd; - (* Invalid handle *) + (* Overflow in byte size *) test 2 true begin try + (* 2^64 bytes *) + let d = 0x1_0000 in + ignore (Unix.map_file fd char c_layout false [|d;d;d;d|]); + false + with + | Failure _ -> true + | Unix.Unix_error(err, _, _) -> + Printf.eprintf "Unexpected error %s\n%!" (Unix.error_message err); + false + end; + (* Overflow in file position *) + test 3 true + begin try + (* 2^63 bytes *) + let d = 0x1_0000 and e = 0x8000 in + ignore (Unix.map_file fd ~pos:10L char c_layout false [|e;d;d;d|]); + false + with + | Failure _ -> true + | Unix.Unix_error(err, _, _) -> + Printf.eprintf "Unexpected error %s\n%!" (Unix.error_message err); + false + end; + (* Invalid handle *) + Unix.close fd; + test 99 true + begin try ignore (Unix.map_file fd float64 c_layout true [|-1; 100|]); false with | Unix.Unix_error((Unix.EBADF|Unix.EINVAL), _, _) -> true | Unix.Unix_error(err, _, _) -> Printf.eprintf "Unexpected error %s\n%!" (Unix.error_message err); false - end - + end; end; (* Force garbage collection of the mapped bigarrays above, otherwise Win32 doesn't let us erase the file. Notice the begin...end above diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/ocaml-5.5.1/testsuite/tests/lib-bigarray-file/mapfile.reference new/ocaml-5.5.2/testsuite/tests/lib-bigarray-file/mapfile.reference --- old/ocaml-5.5.1/testsuite/tests/lib-bigarray-file/mapfile.reference 2026-09-04 13:16:34.000000000 +0200 +++ new/ocaml-5.5.2/testsuite/tests/lib-bigarray-file/mapfile.reference 2026-10-23 13:52:23.000000000 +0200 @@ -2,4 +2,4 @@ map_file 1... 2... 3... 4... map_file errors - 1... 2... + 1... 2... 3... 99... diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/ocaml-5.5.1/testsuite/tests/lib-filename/quotecommand.ml new/ocaml-5.5.2/testsuite/tests/lib-filename/quotecommand.ml --- old/ocaml-5.5.1/testsuite/tests/lib-filename/quotecommand.ml 2026-09-04 13:16:34.000000000 +0200 +++ new/ocaml-5.5.2/testsuite/tests/lib-filename/quotecommand.ml 2026-10-23 13:52:23.000000000 +0200 @@ -159,7 +159,11 @@ (fun () -> Filename.quote_command "true" ~stdout:"a%b" []); check_raises {|stdout "|} (fun () -> Filename.quote_command "true" ~stdout:{|a"b|} []); + check_raises "stdout LF" + (fun () -> Filename.quote_command "true" ~stdout:"a\nb" []); check_raises "program %" (fun () -> Filename.quote_command "a%b" []); check_raises {|program "|} - (fun () -> Filename.quote_command {|a"b|} []) + (fun () -> Filename.quote_command {|a"b|} []); + check_raises "program LF" + (fun () -> Filename.quote_command "a\nb" []) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/ocaml-5.5.1/testsuite/tests/lib-filename/quotecommand.reference new/ocaml-5.5.2/testsuite/tests/lib-filename/quotecommand.reference --- old/ocaml-5.5.1/testsuite/tests/lib-filename/quotecommand.reference 2026-09-04 13:16:34.000000000 +0200 +++ new/ocaml-5.5.2/testsuite/tests/lib-filename/quotecommand.reference 2026-10-23 13:52:23.000000000 +0200 @@ -82,5 +82,7 @@ -------- Rejected (unquotable) characters stdout %: OK stdout ": OK +stdout LF: OK program %: OK program ": OK +program LF: OK diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/ocaml-5.5.1/testsuite/tests/lib-marshal/fuzzy.ml new/ocaml-5.5.2/testsuite/tests/lib-marshal/fuzzy.ml --- old/ocaml-5.5.1/testsuite/tests/lib-marshal/fuzzy.ml 2026-09-04 13:16:34.000000000 +0200 +++ new/ocaml-5.5.2/testsuite/tests/lib-marshal/fuzzy.ml 2026-10-23 13:52:23.000000000 +0200 @@ -35,30 +35,22 @@ Obj.repr (-1); Obj.repr 20000; Obj.repr 0x12345678; - Obj.repr bigint; Obj.repr "foo"; Obj.repr longstring; Obj.repr 3.141592654; Obj.repr A; Obj.repr (B 1); - Obj.repr (C 2.718); - Obj.repr (D "hello"); - Obj.repr (E 'l'); - Obj.repr (F(B 1)); Obj.repr (G(A, G(B 2, G(C 3.14, G(D "", E 'e'))))); - Obj.repr (H(1, A)); - Obj.repr (I(B 2, 1e-6)); (let x = D "sharing" in let y = G(x, x) in let z = G(y, G(x, y)) in Obj.repr z); - Obj.repr [|1;2;3;4;5;6;7;8|]; + Obj.repr [||]; + Obj.repr [|1;2;3|]; Obj.repr [|3.14; 2.718|]; Obj.repr (closures()); Obj.repr 0l; - Obj.repr 123456l; Obj.repr 0L; - (let i = Int64.of_string "123456789123456" in Obj.repr (i,i)); Obj.repr (Failure "fail"); Obj.repr Bigarray.(Array1.init int16_unsigned c_layout 5 (fun x -> 8*x)) |] @@ -69,26 +61,29 @@ Out_channel.with_open_bin filename (fun oc -> Marshal.(to_channel oc (data()) [Closures])) +(* Keep [x] alive *) + +let use_ignore x = ignore (Sys.opaque_identity x) [@@inline never] + (* Try to unmarshal possibly malicious data. Clean failure is success. *) let test ic = In_channel.set_binary_mode ic true; - begin try - ignore (Marshal.from_channel ic) - with Failure _ | Invalid_argument _ | Out_of_memory -> () - end; - Gc.full_major() + try + let v : Obj.t = Marshal.from_channel ic in + Gc.full_major(); use_ignore v + with Failure _ | Invalid_argument _ | Out_of_memory -> + Gc.full_major() (* Same, but use Marshal.from_string instead *) let test_string ic = In_channel.set_binary_mode ic true; let s = In_channel.input_all ic in - begin try - ignore (Marshal.from_string s 0) - with Failure _ | Invalid_argument _ | Out_of_memory -> () - end; - Gc.full_major() + try + let v : Obj.t = Marshal.from_string s 0 in + Gc.full_major(); use_ignore v + with Failure _ | Invalid_argument _ | Out_of_memory -> Gc.full_major() (* Internal fuzzing. Rather naive. *) @@ -116,10 +111,11 @@ | _ (*3*) -> flip_one_bit b; flip_one_bit b end; begin try - ignore (Marshal.from_bytes b 0) - with Failure _ | Invalid_argument _ | Out_of_memory -> () - end; - Gc.full_major() + let v : Obj.t = Marshal.from_bytes b 0 in + Gc.full_major(); use_ignore v + with Failure _ | Invalid_argument _ | Out_of_memory -> + Gc.full_major() + end done let fuzz1 () = @@ -129,10 +125,11 @@ for x = 0 to 255 do Bytes.set_uint8 b i x; begin try - ignore (Marshal.from_bytes b 0) - with Failure _ | Invalid_argument _ | Out_of_memory -> () - end; - Gc.full_major() + let v : Obj.t = Marshal.from_bytes b 0 in + Gc.full_major(); use_ignore v + with Failure _ | Invalid_argument _ | Out_of_memory -> + Gc.full_major() + end done; Bytes.set_uint8 b i (String.get_uint8 d i) done diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/ocaml-5.5.1/testsuite/tests/lib-marshal/intext.ml new/ocaml-5.5.2/testsuite/tests/lib-marshal/intext.ml --- old/ocaml-5.5.1/testsuite/tests/lib-marshal/intext.ml 2026-09-04 13:16:34.000000000 +0200 +++ new/ocaml-5.5.2/testsuite/tests/lib-marshal/intext.ml 2026-10-23 13:52:23.000000000 +0200 @@ -26,6 +26,10 @@ let rec fib n = if n < 2 then 1 else fib(n-1) + fib(n-2) +let lazy_u = lazy (fib 20) +let lazy_e1 = Lazy.force (lazy (fib 10)) +let lazy_e2 = Lazy.force (lazy 3.14) + let test_out ?(flags = []) filename = let oc = open_out_bin filename in Marshal.to_channel oc 1 flags; @@ -72,6 +76,10 @@ (Nativeint.shift_left (Nativeint.of_string "-123456789") 32) flags; let i = Int64.of_string "123456789123456" in Marshal.to_channel oc (i,i) flags; + Marshal.to_channel oc lazy_u (Marshal.Closures :: flags); + Marshal.to_channel oc lazy_e1 flags; + Marshal.to_channel oc lazy_e2 flags; + Marshal.to_channel oc [||] flags; close_out oc @@ -155,6 +163,10 @@ test 37 (i = Int64.of_string "123456789123456"); test 38 (j = Int64.of_string "123456789123456"); test 39 (i == j); + test 40 (Lazy.force (input_value ic) = fib 20); + test 41 (Lazy.force (input_value ic) = fib 10); + test 42 (Lazy.force (input_value ic) = 3.14); + test 43 (Array.length (input_value ic) = 0); close_in ic let test_string () = @@ -548,6 +560,28 @@ test 606 (even' 142 = true); test 607 (even' 142 = even 142) +(* Test for infix pointers, with functions of arity >= 2 *) +let test_infix2 () = + let t = true and + f = false in + let rec odd n z = + if n = z + then f + else even (n-1) z + and even n z = + if n = z + then t + else odd (n-1) z + in + let s = Marshal.to_string (odd, even) [Marshal.Closures] in + let (odd', even': (int -> int -> bool) * (int -> int -> bool)) + = Marshal.from_string s 0 in + test 650 (odd' 41 0 = odd 41 0); + test 651 (odd' 142 0 = odd 142 0); + test 652 (odd' 142 3 = odd 142 3); + test 653 (even' 41 0 = even 41 0); + test 654 (even' 142 0 = even 142 0); + test 655 (even' 142 3 = even 142 3) let test_mutual_rec_regression () = (* this regression was reported by Cedric Pasteur in PR#5772 *) @@ -629,6 +663,7 @@ test_deep(); test_objects(); test_infix (); + test_infix2 (); test_mutual_rec_regression (); test_end_of_file_regression (); test_buggy_serialisers (); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/ocaml-5.5.1/testsuite/tests/lib-marshal/intext.reference new/ocaml-5.5.2/testsuite/tests/lib-marshal/intext.reference --- old/ocaml-5.5.1/testsuite/tests/lib-marshal/intext.reference 2026-09-04 13:16:34.000000000 +0200 +++ new/ocaml-5.5.2/testsuite/tests/lib-marshal/intext.reference 2026-10-23 13:52:23.000000000 +0200 @@ -38,6 +38,10 @@ Test 37 passed. Test 38 passed. Test 39 passed. +Test 40 passed. +Test 41 passed. +Test 42 passed. +Test 43 passed. Default flags (again) Test 1 passed. Test 2 passed. @@ -78,6 +82,10 @@ Test 37 passed. Test 38 passed. Test 39 passed. +Test 40 passed. +Test 41 passed. +Test 42 passed. +Test 43 passed. Marshal.to_string Test 101 passed. Test 102 passed. @@ -180,5 +188,11 @@ Test 605 passed. Test 606 passed. Test 607 passed. +Test 650 passed. +Test 651 passed. +Test 652 passed. +Test 653 passed. +Test 654 passed. +Test 655 passed. Test 700 passed. Test 800 passed. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/ocaml-5.5.1/testsuite/tests/lib-runtime-events/test_big_event.ml new/ocaml-5.5.2/testsuite/tests/lib-runtime-events/test_big_event.ml --- old/ocaml-5.5.1/testsuite/tests/lib-runtime-events/test_big_event.ml 1970-01-01 01:00:00.000000000 +0100 +++ new/ocaml-5.5.2/testsuite/tests/lib-runtime-events/test_big_event.ml 2026-10-23 13:52:23.000000000 +0200 @@ -0,0 +1,36 @@ +(* TEST + include runtime_events; + not-target-windows; + ocamlrunparam += ",e=8"; + exit_status = "-6"; + { + bytecode; + }{ + native; + } +*) + +(* An event is only ever written whole, so one that would straddle the end of + the ring is preceded by padding and written at the start instead. Making + room therefore has to account for the event plus that padding, which can be + one word short of the event itself, so anything larger than half the ring + can fail to fit however far the head is advanced. Before this was rejected + the loop in write_to_ring spun forever. + + e=8 is a 256 word ring and the payload below is 128 words, which with the + header and timestamp is 131, comfortably over half. *) + +type Runtime_events.User.tag += Big + +let big_encoding = + let encode buf () = Bytes.fill buf 0 1024 'x'; 1024 in + let decode _ _ = () in + Runtime_events.Type.register ~encode ~decode + +let big = Runtime_events.User.register "big" Big big_encoding + +let () = + Runtime_events.start (); + Runtime_events.User.write big (); + (* Not reached: the write above must fatal error rather than hang. *) + print_string "wrote an event larger than half the ring\n" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/ocaml-5.5.1/testsuite/tests/lib-runtime-events/test_big_event.reference new/ocaml-5.5.2/testsuite/tests/lib-runtime-events/test_big_event.reference --- old/ocaml-5.5.1/testsuite/tests/lib-runtime-events/test_big_event.reference 1970-01-01 01:00:00.000000000 +0100 +++ new/ocaml-5.5.2/testsuite/tests/lib-runtime-events/test_big_event.reference 2026-10-23 13:52:23.000000000 +0200 @@ -0,0 +1 @@ +Fatal error: runtime_events: event of 131 words does not fit in the ring buffer (256 words, from OCAMLRUNPARAM=e=8). Use OCAMLRUNPARAM=e=9 or higher. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/ocaml-5.5.1/testsuite/tests/lib-str/t01.ml new/ocaml-5.5.2/testsuite/tests/lib-str/t01.ml --- old/ocaml-5.5.1/testsuite/tests/lib-str/t01.ml 2026-09-04 13:16:34.000000000 +0200 +++ new/ocaml-5.5.2/testsuite/tests/lib-str/t01.ml 2026-10-23 13:52:23.000000000 +0200 @@ -752,6 +752,29 @@ test_partial_match r n "zorglub" [||]; + start_test "Partial match for /\\([0-9]\\)[a-z]*!/"; + let r = Str.regexp "\\([0-9]\\)[a-z]*!" in + let n = 1 in + test_partial_match r n "" + [|""; "~"|]; + test_partial_match r n "0" + [|"0"; "0"|]; + test_partial_match r n "0abc" + [|"0abc"; "0"|]; + test_partial_match r n "0123" + [||]; + + start_test "Partial match for /\\(\\(a\\)b\\)+/"; + let r = Str.regexp "\\(\\(a\\)b\\)+" in + let valid_group n s = + try ignore (Str.matched_group n s); true + with Not_found -> true | _ -> false in + (* Non-regression for OSEC-2026-21. The exact values of the + captured groups are not well defined, but must be valid. *) + test true + (let s = "ab" in + Str.string_partial_match r s 0 && valid_group 1 s && valid_group 2 s); + (** Replacement *) start_test "Global replacement"; test (Str.global_replace (Str.regexp "[aeiou]") ".." diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/ocaml-5.5.1/testsuite/tests/lib-str/t01.reference new/ocaml-5.5.2/testsuite/tests/lib-str/t01.reference --- old/ocaml-5.5.1/testsuite/tests/lib-str/t01.reference 2026-09-04 13:16:34.000000000 +0200 +++ new/ocaml-5.5.2/testsuite/tests/lib-str/t01.reference 2026-10-23 13:52:23.000000000 +0200 @@ -95,6 +95,10 @@ ... Partial match for /\(partial\)\|\(match\)/ ...... +Partial match for /\([0-9]\)[a-z]*!/ + .... +Partial match for /\(\(a\)b\)+/ + . Global replacement ... First replacement diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/ocaml-5.5.1/testsuite/tests/lib-unix/win-createprocess/script.ml new/ocaml-5.5.2/testsuite/tests/lib-unix/win-createprocess/script.ml --- old/ocaml-5.5.1/testsuite/tests/lib-unix/win-createprocess/script.ml 1970-01-01 01:00:00.000000000 +0100 +++ new/ocaml-5.5.2/testsuite/tests/lib-unix/win-createprocess/script.ml 2026-10-23 13:52:23.000000000 +0200 @@ -0,0 +1,42 @@ +(* TEST + include unix; + hasunix; + target-windows; + { + bytecode; + }{ + native; + } +*) + +(* Test argument passing and quoting to a .cmd/.bat script *) + +open Printf + +let create_file name contents = + Out_channel.with_open_text name (fun oc -> output_string oc contents) + +let run prog args = + try + let pid = Unix.(create_process prog args stdin stdout stderr) in + ignore (Unix.waitpid [] pid) + with Failure s -> + printf "Failure %s\n%!" s + +let chars = {xxx| !"#$%&'()*+,-./0:;<=>?@A[\]^_`a{|}~|xxx} + +let test_char arg = + printf "Testing \"%s\"\n%!" arg; + run ".\\echo.cmd" [|"echo.cmd"; arg; "X"|] + +let _ = + create_file "echo.cmd" + "@echo off\necho %2 \"%~1\"\n"; + create_file "my (script).bat" + "@echo off\necho Hello world!\n"; + String.iter + (fun c -> test_char (String.make 1 c)) + chars; + printf "Extra tests\n%!"; + run "./echo.cmd" [|"echo"; "1"; "2" |]; + run "my (script).bat" [|"dir"|] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/ocaml-5.5.1/testsuite/tests/lib-unix/win-createprocess/script.reference new/ocaml-5.5.2/testsuite/tests/lib-unix/win-createprocess/script.reference --- old/ocaml-5.5.1/testsuite/tests/lib-unix/win-createprocess/script.reference 1970-01-01 01:00:00.000000000 +0100 +++ new/ocaml-5.5.2/testsuite/tests/lib-unix/win-createprocess/script.reference 2026-10-23 13:52:23.000000000 +0200 @@ -0,0 +1,75 @@ +Testing " " +X " " +Testing "!" +X "!" +Testing """ +Failure Unix.create_process: unquotable argument to script: " +Testing "#" +X "#" +Testing "$" +X "$" +Testing "%" +Failure Unix.create_process: unquotable argument to script: % +Testing "&" +Failure Unix.create_process: unquotable argument to script: & +Testing "'" +X "'" +Testing "(" +X "(" +Testing ")" +X ")" +Testing "*" +X "*" +Testing "+" +X "+" +Testing "," +Failure Unix.create_process: unquotable argument to script: , +Testing "-" +X "-" +Testing "." +X "." +Testing "/" +X "/" +Testing "0" +X "0" +Testing ":" +X ":" +Testing ";" +Failure Unix.create_process: unquotable argument to script: ; +Testing "<" +Failure Unix.create_process: unquotable argument to script: < +Testing "=" +Failure Unix.create_process: unquotable argument to script: = +Testing ">" +Failure Unix.create_process: unquotable argument to script: > +Testing "?" +X "?" +Testing "@" +X "@" +Testing "A" +X "A" +Testing "[" +X "[" +Testing "\" +X "\" +Testing "]" +X "]" +Testing "^" +Failure Unix.create_process: unquotable argument to script: ^ +Testing "_" +X "_" +Testing "`" +X "`" +Testing "a" +X "a" +Testing "{" +X "{" +Testing "|" +Failure Unix.create_process: unquotable argument to script: | +Testing "}" +X "}" +Testing "~" +X "~" +Extra tests +2 "1" +Hello world!
