There is no reason for key and value to have different annotations.
Both may return NULL as a valid value.
gpointer typed parameters are nullable by convention, so there is
no change here. The (nullable) annotation is just for humans really.
To make it match its definition and documentation comment. This fixes a
g-ir-scanner warning.
Signed-off-by: Philip Withnall <pwithnall@gnome.org>
Helps: #3037
Extended error domains can never be unregistered, so these closures
have to be around forever.
Fixes some g-ir-scanner warnings.
Signed-off-by: Philip Withnall <pwithnall@gnome.org>
Helps: #3037
`GByteArray` is a bit odd in that it allows call-chaining. All the
instances of that were missing a `(transfer none)` annotation though.
Signed-off-by: Philip Withnall <pwithnall@gnome.org>
Helps: #3037
This fixes a warning from g-ir-scanner as the declaration and
documentation comment didn’t match.
Signed-off-by: Philip Withnall <pwithnall@gnome.org>
Helps: #3037
This was pointed out in the review for !3696 but owing to a mistake by
me, the fix was not included in the version of the MR which was merged.
Signed-off-by: Philip Withnall <pwithnall@gnome.org>
Helps: #3119
I wrote the changes before clarifying the copyright status of my
contract with the GNOME Foundation, and forgot to update them.
Signed-off-by: Philip Withnall <pwithnall@gnome.org>
Helps: #3119
It was previously set (by default) to `C`, so this commit doesn’t change
the locale behaviour of the tests, but does ensure that messages printed
by the tests are correctly formatted in UTF-8 rather than transliterated
to ASCII.
That makes interpreting test output easier.
Signed-off-by: Philip Withnall <philip@tecnocode.co.uk>
The `%E` modifier causes dates to be formatted using an alternative era
representation for years. This doesn’t do anything for most dates, but
in locales such as Thai and Japanese it causes years to be printed using
era names.
In Thai, this means the Thai solar calendar
(https://en.wikipedia.org/wiki/Thai_solar_calendar). In Japanese, this
means Japanese era names
(https://en.wikipedia.org/wiki/Japanese_era_name).
The `%E` modifier syntax follows what’s supported in glibc — see
nl_langinfo(3).
Supporting this is quite involved, as it means loading the `ERA`
description from libc and parsing it.
Unit tests are included.
Signed-off-by: Philip Withnall <philip@tecnocode.co.uk>
Fixes: #3119
Point people to the official PCRE documentation instead, which is going
to be more up to date. This saves us periodically having to copy in and
reformat the PCRE documentation.
Signed-off-by: Philip Withnall <pwithnall@gnome.org>
Helps: #3037
Move them to a separate page as they don’t really have a ‘class’ struct
each to hang docs off.
Signed-off-by: Philip Withnall <pwithnall@gnome.org>
Helps: #3037
Move it to a separate page, with a massive great list of all the misc
utils. Not a great documentation page, but equivalent to what we had
before, and it can be improved in future.
Signed-off-by: Philip Withnall <pwithnall@gnome.org>
Helps: #3037
All this API is internal to GLib, so the section was never actually
exposed in the public API documentation. Keep the documentation, just
don’t tag it as a SECTION.
Signed-off-by: Philip Withnall <pwithnall@gnome.org>
Helps: #3037
Move it to a separate documentation file, since most of what’s covered
isn’t introspectable.
Signed-off-by: Philip Withnall <pwithnall@gnome.org>
Helps: #3037
Move it to a separate page so the difference between `g_rand_*()` and
`g_random_*()` can be explained.
Signed-off-by: Philip Withnall <pwithnall@gnome.org>
Helps: #3037
It was one paragraph which mentioned `GHookList` and `GHook`, both of
which have their own adequate documentation sections.
Signed-off-by: Philip Withnall <pwithnall@gnome.org>
Helps: #3037
Move it to a separate page so more detail can be provided about all the
groups of API.
Signed-off-by: Philip Withnall <pwithnall@gnome.org>
Helps: #3037
Move it to a separate page so an overview of the deprecated threading
API can be given.
Signed-off-by: Philip Withnall <pwithnall@gnome.org>
Helps: #3037
My GTK testlogs are filled with
GLib-DEBUG: g_unix_open_pipe() called with FD_CLOEXEC; please migrate to using O_CLOEXEC instead
Lets take the hint, and migrate to using O_CLOEXEC.
As with `test-printf`, the `vasprintf()` placeholder checks on BSDs
(including macOS) are less strict than on Linux (glibc), so the expected
critical message is not seen.
Change the test to not expect it on BSDs.
Signed-off-by: Philip Withnall <pwithnall@gnome.org>
Helps: #3187
It was previously possible for this to silently fail, which isn’t great
for program correctness. Instead, raise a critical warning so the
programmer knows to either validate their Unicode inputs, or fix their
format placeholders.
Signed-off-by: Philip Withnall <pwithnall@gnome.org>
Helps: #3187
It’s possible for the function to fail for the same reasons
`g_vasprintf()` would fail.
Signed-off-by: Philip Withnall <pwithnall@gnome.org>
Helps: #3187
As per the previous commit, it’s possible for
`g_printf_string_upper_bound()` to return an error. We need to catch and
handle that error in `g_vasprintf()` to avoid it trying to write to a
`NULL` string allocation and crashing.
So, call `g_vsnprintf()` directly instead of calling
`g_printf_string_upper_bound()`, so that the error case can be handled.
There was already a test for some of this behaviour
(`test_vasprintf_invalid_format_placeholder()`). Because it tested an
invalid format string, the `_g_vsprintf()` call bailed out before
checking whether the buffer it had been passed was `NULL`. The new test
has a valid format string, but an invalid arg.
When running the tests locally, I have disabled the `USE_SYSTEM_PRINTF`
and `HAVE_VASPRINTF` code paths in `g_vasprintf()`.
Signed-off-by: Philip Withnall <pwithnall@gnome.org>
Fixes: #3187
Spotted in https://gitlab.gnome.org/GNOME/glib/-/issues/3187.
In an ideal world, this API would have been designed with an error
return path to begin with — perhaps by returning a `GError` or a
`gssize`. We can’t change the API now, though, which leads to this
slightly awkward “0 indicates an error or success” pattern.
I think that’s justified in this case because:
- This API does not see much use.
- Format strings tend to be literals, and almost always are
non-zero-length, so it tends to be statically possible to determine
that the function won’t return zero on success.
- If callers do need to differentiate the two zero return value cases,
they can just call `g_vsnprintf()` directly instead.
Signed-off-by: Philip Withnall <pwithnall@gnome.org>
Helps: #3187
The test needs to call `ftruncate64()` (not `ftruncate()`) to guarantee
it’s using the 64-bit version on Linux, but this doesn’t exist on other
platforms.
Test to see if it exists and, if not, skip the test.
Fixes commit cf5e371c67, and fixes CI
failures like https://gitlab.gnome.org/GNOME/glib/-/pipelines/602930.
Signed-off-by: Philip Withnall <pwithnall@gnome.org>
The win32-specific APIs in GLib are only defined when building for
Windows. Unfortunately, this means they can not show up in `GLib-2.0.gir`
if it’s build on Linux (which is what mostly happens).
Consequently, that means they’re not present in the API documentation,
which is a bit of a problem.
Make the symbols in the header available on other platforms
(particularly Linux) if building with `g-ir-scanner` (but not
otherwise). This means the win32 symbols show up in `GLib-2.0.gir` and
hence in the documentation.
Signed-off-by: Philip Withnall <pwithnall@gnome.org>
Helps: #3037
On certain platforms where file size (off_t) can be truncated when assigning to
gsize, get_contents_regfile() may use the truncated size as the size to read. Add
an explicit check to raise an error in such cases.
G_FILE_ERROR_FAILED will be raised in this case, aligning with behavior for other
cases.
This generally affects 32-bit non-Win32 platforms.
Just as they were ignored under gtk-doc, ignore them when scanning the
sources using `g-ir-scanner`, and make it look at the full function
declarations instead.
This fixes inclusion of functions like `g_creat()` in the documentation.
Signed-off-by: Philip Withnall <pwithnall@gnome.org>
Helps: #3037
This allows the methods for `GDir` to be introspected. While we don’t
expect languages which use the introspection bindings to use `GDir`,
full introspection support is necessary for the `GDir` documentation to
be correctly built with gi-docgen.
Signed-off-by: Philip Withnall <pwithnall@gnome.org>
Helps: #3037
All GLib tests normally respect `TMPDIR` for writing their temporary
files to. The utils test, however, contained a hack which overwrote
`TMPDIR` so that it could test the behaviour of `g_get_tmp_dir()` for
regressions.
Unfortunately, that hack affected the whole `utils` test suite, not just
the one regression test.
Use the new `g_test_trap_subprocess_with_envp()` API to allow the
regression test to be run as a subprocess with its environment modified,
which allows us to remove the hack affecting the rest of the test suite.
Spotted in
https://gitlab.gnome.org/GNOME/glib/-/issues/3179#note_1925161.
Signed-off-by: Philip Withnall <pwithnall@gnome.org>
This is a variant of `g_test_trap_subprocess()` which allows the
environment for the child process to be specified. This is quite useful
when you want to test code which reads environment variables, as it’s
not safe to set those after the start of `main()`.
This will be useful within and outwith GLib for testing such code.
Signed-off-by: Philip Withnall <pwithnall@gnome.org>
Helps: #1618
The problem is that resetting the environment of a
proccess is not safe, this is mentioned in the man
pages of the setenv, and also by the concept:
when we get a constant string returned by the getenv,
we need to be guaranteed that this string will exist
during all that time we are using it. While setting
the same env var to another value destroys this string
inside of libc.
Now looking at the should_drop_message(), we can see
that it just gets the environment on every call. Getting
the environment is safe, but however there's no safe way
to switch the logging domains selection: setenv can't be
used because of the reasons listed above.
That is why g_log_writer_default_set_debug_domains() is
needed: it's just a safe replacement for the setenv in this case.
Now should_drop_message() still reads the environment, but
does so only on the first call, after that the domains are
stored internally, and can only be changed by
g_log_writer_default_set_debug_domains().
This also means that now resetting G_MESSAGES_DEBUG env
var in runtime has no effect. But in any case this is not
what the user should do, because resetting the environment
in runtime is not correct.
We presumably want the fallback string used when we cannot determine
the program name to contain balanced angle brackets, as it did before
commit 7098250e.
Fixes: 7098250e "gutils: avoid race setting prgname from g_option_context_parse()/g_application_run()"
Signed-off-by: Simon McVittie <smcv@collabora.com>
g_option_context_parse()/g_application_run()/g_test_init() for
convenience also call g_set_prgname(), when the prgname is unset at this
point. This was racy.
Fix the race by using an atomic compare-and-exchange and only reset the
value, if it is unset still.
g_set_application_name() guards against being reset, but it doesn't
remember whether it was set, it only checks whether g_application_name
was set to non-NULL. When allowing g_set_application_name(NULL) that leads
to odd behaviors, like:
g_set_application_name(NULL);
g_set_application_name("foo");
would not warn.
Disallow that and assert against a NULL application_name.
Note that application_name argument is also not marked as "(nullable)".
Modify all the similar Python test wrappers to set
`G_DEBUG=fatal-warnings` in the environment of the program being tested,
so we can catch unexpected warnings/criticals.
Adding this because I noticed it was missing, not because I noticed a
warning/critical was being ignored.
Signed-off-by: Philip Withnall <philip@tecnocode.co.uk>
Mention that ready time being equal to the current time means the source
will fire immediately.
Related to https://gitlab.gnome.org/GNOME/glib/-/issues/3148
Signed-off-by: Sergey Bugaev <bugaevc@gmail.com>
Instead of tracking a "(guint,GSource*)" tuple in the "context->sources"
dictionary, only track pointers to the "source_id".
With this we use the GHashTable as Set (g_hash_table_add()), which is
optimized and avoids storing a separate value array.
It's simple enough to do, because there are literally 5 references to
"context->sources". It's easy to review those usages and reason that the
handling is correct.
While at it, in g_main_context_find_source_by_id() move the check for
SOURCE_DESTROYED() inside the lock. It's not obvious that doing this
without a lock was correct in every case. But doing the check with
a lock should be fast enough to not worry about whether it's absolutely
necessary.
We have g_int_hash()/g_int_equal(), which in practice might also work
with with pointers to unsigned integers. However, according to strict
interpretation of C, I think it is not valid to conflate the two.
Even if it were valid in all cases that we want to support, we should
still have separate g_uint_{hash,equal} functions (e.g. by just #define
them to their underlying g_int_{hash,equal} implementations).
Add instead internal hash/equal functions for guint.
Previously thread-pool-slow ran a single test which encoded a state
machine and polling timer to run 8 different sub-tests and check for
their exit conditions.
This was a bit ugly, and since the timer ran at 1s granularity, several
of the tests completed quite fast and then hang around for most of 1s
before finishing and moving to the next test.
Split the test functions up into separate GTest tests, and split the
state machine up between the test functions. All of the `GMainLoop`
handling is actually only needed for the `test_threadpool_idle_time()`
test.
This reduces the overall test runtime from 36s to 19s on my machine,
with 17s of that being spent in `test_threadpool_idle_time()`.
Signed-off-by: Philip Withnall <philip@tecnocode.co.uk>
Helps: #2810
This makes things a bit more maintainable, as there’s less global state
to worry about.
It introduces no functional changes.
Signed-off-by: Philip Withnall <philip@tecnocode.co.uk>
The race was already acknowledged in the code (via `last_failed`): the
thread pool starts dequeuing jobs as soon as it’s created, so it’s
dequeuing the sorted thread IDs while they’re still being enqueued and
sorted. This can lead to them being dequeued out of the expected order
if new thread IDs are enqueued out of order, which is possible because
they’re randomly generated.
The test tried to handle this by allowing one out-of-order dequeue, but
it looks like the race can race hard enough that multiple out-of-order
dequeues are possible.
Fix that by only starting to dequeue the jobs from the thread pool once
they’ve all been enqueued and put in a total order.
Signed-off-by: Philip Withnall <philip@tecnocode.co.uk>
Fixes: #2810
If the string of one log domain is contained in
another, it was printing both.
For example, if G_MESSAGES_DEBUG is "Gtkspecial",
it would also keep the logs of the "Gtk" domain
The weak symbol resolution doesn’t seem to work (see
https://gitlab.gnome.org/GNOME/glib/-/jobs/3265405):
```
Undefined symbols for architecture x86_64:
"___lsan_enable", referenced from:
_g_leak_sanitizer_is_supported in gutils.c.o
"___lsan_ignore_object", referenced from:
_g_ignore_leak in gquark.c.o
_g_ignore_leak in gthreadpool.c.o
_g_ignore_leak in gutils.c.o
_g_leak_sanitizer_is_supported in gutils.c.o
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)
```
See !3672
Signed-off-by: Philip Withnall <philip@tecnocode.co.uk>
GLib ignores various leaks that we don't consider as such (like the
default gio modules) via the LSAN public interface, however those cases
are always ignored when using a non-ASAN compiled glib is used by an
ASAN-compiled binary.
This makes all the GLib-related programs to fail because of false
positive leaks.
To avoid this, use the gcc extension for weak linking so that we can
control ASAN and LSAN only if the symbols they provide are actually
available at runtime.
On CHERI-enabled systems we use uintptr_t as the underlying storage for
GType and therefore casting to gsize strips the upper bits from a pointer.
Fix this by casting via uintptr_t instead and introduce a new set of
macros to convert between GType and pointers.
Currently, the introspection data for GLib and its sub-libraries is
generated by gobject-introspection, to avoid the cyclic dependency
between the two projects.
Since gobject-introspection is generally available on installed systems,
we can check for its presence, and generate the introspection data
directly from GLib.
This does introduce a cyclic dependency, which is why it's possible to
build GLib without introspection, then build gobject-introspection, and
finally rebuild GLib.
By having introspection data available during the GLib build, we can do
things like generating documentation; validating newly added API; and
close the loop between adding new API and it becoming available to non-C
consumers of the C ABI (i.e. language bindings).
The alt-digits are loaded from `nl_langinfo()` in a `GOnce` section,
which means `nl_langinfo()` is not re-queried after the process changes
locale (if that happens).
So, change the `GOnce` to a mutex and store the locale of the alt-digits
alongside them. This will introduce contention when calling
`format_number()` is called, but how often are multiple threads trying
to format dates at the same time?
If this does get highlighted as a performance problem, the other
approach I considered was a `GPrivate` struct containing all the
locale-specific cached data. That comes at the cost of using a
`GPrivate` slot (although that’s only particularly expensive on Windows,
and the locale code is quite different for Windows, so perhaps that
could be avoided entirely). It does mean that all locale printing could
be lock-free and still safely update cached data on a locale change.
Signed-off-by: Philip Withnall <pwithnall@endlessos.org>
When calling `g_file_set_contents_full()` without
`G_FILE_SET_CONTENTS_CONSISTENT`, the file is written by opening it,
`write()`ing to it, then closing it.
This is fine as long as the file is not longer than the new content you
want to set its contents to. If it is, the last bit of the old content
remains, because `g_file_set_contents_full()` was missing an
`ftruncate()` call.
Fix that, and change the tests to catch truncation failures in future.
Signed-off-by: Philip Withnall <pwithnall@endlessos.org>
Fixes: #3144
The Windows x64 ABI follows the LLP64 model, so unsigned long int is 32 bits
Fixes the following warnings when compiling for Windows x64:
../glib/gobject/gatomicarray.c:85:3: warning: cast to smaller integer type 'unsigned long' from 'gpointer' (aka 'void *') [-Wvoid-pointer-to-int-cast]
VALGRIND_MALLOCLIKE_BLOCK (mem, real_size - sizeof (GAtomicArrayMetadata),
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
../glib/glib/valgrind.h:6479:5: note: expanded from macro 'VALGRIND_MALLOCLIKE_BLOCK'
VALGRIND_DO_CLIENT_REQUEST_STMT(VG_USERREQ__MALLOCLIKE_BLOCK, \
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
../glib/glib/valgrind.h:203:15: note: expanded from macro 'VALGRIND_DO_CLIENT_REQUEST_STMT'
do { (void) VALGRIND_DO_CLIENT_REQUEST_EXPR(0, \
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
../glib/glib/valgrind.h:417:20: note: expanded from macro 'VALGRIND_DO_CLIENT_REQUEST_EXPR'
_zzq_args[1] = (unsigned long int)(_zzq_arg1); \
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
See https://bugs.kde.org/show_bug.cgi?id=427146
g_strv_builder_take() allows to transfer ownership of the passed in
string.
This can be useful to avoid additional allocations when using functions
that transfer ownership to the caller like g_strdup_printf().
The testcase uses g_strv_builder_take and g_strv_builder_add to demo
that calls can be mixed.
We were initializing a DWORD (unsigned 32 bit) with a constant
of bigger rank (G_MAXSIZE) on 64 bit systems.
Fixes the following warnings on CLang when compiling for x64:
../glib/glib/tests/win32.c:47:14: warning: implicit conversion from 'unsigned long long' to 'DWORD' (aka 'unsigned long') changes value from 18446744073709551615 to 4294967295 [-Wconstant-conversion]
DWORD bp = G_MAXSIZE;
~~ ^~~~~~~~~
glib/glibconfig.h:88:19: note: expanded from macro 'G_MAXSIZE'
#define G_MAXSIZE G_MAXUINT64
^~~~~~~~~~~
../glib/glib/gtypes.h:107:21: note: expanded from macro 'G_MAXUINT64'
#define G_MAXUINT64 G_GUINT64_CONSTANT(0xffffffffffffffff)
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
glib/glibconfig.h:69:52: note: expanded from macro 'G_GUINT64_CONSTANT'
#define G_GUINT64_CONSTANT(val) (G_GNUC_EXTENSION (val##ULL))
^~~~~~~~
<scratch space>:96:1: note: expanded from here
0xffffffffffffffffULL
^~~~~~~~~~~~~~~~~~~~~
Many toolchain did not change the definition of NULL to avoid introducing
breaking changes in existing codebases. For example, on Windows NULL is
0 (int) regardless of the C++ standard in use.
Fixes the following warnings on CLang when compiling for Windows:
../glib/glib/tests/cxx.cpp:539:34: warning: missing sentinel in function call [-Wsentinel]
g_test_init (&argc, &argv, NULL);
^
, nullptr
../glib/glib/gtestutils.h:298:9: note: function has been explicitly marked sentinel here
void g_test_init (int *argc,
^
../glib/gio/tests/cxx.cpp:62:34: warning: missing sentinel in function call [-Wsentinel]
g_test_init (&argc, &argv, NULL);
^
, nullptr
../glib/glib/gtestutils.h:298:9: note: function has been explicitly marked sentinel here
void g_test_init (int *argc,
^
Round-tripping pointers via gsize is not guaranteed to work (the C standard
only requires this for (u)inptr_t) and in fact breaks on CHERI-enabled
systems such as Arm Morello where pointers are 128-bits but size_t is 64.
This means the current casts would strip the high bits of the pointer and
return a non-dereferenceable value. Fix this by casting the operand that
holds the pointer to guintptr instead of gsize.
Helps: https://gitlab.gnome.org/GNOME/glib/-/issues/2842
This is required for CHERI systems such as Arm Morello, where gsize is
not large enough to hold a pointer. For all other architectures this
should not result in any functional changes since gsize is already the
same size (and I believe for most architectures even the same type)
as guintptr.
This is fully ABI compatible with all currently supported architectures
and should also be fully API compatible except for some rather unlikely
examples such as someone happening to use something like
`decltype(g_atomic_pointer_add(&a, 0))` in a C++ context where it
changes name mangling of some function.
Helps: https://gitlab.gnome.org/GNOME/glib/-/issues/2842
Use a gpointer* instead of a gsize* as the g_atomic_pointer_* argument.
This is required for architectures such as Arm Morello where gsize is
64 bits but gpointer is 128 bits.
Helps: https://gitlab.gnome.org/GNOME/glib/-/issues/2842
If no error with -Werror=sign-conversion, the resulting object file
does not differ from compilation without -Werror=sign-conversion.
So the -Werror argument is now applied directly to string.c and testing.c.
Finally, the currently specific -Werror targets string-macro and
testing-macro are removed.
We can't easily use g_autofd with g_unix_open_pipe, because its
parameter is an array of two fds that both need closing. Add an inline
convenience wrapper providing the obvious semantics.
Signed-off-by: Simon McVittie <smcv@collabora.com>
g_clear_fd() is documented to be async-signal safe whenever the
fd is either negative or valid (which it should be here) and the error
is NULL (which it always is here).
Signed-off-by: Simon McVittie <smcv@collabora.com>
It's not very likely, but there is a small chance that an
incoming signal could disturb the non-blocking read calls in
g_wakeup_acknowledge, leading to a subsequent spurious wake up.
This commit addresses the problem by doing the usual EINTR
loop (which is already present on the write side incidentally)
Previously, this would loop as long as read() got the expected number of
bytes back, which is 8. That means every successful read() of the eventfd
would perform an additional syscall() as a followup.
This is not ideal because eventfd (unless used as an EFD_SEMAPHORE) will
reset the counter as part of the read(). So that means that we either do
an additional throw-away syscall() or potentially race against a producer
generating new events before this change.
Currently the GSourceList has it's own allocation plus a secondary
allocation for the GList which contains it (from GMainContext). Not only
are these a pointer chase, but they are on separate cachelines too. Without
changing the code much we can at least keep things on the same cacheline
so that the pointer chase matters less.
Since the GList becomes embedded in the GSourceList you can use a
g_queue_unlink() directly removing the link without traversing the GList
like was done before.
Furthermore, we can simplify some code with g_queue_push_tail_link()
instead of some extra branching.
This should not result in any functional changes, but will eventually
allow glib to be functional on CHERI-enabled systems such as Morello.
Helps: https://gitlab.gnome.org/GNOME/glib/-/issues/2842
These functions can be used to initalize pointer-type variables rather
than a gsize. This is required to support CHERI-enabled platforms where
gsize cannot be used to store pointers. Follow-up changes will migrate
the uses of g_once_init that store pointers to the new API
Helps: https://gitlab.gnome.org/GNOME/glib/-/issues/2842
One test key was serving two purposes, which meant tests for it couldn’t
be separated out. It was testing escape-at-end-of-line and also
invalid-escape.
Signed-off-by: Philip Withnall <pwithnall@endlessos.org>
Helps: #3098
This reverts commit 4a96727642.
It was a temporary workaround to prevent disruption to apps late on in
the 2.78 cycle. Since we’re now early in the 2.80 cycle, let’s revert
the workaround and allow apps more time to fix their error handling for
`GKeyFile`.
Fixes: #3098
Commit 0b114b2687 accidentally made the
tests only pass on systems running in a UTC timezone, as it checked the
local timezone `%Z` format against `UTC`.
This happens to pass on all the GLib CI runners except the macOS one,
which is running under CET.
Make the formatting tests timezone-independent by running them with UTC
datetimes. There’s already a separate test for checking that `%Z` works
correctly in a non-UTC timezone (`test_z`).
Signed-off-by: Philip Withnall <pwithnall@endlessos.org>
See: https://gitlab.gnome.org/GNOME/glib/-/merge_requests/3611#note_1859208
POSIX allows dlclose() implementations that are no-ops,
and in such case library destructors run at application
exit rather than dlclose().
That's the case, for example, of UNIX systems with the
Musl LibC.
This way, the generated GResource code won't choke if no prototypes are found
when being built by clang-cl, which also goes the _MSC_VER >= 1500 route.
Fixes clang-cl build of generated GResources code when building the appstream
git checkout, which supported Windows recently, as the build there demands
'-Werror,-Wmissing-prototypes'.
Part-of: <https://gitlab.gnome.org/GNOME/glib/-/merge_requests/3589>
With gcc and clang using -Wsign-conversion a related warning is generated
if code using glib 2.78.0 contains function g_assert_cmpint().
Warning is fixed by adding related casts. Related tests have been also
updated and will be also compiled with -Wsign-conversion to detect
related problems in future.
The pointer argument must not be `NULL` (though it can point to a
location which is zero/null-valued), so this should be `(not optional)`
not `(not nullable)`.
Spotted in !3577.
Signed-off-by: Philip Withnall <pwithnall@endlessos.org>
On FreeBSD /home is generally a symlink to /usr/home, so the relative
resolves to /usr/bin instead of /bin and `sh` can only be found in /bin
on FreeBSD. Fix this by resolving symlinks in the CWD first.
Some compilers (e.g. CHERI LLVM) warn when casting a non-intptr_t integer
type to a pointer. The GSIZE_TO_POINTER() macro thus triggers warnings
which can be silence by casting to guintptr before gpointer.
NB: This macro must not be used to create valid pointers from a integer.
The gutils-user-database.c is broken in two ways and currently doesn't test anything:
* It only overrides getpwuid, where the implementation used getpwnam_r if it exist, which should be every system for at least 20 years.
* It only partly cargo-culted setting the environment for the local and installed tests, but failed to actually set the environment for either.
Before commit 71b7efd08a, `GKeyFile`
incorrectly allowed invalid escape sequences: it would treat the
sequence as a literal, set a `GError`, but not return failure from the
function. So if a caller was explicitly checking for returned `GError`s,
they could detect the invalid escape; but if they were just checking the
function’s return value, they’d miss it.
This is not correct use of `GError`, and the [Desktop Entry
Spec](https://specifications.freedesktop.org/desktop-entry-spec/latest/ar01s04.html)
doesn’t allow for invalid escape sequences to be accepted. So it’s wrong
in both ways.
However, the commit above changed this behaviour without realising it,
quite close to the 2.78 stable release deadline. There are numerous key
files in the wild which use invalid escape sequences, and it’s too late
in the cycle to ‘break’ parsing of all of them.
So, for now, revert to the old behaviour for invalid escape sequences,
and give people another cycle to adapt to the changes. This will likely
mean they end up calling `g_key_file_get_value()` rather than
`g_key_file_get_string()`. See
https://gitlab.gnome.org/GNOME/glib/-/issues/3098 for tracking
re-enabling the error handling for invalid escape sequences.
Signed-off-by: Philip Withnall <philip@tecnocode.co.uk>
Fixes: #3095
See: #3098
pthread_exit() is also marked noreturn, and the function does not in
fact return.
For win32, I can't find a copy of the header file to check if
_endthreadex() is marked with __declspec(noreturn), but its
documentation does say that it never returns.
When parsing a line of "key3=foo\i\" in a keyfile,
g_key_file_parse_value_as_string currently first sets the error to
'contains invalid escape' and later to 'contains escape character at end
of line'.
This leaks the first GError and causes the following warning message:
Error set over the top of a previous GError or uninitialized memory.
This indicates a bug in someone's code. You must ensure an error is
NULL before it's set. The overwriting error message was: Key file
contains escape character at end of line
Fix this by returning when an error is detected. As we may have
collected data in pieces, we instead collect to a tmp_pieces GSList and
free it on error.
On 32 bit systems 'long' value will overflow in 2038 and become negative.
As it is used to index into letters array, and % operation preserves signs,
data corruption will then occur.
Signed-off-by: Alexander Kanavin <alex@linutronix.de>
The point of to be able to call localtime()/localtime_r() from another
place inside glib (without reimplementing the #ifdef).
- also handles failures from localtime_r(). It is documented that it
might fail, so detect the failure.
- in case of failures of localtime(), still initialize the GDate.
Previously, we may error out with a g_critical() assertion before.
However, now that failures from localtime_r() are also caught, I think
we should make an effort to initialize the GDate to something. It
either way it not supposed to happen.
Print the PID, the errno and the pidfd in case of an unexpected failure
in g_child_watch_dispatch().
This is always(?) caused by a bug in the user application. Also hint to
g_child_watch_source_new() documentation for possible causes.
Also use G_PID_FORMAT for printing GPid values.
Prompted by #3071, this clarifies that `g_test_trap_subprocess()` uses
`g_child_watch_source_new()` internally, so it will not work if any of
the preconditions for using that API are not met. In particular, if
`SIGCHLD` is ignored, things will break.
This documentation is not meant to be an API guarantee which constrains
the implementation of `g_test_trap_subprocess()` in future, just a tip
to people currently using the API.
Signed-off-by: Philip Withnall <philip@tecnocode.co.uk>
Helps: #3071
This is one step towards rectifying the mistake of using `FD_CLOEXEC` in
the first place. Eventually we may deprecate support for `FD_CLOEXEC`,
as the `O_*` flags better match the underlying `pipe()` API.
See discussion on
https://gitlab.gnome.org/GNOME/glib/-/merge_requests/3459#note_1779264
Signed-off-by: Philip Withnall <philip@tecnocode.co.uk>
Since 5c65437d "glib-unix: Add O_NONBLOCK support to g_unix_open_pipe()"
we have been using O_NONBLOCK unconditionally, so we might as well drop
the fallback here as well. This commit should be reverted if someone
reports a significant/supported platform that genuinely doesn't have
O_NONBLOCK.
Signed-off-by: Simon McVittie <smcv@collabora.com>
Since 5c65437d "glib-unix: Add O_NONBLOCK support to g_unix_open_pipe()"
we have effectively been assuming that these two flags are
distinguishable. If that's an assumption we want to make, we should make
it a static assertion, so that GLib will fail to compile on platforms
where it isn't true.
Signed-off-by: Simon McVittie <smcv@collabora.com>
Previous default used was 32KiB (the library default) which caused some
complex patterns to fail, see #2824. The memory will not be allocated
unless used.
There is no point to enable jit in g_regex_new, since JIT will be only
used when we do a first match, and at that point
enable_jit_with_match_options will be called again already and will
update the options set in g_regex_new. Instead just run it at first
match for the first time, to the same end result.
We might repeatedly get si_pid == 0 for a child that hasn't exited,
meaning we won't get a correct exit status. This seems to happen when
the glib application tracks a ptrace():ed child process; the correct
exit status of the process using e.g. a BPF program, where one can
observe that glib appears to get it wrong.
Fixes: #3071
While it's only called one time, this is something that can only happen
during the function call, so it's more correct to mark it as call scope,
so that bindings don't have to wait for the callback invocation to
cleanup the data.
As per commit 5d738ddc some callbacks are not nullable anymore even
though they should be.
This breaks the introspection as some arguments won't be considered
nullable anymore.
You can notice this in https://gitlab.gnome.org/3v1n0/gjs/-/pipelines/558839
where some some tests are running against stable g-i version and others
(the failing ones) against the devel one.
Currently, when adding new elements to GTree we blindly increment the node
counter, which is only of guint size (so 32-bit even on 64-bit Unix
platforms).
This is even more problematic because the only way to check whether
particular GTree is empty is to check whether its node count is zero.
This will obviously give wrong answer if this counter overflows.
Let's fix this by adding an appropriate check when adding a new node.
For the recently added g_tree_{insert,replace}_node () API we can simply
return NULL in such case.
However, the older g_tree_{insert,replace} () API doesn't have any ability
to return an error so for them we follow the example of
g_ptr_array_extend () and g_ptr_array_set_size () by calling g_error ()
when this happens.
Signed-off-by: Maciej S. Szmigiero <maciej.szmigiero@oracle.com>
g_tree_nnodes () is returning a signed integer type (gint), however the
tree node counter value type is really an unsigned integer (guint).
This means that the returned size will be negative if the container holds
more than G_MAXINT elements.
Add a note to this function that its return value can be cast back to
guint in order to support its full range of values.
This will also make sure that we take this into account in future Glib
versions.
Signed-off-by: Maciej S. Szmigiero <maciej.szmigiero@oracle.com>
Fix typo in gio/gappinfo (duplicated word)
Fix typo in gio/gtlsdatabase.c (duplicated word)
Fix typo in gio/gapplication.c (duplicated word, previous words rearranged to improve readability)
Fix typo in glib/tests/gvariant.c (duplicated word)
Fix typo in glib/win_iconv.c (duplicated word)
Fix typo in gio/gschema.dtd (meaning, missing letter)
Fix typo in gio/gdbusintrospection.c (duplicated word)
Fix typo in gio/gdbusintrospection.c (duplicated word).
As commit 44616ebafd ('gmain: More explicitly document
g_main_context_release() prereqs') correctly notes, you need to have the
context acquired before releasing it (just like a ref must match an
unref).
Commit 3926af723a ('gmain: Add precondition assertions to
g_main_context_release()') then goes one step further, and requires that
the calling thread is also the owner (the thread, that acquired the
context).
This is something which has been documented by g_main_context_release()
for years:
> Releases ownership of a context previously acquired **by this thread**
With acquire/release and g_main_context_is_owner() we track the thread
that acquired the context. That is mainly useful for asserting
correctness to not accessing the context from an unexpected thread.
Note that g_main_context_acquire() returns FALSE and does nothing when
the context is already acquired from another thread. Methods like
g_main_context_{prepare,query,dispatch}() require that the calling
thread is the owner (although, they don't assert for that, which they
maybe should).
With the assertion, it means you cannot pass an acquired context to
another thread for release. Obviously, if you pass on an acquired
context to another thread, the only next thing you can do is
g_main_context_release() (no acquire,prepare,query,dispatch). But it's
still useful to be able to release it, and to be able to keep it
acquired for a prolonged time.
libnm needs that, as it integrates a GMainContext into another
GMainContext. For that, it needs to acquire the inner context and keep
it acquired for as long as the context is integrated. Otherwise, when a
source gets attached to the inner context, the inner context is not
woken up (see g_source_attach_unlocked()). In commit e26a8a5981 ('Add
G_MAIN_CONTEXT_FLAGS_OWNERLESS_POLLING'), a flag was introduced to solve
that same problem, without keeping the inner context acquired all the
time. Note that G_MAIN_CONTEXT_FLAGS_OWNERLESS_POLLING is a flag of the
GMainContext, so it only works if the user who integrates the inner
context also controls how the context was created. For libnm, having
the inner context acquired at all times is no problem, because it's
understood that the user gives up agency on the inner context while it's
integrated. The only thing to consider is that the outer context might
be iterated on another thread. When calling prepare,query,dispatch on
the inner context, the code will notice it, release the inner context
and re-acquire it on the new thread ([1]). This works just fine, but it
requires that g_main_context_release() works from any thread.
So, in order to not break NetworkManager, let’s drop the ownership
assertion. However, NetworkManager is strictly breaking the API contract
here, and GLib reserves the right to re-add this assertion in future.
Still keep the assertion for the owner_count.
(Commit and commit message significantly updated by Philip Withnall; all
errors are his.)
[1] 9f01cff04f/src/libnm-glib-aux/nm-shared-utils.c (L4944)
Related: 3926af723a ('gmain: Add precondition assertions to g_main_context_release()')
Related: c67dd9d3fe ('gmain: Add a missing return on error path in g_main_context_release()')
Closes#3054
This reverts commit 252bbcd207.
After further discussion in !3511, we’ve decided that there are risks
associated with this change, and it’s not the best way of addressing the
original problem.
The original motivation for the change turned out to be that
`-mms-bitfields` was not handled by `windres`, which was receiving it
from `pkg-config --cflags glib-2.0` in some projects. However, if
`windres` is claiming to accept CFLAGS then it should accept (and
ignore) `-mms-bitfields`, since the `-m` family of options are defined
in `man gcc`, just like `-I`, `-D`, etc.
There is some question that there might still be third party projects
which are built with an old enough compiler that `-mms-bitfields` is not
the compiler default. For that reason, we should either still continue
to specify `-mms-bitfields` in the `.pc` file, or add a test to assert
that third party projects are always compiled with `-mms-bitfields` set.
But adding a new test for all third-party compilations is risky (if we
get it wrong, things will break; and it’s a test which may behave
differently on different platforms), so it seems safer to just keep
`-mms-bitfields` in `.pc` for now.
Once all compilers which we require specify `-mms-bitfields` by default,
we can finally drop this flag (without adding a test for third-party
compilations).
See: https://gitlab.gnome.org/GNOME/glib/-/merge_requests/3511
The old name was not quite correct: the part of a URI which is just past
the scheme might be the hostname or the path. It isn’t necessarily just
a path.
This introduces no functional changes.
Signed-off-by: Philip Withnall <pwithnall@endlessos.org>
Otherwise, crashing tests like assert-msg-test will still report to
pipe-based crash reporting frameworks like systemd-coredump, even though
the RLIMIT_CORE limit is zero.
Signed-off-by: Simon McVittie <smcv@collabora.com>
Following Emmanuele's instructions for use of introspection annotations:
https://www.bassi.io/articles/2023/02/20/bindable-api-2023/
I have audited all uses of the (closure) annotation in glib and
determined that only a handful are correct. This commit changes almost
all of our use of (closure) annotations to conform to Emmanuele's rules.
We don't know how big a pthread_key_t is, so we malloc() a big enough
chunk of memory for it and store a pointer into the GPrivate struct.
It turns out, on Linux, pthread_key_t is just an int, so we could much
easier just store it directly into the struct.
https://bugzilla.gnome.org/show_bug.cgi?id=737445Fixes: #931
g_private_get_impl() was returning a pointer to the pthread_key_t, but every
function using this was immediately dereferencing it. Change it so that we
dereference the value in the helper function and return it by value.
https://bugzilla.gnome.org/show_bug.cgi?id=737445
An oversight in 86b4b045: since the comment of group N now consists of
the last null-key values of group N-1, these keys must obviously be
skipped when adding a new non-null key to group N-1.
Closes: #3047
Fixes: 86b4b0453e
A forgotten edge case in 86b4b045: when the last value of the last group
has been added via g_key_file_set_value() and it contains line breaks.
The best we can do in this case is probably to do nothing.
Closes: #3047
Fixes: 86b4b0453e
Although g_ptr_array_sort_with_data() could achieve the goal, the
wrapper functions should be expected there because that's the reason
why they are added.
To match the current widespread usage.
We can’t automatically append a colon to the group description, as that
would interact badly with translation of the string.
Signed-off-by: Philip Withnall <pwithnall@endlessos.org>
Fixes: #3007
Just in case anyone tries to look it up using `find_program()` in a
`meson.build` in GLib (or a project pulling GLib in as a subproject) in
future.
Signed-off-by: Philip Withnall <pwithnall@endlessos.org>
The code to test that `g_usleep(0)` returns immediately assumes that
`g_usleep(1)` always takes longer, but that’s not necessarily always the
case. Even if no sleeping happens, the function call for `g_usleep(0)`
could get descheduled and take longer than normal.
This results in occasional failures like this one:
```
GLib:ERROR:../glib/tests/timer.c:367:test_usleep_with_zero_wait: assertion failed (elapsed0 <= elapsed1): (0.000206 <= 0.000202)
```
(Source: https://gitlab.gnome.org/GNOME/glib/-/jobs/2898468)
I can’t think of a suitable invariant comparison which can be done with
the timers, but running the comparison 10 times and allowing it to fail
once should work. A probabilistic test of `g_usleep(0)`.
Signed-off-by: Philip Withnall <pwithnall@endlessos.org>
It’s fairly consistently timing out on macOS. Looking at the verbose
test output, it’s still making progress right up until when it times out
(i.e. it hasn’t hit a `GRecMutex` bug and hasn’t deadlocked), so it
seems that the test runner is just hopelessly overloaded/underpowered
for the number of threads and iterations we’re asking it to test.
Tone those numbers down for CI test runs then.
Signed-off-by: Philip Withnall <pwithnall@endlessos.org>
And dynamically allocate the arrays. This will allow the scale of the
test to be configured in the following commit, which will allow it to be
tweaked to not time out on slow CI runners.
Signed-off-by: Philip Withnall <pwithnall@endlessos.org>
Displaying the execution time will aid developers in understanding which
test cases are responsible for slow execution times. The test code is
already measuring the execution time for every test case, but is not
reporting that data anywhere accessible to developers running the tests.
The new code will print a TAP comment:
# slow test /the/test/path executed in NN.NN secs
for any test taking longer than 0.5 seconds to run.
Example new output format:
$ ./build/glib/tests/unix
TAP version 13
# random seed: R02S690dc3c7a04866e4890501eedc7f8eef
1..13
# Start of glib-unix tests
ok 1 /glib-unix/pipe
# /glib-unix/pipe-stdio-overwrite summary: Test that g_unix_open_pipe() will use the first available FD, even if it?s stdin/stdout/stderr
# Bug Reference: https://gitlab.gnome.org/GNOME/glib/-/issues/2795
ok 2 /glib-unix/pipe-stdio-overwrite
ok 3 /glib-unix/error
ok 4 /glib-unix/nonblocking
ok 5 /glib-unix/sighup
# slow test /glib-unix/sighup executed in 0.50 secs
ok 6 /glib-unix/sigterm
# slow test /glib-unix/sigterm executed in 0.50 secs
ok 7 /glib-unix/sighup_again
# slow test /glib-unix/sighup_again executed in 0.50 secs
ok 8 /glib-unix/sighup_add_remove
ok 9 /glib-unix/sighup_nested
ok 10 /glib-unix/callback_after_signal
# slow test /glib-unix/callback_after_signal took 2.00 secs
ok 11 /glib-unix/child-wait
# Start of get-passwd-entry tests
# /glib-unix/get-passwd-entry/root summary: Tests that g_unix_get_passwd_entry() works for a known-existing username.
ok 12 /glib-unix/get-passwd-entry/root
# /glib-unix/get-passwd-entry/nonexistent summary: Tests that g_unix_get_passwd_entry() returns an error for a nonexistent username.
ok 13 /glib-unix/get-passwd-entry/nonexistent
# End of get-passwd-entry tests
# End of glib-unix tests
As a practical usage example, the meson log can be queried to find
slow tests project-wide:
$ grep 'slow test' build/meson-logs/testlog.txt | sort -n -k 7 -r | head
# slow test /threadpool/basics executed in 36.04 secs
# slow test /gobject/refcount/properties-3 executed in 30.00 secs
# slow test /gio/io-basics executed in 12.54 secs
# slow test /timeout/rounding executed in 10.60 secs
# slow test /GObject/threaded-weak-ref executed in 10.42 secs
# slow test /thread/rerun-all executed in 9.84 secs
# slow test /gobject/refcount/object-advanced executed in 5.46 secs
# slow test /thread/static-rw-lock executed in 5.00 secs
# slow test /gobject/refcount/signals executed in 5.00 secs
# slow test /gobject/refcount/signals executed in 5.00 secs
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
This is useful when writing similarly low-level code, and was always true
as implemented here; let's document it so that other codebases can rely
on it.
Signed-off-by: Simon McVittie <smcv@collabora.com>
Currently we require explicitly specifying the port when configuring a
proxy server, which is seriously weird. I take the fact that nobody
reported a bug until 2022 to indicate that almost nobody is using
proxies. Whatever. Let's assume that if no port is provided, the default
port for the protocol should be used instead.
For example, you can now specify in GNOME settings that your proxy server
is https://example.com and it will work. Previously, you had to write
https://example.com:443. Yuck!
This was originally reported as GProxyResolver bug, but nothing is
actually wrong there. It's actually GProxyAddressEnumerator that gets
tripped up by URLs returned by GProxyResolver without a default port.
This breaks GSocketClient.
Fixing this requires exposing GUri's _default_scheme_port() function to
GIO. I considered copy/pasting it since it's not very much code, but I
figure the private call mechanism is probably not too expensive, and I
don't like code duplication.
Fixes#2832
This is true for socks://, socks4://, socks4a://, and socks5://. I could
list them individually and risk breaking in the future if socks6:// ever
exists, or test for "socks" and risk breaking if a future URL scheme
begins with "socks" but doesn't use port 1080. I picked the latter.
This allows the `g_free()` wrapper introduced in the previous commit to
only be defined if `free_sized()` is actually available to improve
performance.
This avoids passing an allocation size to every `g_free()` call if it’s
not going to be used, saving a register store instruction each time.
Suggested by Marco Trevisan in
https://gitlab.gnome.org/GNOME/glib/-/merge_requests/3252#note_1660032
Signed-off-by: Philip Withnall <pwithnall@endlessos.org>
When using GCC we can take the advantage of __builtin_object_size() to
know the allocated size of a memory area, this generally only works when
some optimization level enabled (-O1 seems enough here) and can provide
us with memory size information for lower-level optimizations.
Adds a helper to truncate UTF8 strings in the middle, allowing to make
them fit certain size constraints.
This function is modeled after similar functionality that has existed
since 2008 in nautilus and in eel before that.
It's redundant, which leads to impossible code like:
if (child_watch_source->using_pidfd)
{
if (child_watch_source->poll.fd >= 0)
close (child_watch_source->poll.fd);
GChildWatchSource uses waitpid(), among pidfd and GetExitCodeProcess().
It thus only works for child processes which the user must ensure to
exist and not being reaped yet. Also, the user must not kill() the PID
after the child process is reaped and must not race kill() against
waitpid(). Also, the user must not call waitpid()/kill() after the child
process is reaped.
Previously, GChildWatchSource would call waitpid() already when adding
the source (g_child_watch_source_new()) and from the worker thread
(dispatch_unix_signals_unlocked()). That is racy:
- if a child watcher is attached and did not yet fire, you cannot call
kill() on the PID without racing against the PID being reaped on the
worker thread. That would then lead to ESRCH or even worse, killing
the wrong process.
- if you g_source_destroy() the source that didn't fire yet, the user
doesn't know whether the PID was reaped in the background. Any
subsequent kill()/waitpid() may fail with ESRCH/ECHILD or even address
the wrong process.
The race is most visible on Unix without pidfd support, because then the
process gets reaped on the worker thread or during g_child_watch_source_new().
But it's also with Windows and pidfd, because we would have waited for
the process in g_child_watch_check(), where other callbacks could fire
between reaping the process status and emitting the source's callback.
Fix all that by calling waitpid() right before dispatching the callback.
Note that the prepare callback only has one caller, which pre-initializes
the timeout argument to -1. That may be an implementation detail and not
publicly promised, but it wouldn't make sense to do it any other way in
the caller.
Also, note that g_unix_signal_watch_prepare() and the UNIX branch of
g_child_watch_prepare() already relied on that.
Note that the variable source_timeout is already initialized upon
definition, at the beginning of the block.
It's easy to see, that no code changes the variable between the variable
definition, and the place where it was initialized. It was thus
unnecessary.
It's not about dropping the unnecessary code (the compiler could do that
just fine too). It's that there is the other branch of the "if/else", where
the variable is also not initialized. But the other branch also requires
that the variable is in fact initialized to -1, because prepare()
callbacks are free not to explicitly set the output value. So both
branches require the variable to be initialized to -1, but only one of
them did. This poses unnecessary questions about whether anything is
wrong. Avoid that by dropping the redundant code.
- if a child watch source has "using_pidfd", it is never linked in the
unix_child_watches list. Drop that check.
- replace the deep nested if, with an early "continue" in the loop,
if we detect there is nothing to do. It makes the code easier to
read.
Let's move the difference between the win/unix implementations closer to
where the difference is. Thereby, we easier see the two implementations
side by side. Splitting it at a higher layer makes the code harder to
read.
This is just a preparation for what comes next.