fix gvpr corruption of dynamically allocated arguments to user-defined functions
Gvpr programs can define their own functions which can then be called within the
same program:
void foo(string s) {
print(s);
}
foo("bar");
This mostly worked. However in some cases the gvpr implementation was not
extending the lifetime of the memory allocated to store the passed in value
long enough. Enumerating the cases in which this occurred is complicated because
whether this (used-after-free) memory retained its intended content depended on
(1) the complexity of the expression of the passed in value and (2) what the
target function (`foo` in the above example) was itself doing. As a result, it
seems users mostly did not observe the problem (program/output corruption)
unless they were writing non-trivial functions and calling them with non-trivial
expressions.
Commit 8da53964edec8a665c3996d483df243eb150c2c4 compounded the above problem by
replacing the underlying allocator. While both before and after states use an
arena allocator,¹ the allocator after this change eagerly returns memory to the
backing system allocator (`malloc`) on `vmclear` while the allocator before this
change retained it within its own internal pool. The system allocator is used
much more pervasively in the Graphviz code base than the more tightly scoped
lib/vmalloc allocator, and it also typically does much more aggressive reuse of
recently-freed memory under the assumption that this is more likely to still be
cache-resident and thus faster to access. The net effect of this was that the
chance of the memory in question being reused and overwritten significantly
increased, making a number of latent cases of the problem described above now
user-visible.
The fix in this commit removes the freeing of expressions that are still
potentially in use. The contents of a subexpression in the above described
scenarios now remains intact up to the point it is accessed when evaluating its
parent containing expression.
The astute reader who has followed everything up to now may notice that the
subexpressions’ contents are actually maintained _beyond_ the point of
evaluation of the parent expression, and may be wondering, “didn’t you just turn
a use-after-free into a memory leak?” Unfortunately the answer is yes. However,
it is unclear how to determine when it is safe to free a subexpression without
introducing a more complex concept of call stacks and arbitrarily nested
expressions to lib/expr. Thus given the choice right now between use-after-free
or leaking memory, we are choosing to leak memory. Hopefully this can be
revisited in future.
cgraph: fix: handle allocation failures in 'agcanon' and friends
There are a number of cgraph API functions that use an internally managed buffer
to save the caller from having to allocate space themselves. Failure to expand
this buffer was being silently ignored, resulting in messy crashes when memory
was exhausted. These failures are now checked for and `agcanon` and `agcanonStr`
return `NULL` on allocation failure. `agwrite` returns `EOF` on allocation
failure.
This code was accepting large negative numbers and then converting them to
positive numbers that were applied as the line length limit. This seems clearly
unintended. This rephrasing now ignores any out of range value set for
`linelength`.
Windows: remove usage of 'sed' in setup-build-utilities script
This results in `$PATH` potentially having duplicated trailing entries, but this
causes no harm and preventing it does not seem worth a `sed` dependency.
Costa Shulyupin [Tue, 22 Feb 2022 18:58:05 +0000 (20:58 +0200)]
lib/cgraph: use return value of 'fread' in aglasterr
fix warning:
agerror.c: In function ‘aglasterr’:
agerror.c:49:5: warning: ignoring return value of ‘fread’, declared with attribute warn_unused_result [-Wunused-result]
49 | fread(buf, sizeof(char), len, agerrout);
This code was attempting to avoid dynamic string construction with low numbered
IDs. This is unnecessary when using a modern compiler that understands functions
like `snprintf` as built-ins and can achieve better optimization without this
trick.
Apart from de-duplicating code, this has the effect of making this code thread
safe where it was not before.
Magnus Jacobsson [Sun, 20 Feb 2022 10:10:04 +0000 (11:10 +0100)]
gvc++: include "AGraph.h" instead of <AGraph.h> in GVLayout.h
This makes it possible to use GVLayout.h from the install directory
without the need to specify both "include" and "include/graphviz" as
include directories to the compiler.
Magnus Jacobsson [Sun, 20 Feb 2022 10:08:52 +0000 (11:08 +0100)]
gvc++: include "gvc.h" instead of <gvc.h> in GVContext.h
This makes it possible to use GVContext.h from the install directory
without the need to specify both "include" and "include/graphviz" as
include directories to the compiler.
Towards https://gitlab.com/graphviz/graphviz/-/issues/2196.
Magnus Jacobsson [Sun, 20 Feb 2022 10:03:43 +0000 (11:03 +0100)]
cgraph++: include "cgraph.h" instead of <cgraph.h> in AGraph.h
This makes it possible to use AGraph.h from the install directory
without the need to specify both "include" and "include/graphviz" as
include directories to the compiler.
Towards https://gitlab.com/graphviz/graphviz/-/issues/2196.
Commit 83eea6075b8f4746d632a792b1d1b97c19797acb altered the calling convention
of `errorv`, but failed to take into account that the local variable `s` was
being used for something else prior to its use as the first unpacked parameter.
Following this commit, the first parameter was being discarded as `s` was
overwritten with `error_info.id` or `id`.
tclpkg vgpanecmd: remove acceptance of ambiguous abbreviated commands
`vgpanecmd` accepted abbreviations of commands, e.g. `li` for list. However
these abbreviations were accepted as first match with no disambiguation. So `r`
would always be interpreted as `rotate`, not `remove`. This behavior appears to
be an unintentional side effect of commands being incrementally introduced over
time without this kind of prefix-matching overlap being taken into account,
though this is mostly a guess as all this code was implemented prior to the
first Git commit.