summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBerke Güzel <wenekar1@gmail.com>2026-10-15 12:38:28 +0300
committerBerke Güzel <wenekar1@gmail.com>2026-10-15 12:38:28 +0300
commit86b1eed7dd946d6054eff37cdb7b4a784283e23b (patch)
treea8ce1c995aed62d2e67a48a10dee112788b32ff1
parent1924e4e0c9c863b641f08224f740feb51bf1bc7d (diff)
parent57ebf3b3238a165c1973ecdf96ba79f95d650fc9 (diff)
Merge branch 'wt/abi-version' into api-hardening
-rw-r--r--include/term/term.h15
-rw-r--r--src/meson.build6
-rw-r--r--src/screen.c3
-rw-r--r--src/term.c5
-rw-r--r--tests/meson.build11
-rw-r--r--tests/term_test.c22
-rw-r--r--tests/version_test.c27
7 files changed, 89 insertions, 0 deletions
diff --git a/include/term/term.h b/include/term/term.h
index d413db5..4eea622 100644
--- a/include/term/term.h
+++ b/include/term/term.h
@@ -41,6 +41,16 @@ struct term_snapshot;
#define TERM_SCROLLBACK_DEFAULT 10000
/*
+ * The version of this header. A host compares TERM_VERSION_CODE with
+ * term_version() to check that the library matches the header.
+ */
+#define TERM_VERSION(major, minor, patch) (((major) << 16) | ((minor) << 8) | (patch))
+#define TERM_VERSION_MAJOR 0
+#define TERM_VERSION_MINOR 1
+#define TERM_VERSION_PATCH 0
+#define TERM_VERSION_CODE TERM_VERSION(TERM_VERSION_MAJOR, TERM_VERSION_MINOR, TERM_VERSION_PATCH)
+
+/*
* Colors. A term_color is either a direct color (alpha 0xFF, then RGB) or a
* palette slot: an index 0-255, the default foreground, or the default
* background. A slot resolves when a snapshot is painted, so a theme change
@@ -147,12 +157,17 @@ struct term_options {
enum term_cursor_style cursor_style;
bool cursor_steady; /* false: the cursor blinks */
struct term_callbacks callbacks;
+ /* Must be zero. term_new() returns NULL if any word is nonzero, so a
+ * later version can give a word a meaning. */
+ uint32_t reserved[8];
};
/* NULL when memory runs out. */
/* The Unicode version of the width tables, such as "18.0.0". A host can
* pass it to programs as UNICODE_VERSION. */
TERM_API const char *term_unicode_version(void);
+/* The TERM_VERSION_CODE the library was built with. */
+TERM_API unsigned term_version(void);
/* The length of the prefix of buf made of bytes in [0x20, hi). With hi
* 0x7f, it finds a run of printable ASCII in UTF-8 text, so a host can
diff --git a/src/meson.build b/src/meson.build
index 694ee0e..7223caa 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -1,5 +1,9 @@
subdir('osc')
+# The SONAME carries the major version. An ABI break bumps it, so an old
+# binary keeps the old library.
+term_soversion = meson.project_version().split('.')[0]
+
# The x86-64 SIMD kernels pick their level at run time (see simd.h), so the
# library needs no ISA flags. -march in c_args sets the lowest level.
term_lib = both_libraries('term',
@@ -23,6 +27,8 @@ term_lib = both_libraries('term',
include_directories: term_private_inc,
dependencies: [m_dep],
gnu_symbol_visibility: 'hidden',
+ version: meson.project_version(),
+ soversion: term_soversion,
install: true
)
diff --git a/src/screen.c b/src/screen.c
index 87d6f2c..c41234f 100644
--- a/src/screen.c
+++ b/src/screen.c
@@ -847,6 +847,9 @@ struct term *term_new(const struct term_options *opt)
static const struct term_options defaults;
if (!opt)
opt = &defaults;
+ for (size_t i = 0; i < sizeof(opt->reserved) / sizeof(opt->reserved[0]); i++)
+ if (opt->reserved[i])
+ return NULL;
int cols = opt->size.x < 1 ? 80 : opt->size.x;
int rows = opt->size.y < 1 ? 24 : opt->size.y;
if (cols > TERM_MAX_COLS)
diff --git a/src/term.c b/src/term.c
index 1b16670..bf8f3cf 100644
--- a/src/term.c
+++ b/src/term.c
@@ -8,6 +8,11 @@ const char *term_unicode_version(void)
return UNICODE_DATA_VERSION;
}
+unsigned term_version(void)
+{
+ return TERM_VERSION_CODE;
+}
+
void term_lock(struct term *t)
{
term_write_begin(t);
diff --git a/tests/meson.build b/tests/meson.build
index 69f3c49..5c77412 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -117,6 +117,17 @@ if cc.has_link_argument('-Wl,--wrap=realloc') and cc.has_link_argument('-Wl,--wr
suite: 'unit',
)
endif
+
+# The project version comes in as a define, so the header macros cannot
+# drift from meson.build.
+version_test = executable('version-test', 'version_test.c',
+ c_args: test_c_args + ['-DTERM_TEST_PROJECT_VERSION="@0@"'.format(meson.project_version())],
+ dependencies: [term_internal_dep],
+)
+test('version-test', version_test,
+ env: test_env,
+ suite: 'unit',
+)
test('term-cross-test', bash,
args: [files('cross_test.sh'), meson.project_source_root()],
suite: 'portability',
diff --git a/tests/term_test.c b/tests/term_test.c
index b904148..7b531b1 100644
--- a/tests/term_test.c
+++ b/tests/term_test.c
@@ -1881,8 +1881,30 @@ static void test_concurrent_feed_and_snapshot(void)
term_free(t);
}
+static void test_reserved_options_must_be_zero(void)
+{
+ struct term_options opt = { .size = { .x = 10, .y = 2 } };
+ struct term *t = term_new(&opt);
+ assert(t != NULL);
+ term_free(t);
+
+ size_t words = sizeof(opt.reserved) / sizeof(opt.reserved[0]);
+ for (size_t i = 0; i < words; i++) {
+ opt.reserved[i] = 1;
+ assert(term_new(&opt) == NULL);
+ opt.reserved[i] = 0;
+ }
+ opt.reserved[words - 1] = 0x80000000u;
+ assert(term_new(&opt) == NULL);
+ opt.reserved[words - 1] = 0;
+ t = term_new(&opt);
+ assert(t != NULL);
+ term_free(t);
+}
+
int main(void)
{
+ test_reserved_options_must_be_zero();
test_concurrent_feed_and_snapshot();
test_grow_past_reservation();
test_skipped_cells_read_blank();
diff --git a/tests/version_test.c b/tests/version_test.c
new file mode 100644
index 0000000..0eb889e
--- /dev/null
+++ b/tests/version_test.c
@@ -0,0 +1,27 @@
+#include "term/term.h"
+
+#include <assert.h>
+#include <stdio.h>
+#include <string.h>
+
+/* The project version from meson.build. The build defines it. */
+#ifndef TERM_TEST_PROJECT_VERSION
+#error "define TERM_TEST_PROJECT_VERSION as the project version, such as \"0.1.0\""
+#endif
+
+int main(void)
+{
+ char text[32];
+ snprintf(text, sizeof(text), "%d.%d.%d",
+ TERM_VERSION_MAJOR, TERM_VERSION_MINOR, TERM_VERSION_PATCH);
+ assert(!strcmp(text, TERM_TEST_PROJECT_VERSION));
+
+ int major, minor, patch;
+ assert(sscanf(TERM_TEST_PROJECT_VERSION, "%d.%d.%d", &major, &minor, &patch) == 3);
+ assert(TERM_VERSION_CODE == TERM_VERSION(major, minor, patch));
+ assert(term_version() == TERM_VERSION_CODE);
+
+ assert(TERM_VERSION(0, 2, 0) > TERM_VERSION(0, 1, 255));
+ assert(TERM_VERSION(1, 0, 0) > TERM_VERSION(0, 255, 255));
+ return 0;
+}