bug-gnulib@gnu.org mirror (unofficial)
 help / color / mirror / Atom feed
From: Bruno Haible <bruno@clisp.org>
To: bug-gnulib@gnu.org
Subject: new module 'immutable'
Date: Sun, 10 Jan 2021 01:23:23 +0100	[thread overview]
Message-ID: <40258955.UtLViUJvzv@omega> (raw)

[-- Attachment #1: Type: text/plain, Size: 1007 bytes --]

A year ago, we discussed the possibility of having a module that implements
immutable objects, along the lines of
<https://lists.gnu.org/archive/html/bug-gnulib/2019-12/msg00231.html> and
<https://lists.gnu.org/archive/html/bug-gnulib/2019-12/msg00235.html>.

Here's the implementation. Quite easy, now that the 'ssfmalloc' module is
present.

Note: In <https://lists.gnu.org/archive/html/bug-gnulib/2019-12/msg00240.html>
we discussed the possible need for data cache flushing. In my tests, it is
not needed (so far). Please report if you find a platform where this module
introduces non-deterministic crashes.


2021-01-09  Bruno Haible  <bruno@clisp.org>

	immutable: Add tests.
	* tests/test-immutable.sh: New file.
	* tests/test-immutable.c: New file.
	* modules/immutable-tests: New file.

	immutable: New module.
	* lib/immutable.h: New file.
	* lib/immutable.c: New file.
	* m4/immutable.m4: New file.
	* m4/mprotect.m4: New file, based on libffcall/m4/codeexec.m4.
	* modules/immutable: New file.


[-- Attachment #2: 0001-immutable-New-module.patch --]
[-- Type: text/x-patch, Size: 18755 bytes --]

From 7e640c93f4b64b972a9e7a02bb092b6b6cfc069d Mon Sep 17 00:00:00 2001
From: Bruno Haible <bruno@clisp.org>
Date: Sun, 10 Jan 2021 01:13:04 +0100
Subject: [PATCH 1/2] immutable: New module.

* lib/immutable.h: New file.
* lib/immutable.c: New file.
* m4/immutable.m4: New file.
* m4/mprotect.m4: New file, based on libffcall/m4/codeexec.m4.
* modules/immutable: New file.
---
 ChangeLog         |   9 ++
 lib/immutable.c   | 254 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 lib/immutable.h   |  93 ++++++++++++++++++++
 m4/immutable.m4   |  11 +++
 m4/mprotect.m4    | 162 ++++++++++++++++++++++++++++++++++
 modules/immutable |  30 +++++++
 6 files changed, 559 insertions(+)
 create mode 100644 lib/immutable.c
 create mode 100644 lib/immutable.h
 create mode 100644 m4/immutable.m4
 create mode 100644 m4/mprotect.m4
 create mode 100644 modules/immutable

diff --git a/ChangeLog b/ChangeLog
index 4329b81..d319a98 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,12 @@
+2021-01-09  Bruno Haible  <bruno@clisp.org>
+
+	immutable: New module.
+	* lib/immutable.h: New file.
+	* lib/immutable.c: New file.
+	* m4/immutable.m4: New file.
+	* m4/mprotect.m4: New file, based on libffcall/m4/codeexec.m4.
+	* modules/immutable: New file.
+
 2021-01-09  Paul Eggert  <eggert@cs.ucla.edu>
 
 	snippet/_Noreturn: port to pedantic clang
diff --git a/lib/immutable.c b/lib/immutable.c
new file mode 100644
index 0000000..089ad17
--- /dev/null
+++ b/lib/immutable.c
@@ -0,0 +1,254 @@
+/* Immutable data.
+
+   Copyright (C) 2021 Free Software Foundation, Inc.
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
+
+/* Written by Bruno Haible <bruno@clisp.org>, 2021.  */
+
+#include <config.h>
+
+/* Specification.  */
+#include "immutable.h"
+
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+
+#if IMMUTABLE_EFFECTIVE
+/* Real implementation.  */
+
+/* Get CHAR_BIT.  */
+# include <limits.h>
+
+/* Get intptr_t, uintptr_t.  */
+# include <stdint.h>
+
+# include <stdio.h>
+
+/* Declare getpagesize().  */
+# include <unistd.h>
+/* On HP-UX, getpagesize exists, but it is not declared in <unistd.h> even if
+   the compiler options -D_HPUX_SOURCE -D_XOPEN_SOURCE=600 are used.  */
+# ifdef __hpux
+extern
+#  ifdef __cplusplus
+       "C"
+#  endif
+       int getpagesize (void);
+# endif
+
+/* Declare mmap(), mprotect().  */
+# include <sys/types.h>
+# include <sys/mman.h>
+
+/* Declare open().  */
+# include <unistd.h>
+# include <fcntl.h>
+
+# include "glthread/lock.h"
+
+
+/* ================= Back end of the malloc implementation ================= */
+
+/* The memory page size.
+   Once it is initialized, a power of 2.  Typically 4096 or 8192.  */
+static uintptr_t pagesize;
+
+/* Initializes pagesize.  */
+static void
+init_pagesize (void)
+{
+  /* Simultaneous execution of this initialization in multiple threads is OK. */
+  pagesize = getpagesize ();
+}
+
+
+/* Variables needed for obtaining memory pages via mmap().  */
+static int file_fd;
+static long file_length;
+
+/* Initialization of these variables. */
+static void
+do_init_mmap_file (void)
+{
+  char filename[100];
+  sprintf (filename, "%s/glimmdata-%d-%ld", "/tmp", getpid (), random ());
+  file_fd = open (filename, O_CREAT | O_TRUNC | O_RDWR | O_CLOEXEC, 0700);
+  if (file_fd < 0)
+    {
+      fprintf (stderr, "glimm: Cannot open %s!\n", filename);
+      abort ();
+    }
+  /* Remove the file from the file system as soon as possible, to make
+     sure there is no leftover after this process terminates or crashes.  */
+  unlink (filename);
+
+  file_length = 0;
+}
+
+/* Once-only initializer for these variables.  */
+gl_once_define (static, for_mmap_once)
+
+static inline void
+init_mmap_file (void)
+{
+  /* Use a once-only initializer here, since simultaneous execution of
+     do_init_mmap_file() in multiple threads must be avoided.  */
+  gl_once (for_mmap_once, do_init_mmap_file);
+}
+
+
+/* Size of the (page-aligned) header that links the writable mapping
+   and the read-only mapping together.  */
+# define SHARED_LINK_HEADER_SIZE \
+    (INTPTR_WIDTH / CHAR_BIT)   /* = sizeof (void *) */
+
+/* Allocates a contiguous set of pages of memory.
+   size > 0, must be a multiple of pagesize.
+   Returns a multiple of PAGESIZE, or 0 upon failure.  */
+static uintptr_t
+alloc_pages (size_t size)
+{
+  /* Extend the file by size/pagesize pages.  */
+  long new_file_length = file_length + size;
+  if (ftruncate (file_fd, new_file_length) < 0)
+    {
+      fprintf (stderr, "glimm: Cannot extend backing file!\n");
+      return 0;
+    }
+  /* Create separate writable mapping and read-only mapping.  */
+  char *mem_w = (char *) mmap (NULL, size, PROT_READ | PROT_WRITE,
+                               MAP_SHARED, file_fd, file_length);
+  char *mem_r = (char *) mmap (NULL, size, PROT_READ,
+                               MAP_SHARED, file_fd, file_length);
+  if (mem_w == (char *)(-1) || mem_r == (char *)(-1))
+    {
+      if (mem_w != (char *)(-1))
+        munmap (mem_w, size);
+      if (mem_r != (char *)(-1))
+        munmap (mem_r, size);
+      return 0;
+    }
+  file_length = new_file_length;
+
+  /* Link the two memory areas together.  */
+  ((intptr_t *) mem_w)[0] = mem_r - mem_w;
+  return (uintptr_t) mem_w;
+}
+
+/* Frees a contiguous set of pages of memory, returned by alloc_pages.
+   size > 0, must be a multiple of pagesize.  */
+static void
+free_pages (uintptr_t pages, size_t size)
+{
+  pages -= SHARED_LINK_HEADER_SIZE;
+  if ((pages & (pagesize - 1)) != 0)
+    abort ();
+  char *mem_w = (char *) pages;
+  char *mem_r = mem_w + ((intptr_t *) mem_w)[0];
+  if (munmap (mem_w, size) < 0)
+    abort ();
+  if (munmap (mem_r, size) < 0)
+    abort ();
+}
+
+/* Cygwin defines PAGESIZE in <limits.h>.  */
+# undef PAGESIZE
+
+/* ======================= Instantiate the front end ======================= */
+
+# define PAGESIZE pagesize
+/* On Cygwin and Linux/PowerPC, PAGESIZE is 65536.  On macOS 11, it is 16384.
+   On all other platforms, it is either 4096 or 8192.  */
+# if defined __CYGWIN__ || (defined __linux__ && defined __powerpc__)
+#  define PAGESIZE_MAX 65536
+# else
+#  define PAGESIZE_MAX 16384
+# endif
+
+# define ALLOC_PAGES alloc_pages
+# define FREE_PAGES free_pages
+# define ALIGNMENT sizeof (void *)
+# define PAGE_RESERVED_HEADER_SIZE SHARED_LINK_HEADER_SIZE
+
+# include "ssfmalloc.h"
+
+
+void *
+immmalloc (size_t size)
+{
+  /* Initializations.  */
+  if (!pagesize)
+    {
+      init_mmap_file ();
+      init_pagesize ();
+    }
+
+  void *writable_pointer = (void *) allocate_block (size);
+  if (writable_pointer == NULL)
+    errno = ENOMEM;
+  return writable_pointer;
+}
+
+const void *
+immfreeze (void *writable_pointer)
+{
+  uintptr_t mem_w = (uintptr_t) writable_pointer & -(intptr_t)pagesize;
+  return (void *) ((uintptr_t) writable_pointer + ((intptr_t *) mem_w)[0]);
+}
+
+void
+immfree (const void *readonly_pointer)
+{
+  uintptr_t mem_r = (uintptr_t) readonly_pointer & -(intptr_t)pagesize;
+  free_block ((uintptr_t) readonly_pointer - ((intptr_t *) mem_r)[0]);
+}
+
+#else
+/* Dummy implementation.  */
+
+void *
+immmalloc (size_t size)
+{
+  void *p = malloc (size);
+  if (p == NULL)
+    errno = ENOMEM;
+  return p;
+}
+
+const void *
+immfreeze (void *writable_pointer)
+{
+  return writable_pointer;
+}
+
+void
+immfree (const void *readonly_pointer)
+{
+  void *writable_pointer = (void *) readonly_pointer;
+  free (writable_pointer);
+}
+
+#endif
+
+
+const char *
+immstrdup (const char *string)
+{
+  size_t size = strlen (string) + 1;
+  void *wp = immmalloc (size);
+  memcpy (wp, string, size);
+  return (const char *) immfreeze (wp);
+}
diff --git a/lib/immutable.h b/lib/immutable.h
new file mode 100644
index 0000000..75a2f5a
--- /dev/null
+++ b/lib/immutable.h
@@ -0,0 +1,93 @@
+/* Immutable data.
+
+   Copyright (C) 2021 Free Software Foundation, Inc.
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
+
+/* Written by Bruno Haible <bruno@clisp.org>, 2021.  */
+
+#ifndef _IMMUTABLE_H
+#define _IMMUTABLE_H
+
+/* This file provide a facility to allocate and free immutable data objects.
+
+   An immutable data object is allocated in three steps:
+     1. You allocate an immutable memory region.
+          DATA *wp = immmalloc (sizeof (*wp));
+        The pointer wp is actually a writable view to the memory region.
+     2. You fill the memory region, through the pointer wp:
+          wp->x = ...;
+          wp->y = ...;
+          ...
+     3. You declare the memory region as frozen.  This means that you relinquish
+        write access.
+          DATA const *p = immfreeze (wp);
+        You can now let wp get out-of-scope.
+
+   Then the pointer p can be used only in read-only ways.  That is, if you cast
+   away the 'const' and attempt to write to the memory region, it will crash at
+   runtime (through a SIGSEGV signal).
+     p->x = ...;             // rejected by the compiler
+     ((DATA *) p)->x = ...;  // crashes at runtime
+
+   Finally, you can free the immutable data object:
+     immfree (p);
+ */
+
+/* If you compile this module with the C macro NO_IMMUTABLE set to 1, or on a
+   platform that lacks support for read-only and writeable memory areas, the
+   functions work alike, except that the "read-only" pointers are actually
+   writable.  */
+
+#include <stddef.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* This macro tells whether the implementation effectively rejects writes to
+   immutable data.  */
+#if !NO_IMMUTABLE && HAVE_WORKING_MPROTECT
+# define IMMUTABLE_EFFECTIVE 1
+#else
+# define IMMUTABLE_EFFECTIVE 0
+#endif
+
+/* Allocates an immutable memory region.
+   SIZE if the number of bytes; should be > 0.
+   Returns a writeable pointer to the memory region.
+   Upon memory allocation failure, returns NULL with errno set to ENOMEM.  */
+extern void * immmalloc (size_t size);
+
+/* Freezes an immutable memory region.
+   WRITABLE_POINTER is a non-NULL return value from immmalloc().
+   Returns a read-only pointer to the same memory region.  */
+extern const void * immfreeze (void *writable_pointer);
+
+/* Frees an immutable memory region.
+   READONLY_POINTER is a return value from immfreeze().  */
+extern void immfree (const void *readonly_pointer);
+
+/* The following is just an application to some data types.  */
+
+/* Allocates an immutable memory region that contains a copy of the given string.
+   Returns a read-only pointer to this duplicated string.
+   Upon memory allocation failure, returns NULL with errno set to ENOMEM.  */
+extern const char * immstrdup (const char *string);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _IMMUTABLE_H */
diff --git a/m4/immutable.m4 b/m4/immutable.m4
new file mode 100644
index 0000000..ffc62d2
--- /dev/null
+++ b/m4/immutable.m4
@@ -0,0 +1,11 @@
+# immutable.m4 serial 1
+dnl Copyright (C) 2021 Free Software Foundation, Inc.
+dnl This file is free software; the Free Software Foundation
+dnl gives unlimited permission to copy and/or distribute it,
+dnl with or without modifications, as long as this notice is preserved.
+
+AC_DEFUN([gl_IMMUTABLE],
+[
+  AC_REQUIRE([gl_FUNC_MPROTECT_WORKS])
+  AC_REQUIRE([AC_C_INLINE])
+])
diff --git a/m4/mprotect.m4 b/m4/mprotect.m4
new file mode 100644
index 0000000..13e27e2
--- /dev/null
+++ b/m4/mprotect.m4
@@ -0,0 +1,162 @@
+# mprotect.m4 serial 1
+dnl Copyright (C) 1993-2021 Free Software Foundation, Inc.
+dnl This file is free software, distributed under the terms of the GNU
+dnl General Public License as published by the Free Software Foundation;
+dnl either version 2 of the License, or (at your option) any later version.
+dnl As a special exception to the GNU General Public License, this file
+dnl may be distributed as part of a program that contains a configuration
+dnl script generated by Autoconf, under the same distribution terms as
+dnl the rest of that program.
+
+dnl Test whether mprotect() works.
+dnl Sets gl_cv_func_mprotect_works and defines HAVE_WORKING_MPROTECT.
+
+AC_DEFUN([gl_FUNC_MPROTECT_WORKS],
+[
+  AC_REQUIRE([AC_CANONICAL_HOST]) dnl for cross-compiles
+  AC_REQUIRE([gl_FUNC_MMAP_ANON])
+
+  AC_CHECK_FUNCS([mprotect])
+  if test $ac_cv_func_mprotect = yes; then
+    AC_CACHE_CHECK([for working mprotect], [gl_cv_func_mprotect_works],
+      [if test $cross_compiling = no; then
+         mprotect_prog='
+           #include <sys/types.h>
+           /* Declare malloc().  */
+           #include <stdlib.h>
+           /* Declare getpagesize().  */
+           #if HAVE_UNISTD_H
+            #include <unistd.h>
+           #endif
+           #ifdef __hpux
+            extern
+            #ifdef __cplusplus
+            "C"
+            #endif
+            int getpagesize (void);
+           #endif
+           /* Declare mprotect().  */
+           #include <sys/mman.h>
+           char foo;
+           int main ()
+           {
+             unsigned long pagesize = getpagesize ();
+           #define page_align(address)  (char*)((unsigned long)(address) & -pagesize)
+         '
+         no_mprotect=
+         AC_RUN_IFELSE(
+           [AC_LANG_SOURCE([
+              [$mprotect_prog
+                 if ((pagesize - 1) & pagesize)
+                   return 1;
+                 return 0;
+               }
+              ]])
+           ],
+           [],
+           [no_mprotect=1],
+           [:])
+         mprotect_prog="$mprotect_prog"'
+           char* area = (char*) malloc (6 * pagesize);
+           char* fault_address = area + pagesize*7/2;
+         '
+         if test -z "$no_mprotect"; then
+           AC_RUN_IFELSE(
+             [AC_LANG_SOURCE([
+                GL_NOCRASH
+                [$mprotect_prog
+                   nocrash_init();
+                   if (mprotect (page_align (fault_address), pagesize, PROT_NONE) < 0)
+                     return 0;
+                   foo = *fault_address; /* this should cause an exception or signal */
+                   return 0;
+                 }
+                ]])
+             ],
+             [no_mprotect=1],
+             [],
+             [:])
+         fi
+         if test -z "$no_mprotect"; then
+           AC_RUN_IFELSE(
+             [AC_LANG_SOURCE([
+                GL_NOCRASH
+                [$mprotect_prog
+                   nocrash_init();
+                   if (mprotect (page_align (fault_address), pagesize, PROT_NONE) < 0)
+                     return 0;
+                   *fault_address = 'z'; /* this should cause an exception or signal */
+                   return 0;
+                 }
+                ]])
+             ],
+             [no_mprotect=1],
+             [],
+             [:])
+         fi
+         if test -z "$no_mprotect"; then
+           AC_RUN_IFELSE(
+             [AC_LANG_SOURCE([
+                GL_NOCRASH
+                [$mprotect_prog
+                   nocrash_init();
+                   if (mprotect (page_align (fault_address), pagesize, PROT_READ) < 0)
+                     return 0;
+                   *fault_address = 'z'; /* this should cause an exception or signal */
+                   return 0;
+                 }
+                ]])
+             ],
+             [no_mprotect=1],
+             [],
+             [:])
+         fi
+         if test -z "$no_mprotect"; then
+           AC_RUN_IFELSE(
+             [AC_LANG_SOURCE([
+                GL_NOCRASH
+                [$mprotect_prog
+                   nocrash_init();
+                   if (mprotect (page_align (fault_address), pagesize, PROT_READ) < 0)
+                     return 1;
+                   if (mprotect (page_align (fault_address), pagesize, PROT_READ | PROT_WRITE) < 0)
+                     return 1;
+                   *fault_address = 'z'; /* this should not cause an exception or signal */
+                   return 0;
+                 }
+                ]])
+             ],
+             [],
+             [no_mprotect=1],
+             [:])
+         fi
+         if test -z "$no_mprotect"; then
+           gl_cv_func_mprotect_works=yes
+         else
+           gl_cv_func_mprotect_works=no
+         fi
+       else
+         dnl When cross-compiling, assume the known behaviour.
+         case "$host_os" in
+           dnl Guess yes on Linux systems, glibc systems,
+           dnl macOS, BSD systems, AIX, HP-UX, IRIX, Solaris, Cygwin.
+           linux-* | linux | *-gnu* | gnu* | \
+           darwin* | freebsd* | dragonfly* | netbsd* | openbsd* | \
+           aix* | hpux* | irix* | solaris* | cygwin*)
+             gl_cv_func_mprotect_works="guessing yes" ;;
+           mingw*)
+             gl_cv_func_mprotect_works="guessing no" ;;
+           *)
+             dnl If we don't know, obey --enable-cross-guesses.
+             gl_cv_func_mprotect_works="$gl_cross_guess_normal" ;;
+         esac
+       fi
+      ])
+    case "$gl_cv_func_mprotect_works" in
+      *yes)
+        AC_DEFINE([HAVE_WORKING_MPROTECT], [1],
+          [have a working mprotect() function])
+        ;;
+    esac
+  fi
+])
diff --git a/modules/immutable b/modules/immutable
new file mode 100644
index 0000000..fef376a
--- /dev/null
+++ b/modules/immutable
@@ -0,0 +1,30 @@
+Description:
+Immutable data.
+
+Files:
+lib/immutable.h
+lib/immutable.c
+m4/immutable.m4
+m4/mprotect.m4
+m4/mmap-anon.m4
+m4/nocrash.m4
+
+Depends-on:
+stdint
+open
+ssfmalloc
+
+configure.ac:
+gl_IMMUTABLE
+
+Makefile.am:
+lib_SOURCES += immutable.c
+
+Include:
+"immutable.h"
+
+License:
+LGPLv2+
+
+Maintainer:
+Bruno Haible
-- 
2.7.4


[-- Attachment #3: 0002-immutable-Add-tests.patch --]
[-- Type: text/x-patch, Size: 5918 bytes --]

From 50a1c8361c20909aab8fc1ced6e84986812594e9 Mon Sep 17 00:00:00 2001
From: Bruno Haible <bruno@clisp.org>
Date: Sun, 10 Jan 2021 01:15:01 +0100
Subject: [PATCH 2/2] immutable: Add tests.

* tests/test-immutable.sh: New file.
* tests/test-immutable.c: New file.
* modules/immutable-tests: New file.
---
 ChangeLog               |   5 ++
 modules/immutable-tests |  15 +++++
 tests/test-immutable.c  | 158 ++++++++++++++++++++++++++++++++++++++++++++++++
 tests/test-immutable.sh |  12 ++++
 4 files changed, 190 insertions(+)
 create mode 100644 modules/immutable-tests
 create mode 100644 tests/test-immutable.c
 create mode 100644 tests/test-immutable.sh

diff --git a/ChangeLog b/ChangeLog
index d319a98..e70dac6 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,10 @@
 2021-01-09  Bruno Haible  <bruno@clisp.org>
 
+	immutable: Add tests.
+	* tests/test-immutable.sh: New file.
+	* tests/test-immutable.c: New file.
+	* modules/immutable-tests: New file.
+
 	immutable: New module.
 	* lib/immutable.h: New file.
 	* lib/immutable.c: New file.
diff --git a/modules/immutable-tests b/modules/immutable-tests
new file mode 100644
index 0000000..3265fa6
--- /dev/null
+++ b/modules/immutable-tests
@@ -0,0 +1,15 @@
+Files:
+tests/test-immutable.sh
+tests/test-immutable.c
+tests/macros.h
+
+Depends-on:
+stdbool
+_Exit
+
+configure.ac:
+
+Makefile.am:
+TESTS += test-immutable.sh
+check_PROGRAMS += test-immutable
+test_immutable_LDADD = $(LDADD) $(LIBTHREAD)
diff --git a/tests/test-immutable.c b/tests/test-immutable.c
new file mode 100644
index 0000000..4bee1f2
--- /dev/null
+++ b/tests/test-immutable.c
@@ -0,0 +1,158 @@
+/* Test of immutable data.
+   Copyright (C) 2021 Free Software Foundation, Inc.
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
+
+/* Written by Bruno Haible <bruno@clisp.org>, 2021.  */
+
+#include <config.h>
+
+#include "immutable.h"
+
+#include <signal.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "macros.h"
+
+struct data
+{
+  int x;
+  long y;
+};
+
+#if IMMUTABLE_EFFECTIVE
+
+static _GL_ASYNC_SAFE _Noreturn void
+segv_handler (int signo)
+{
+  _Exit (0);
+}
+
+static void
+install_segv_handler (void)
+{
+  signal (SIGSEGV, segv_handler);
+# if defined __APPLE__ && defined __MACH__
+  signal (SIGBUS, segv_handler);
+# endif
+}
+
+#endif
+
+int
+main (int argc, char *argv[])
+{
+  if (argc != 2)
+    {
+      fprintf (stderr, "%s: need 1 argument\n", argv[0]);
+      return 1;
+    }
+  int test = atoi (argv[1]);
+  switch (test)
+    {
+    case 0:
+      /* Indicates whether the implementation effectively rejects writes to
+         immutable data.  */
+      #if !IMMUTABLE_EFFECTIVE
+      fputs ("Skipping test: immutability cannot be enforced\n", stderr);
+      return 77;
+      #else
+      break;
+      #endif
+
+    case 1:
+      /* Correct use of immmalloc.  */
+      {
+        struct data *wp;
+        struct data const *p;
+
+        wp = (struct data *) immmalloc (sizeof (struct data));
+        ASSERT (wp != NULL);
+        wp->x = 7;
+        wp->y = 42;
+        p = immfreeze (wp);
+        ASSERT (p->x == 7);
+        ASSERT (p->y == 42);
+        immfree (p);
+      }
+      break;
+
+    case 2:
+      /* Catch invalid write access.  */
+      {
+        struct data *wp;
+        struct data const *p;
+
+        wp = (struct data *) immmalloc (sizeof (struct data));
+        ASSERT (wp != NULL);
+        wp->x = 7;
+        wp->y = 42;
+        p = immfreeze (wp);
+        #if IMMUTABLE_EFFECTIVE
+        install_segv_handler ();
+        #endif
+        /* This assignment should crash.  */
+        ((struct data *) p)->y = 77;
+        #if IMMUTABLE_EFFECTIVE
+        return 1;
+        #endif
+      }
+      break;
+
+    case 3:
+      /* Catch invalid write access while another data object is not frozen.  */
+      {
+        struct data *wp;
+        struct data const *p;
+        struct data *wp2;
+
+        wp = (struct data *) immmalloc (sizeof (struct data));
+        ASSERT (wp != NULL);
+        wp->x = 7;
+        wp->y = 42;
+        p = immfreeze (wp);
+        ASSERT (p->x == 7);
+        ASSERT (p->y == 42);
+
+        wp2 = (struct data *) immmalloc (sizeof (struct data));
+        ASSERT (wp2 != NULL);
+        wp2->x = 7;
+        #if IMMUTABLE_EFFECTIVE
+        install_segv_handler ();
+        #endif
+        /* This assignment should crash.  */
+        ((struct data *) p)->y = 42;
+        #if IMMUTABLE_EFFECTIVE
+        return 1;
+        #endif
+      }
+      break;
+
+    case 4:
+      /* Correct use of immstrdup.  */
+      {
+        const char *s = immstrdup ("Hello");
+        ASSERT (strlen (s) == 5);
+        ASSERT (strcmp (s, "Hello") == 0);
+        immfree (s);
+      }
+      break;
+
+    default:
+      ASSERT (false);
+    }
+  return 0;
+}
diff --git a/tests/test-immutable.sh b/tests/test-immutable.sh
new file mode 100644
index 0000000..5b8d8d0
--- /dev/null
+++ b/tests/test-immutable.sh
@@ -0,0 +1,12 @@
+#!/bin/sh
+
+# Print "Skipping test" if immutability cannot be enforced.
+./test-immutable${EXEEXT} 0
+if test $? = 77; then exit 77; fi
+
+st=0
+for i in 1 2 3 4 ; do
+  ${CHECKER} ./test-immutable${EXEEXT} $i \
+    || { echo test-immutable.sh: test case $i failed >&2; st=1; }
+done
+exit $st
-- 
2.7.4


             reply	other threads:[~2021-01-10  0:23 UTC|newest]

Thread overview: 3+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2021-01-10  0:23 Bruno Haible [this message]
2021-01-18  4:09 ` immutable: Implement on native Windows Bruno Haible
2021-01-18 16:52   ` Gisle Vanem

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

  List information: https://lists.gnu.org/mailman/listinfo/bug-gnulib

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=40258955.UtLViUJvzv@omega \
    --to=bruno@clisp.org \
    --cc=bug-gnulib@gnu.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).