C3 0.8.1 Raiding the stdlib for bugs
"Bug fixes" is probably the least exciting thing you can lead with, but after 0.8.0 settled things in the language, it was natural to spend 0.8.1 hunting bugs.
Quite a few were found: over 100 fixes landed — most being corner cases, but a few are real, including a textbook Zip-Slip vulnerability in the zip extractor(!).
Outside of compiler development, there's also been significant progress on the specification here, which is necessary for C3 to be able to sign off on 1.0 in two years.
Language changes¶
Access reflected fields using a.$field and a.$field = b.¶
Previously, you could mutate and access fields on a struct by using $field.get(struct_value) and $field.set(struct_value, new_field_value). This is replaced by a much more streamlined struct_value.$field and struct_value.$field = new_field_value with the old variants deprecated:
struct Foo
{
int i;
bool b;
}
fn void test()
{
var $field = $reflect(Foo.i);
Foo f = { 45, true };
// 0.8.0 prints 45
io::printn($field.get(f));
// 0.8.1 prints 45
io::printn(f.$field);
// 0.8.1 alternative syntax, also prints 45
io::printn(f.$eval($reflect(Foo.i)));
// 0.8.0 sets f.i to 44
$field.set(f, 44);
// 0.8.1 sets f.i to 44
f.$field = 44;
// 0.8.1 alternative syntax
f.$eval($reflect(Foo.i)) = 44;
}
Access to project path at compile time¶
env::PROJECT_PATH now returns the path to the project at compile time. This is mainly useful with $embed and similar compile time commands.
$vaarg[^1] supported¶
macro foo(...)
{
// 0.8.0
var x = $vaarg[$vaarg.len - 1];
// 0.8.1 – with ^ support
var x = $vaarg[^1];
}
Stdlib changes¶
Changes to API:¶
LinkedHashMaprenamedOrderedMap,LinkedHashSetrenamedOrderedSet. Old names are deprecated.- Enhanced
path::lsfunctionality: it now supports wildcard search. ini::parseand related take anerror_lineargument to identify the line with error.- Stricter JSON marshaling: it will return INVALID_NUMBER when encountering an inf or NaN for a float, and reject
1.literals. @loop_over_aiwould leak fds, deprecated and replaced by@loop_over_addresses.spawnnow allows binding I/O and using different settings per pipe.io::write_allnow retries on incomplete writes.FixedThreadPoolandThreadPoolare deprecated; they will be replaced by a new thread pool in a later 0.8.x version.
Additions to the stdlib¶
Concurrency & Threads¶
The BufferedChannel and UnbufferedChannel both gained non-blocking push/pop, using try_push and try_pop. In addition, UnboundedChannel was added. Such a channel will grow on demand, unlike BufferedChannel which has a fixed size.
This release also offers a preview of ThreadGroup for running tasks in parallel and collecting the results.
Platform support¶
libc::errno is now available on FreeBSD.
Compile time¶
The new values::expand macro turns strings containing expressions into values. This can be useful in some cases, as $reflect only creates statements.
Fixes¶
The 0.8.1 release contains over 100 fixes to stdlib, almost exclusively on the stdlib. The vast majority are stdlib corner cases.
The largest category is encoding and parsing: UTF-8/16/32 conversion edge cases (BOM handling, surrogate pairs, length detection), JSON's float precision and surrogate pair handling, varint signed-integer round-trips, base32/base64/codepage encoders that leaked memory on error, and PEM parsing on malformed input.
Memory management got a sweep. BackingArenaAllocator, DynamicArenaAllocator, OnStackAllocator, and the Vmem temp allocator each had at least one bug in a realloc, calloc, or destroy path. SortedMap had a possible array overflow. Deque.free didn't reset capacity, leaving the struct in a state that crashed on reuse. Wasm allocation could over-reserve unnecessarily.
Threading and synchronization got a check too: __atomic_compare_exchange had an incorrect implementation, lock_timeout on POSIX would sleep the full requested duration on every retry, the stack_size setting for thread creation was ignored on POSIX, and thread priority on Win32 was off by one. UnbufferedChannel could yield unpredictable values because its memory wasn't zeroed at creation.
Security: the Zip extraction code had a textbook Zip-Slip vulnerability — a malicious archive could write outside the target directory via .. components, absolute paths, or sibling-prefix paths in entry names. Fixed with up-front rejection of obviously-bad names plus a post-normalization containment check.
A handful of small correctness fixes touched types people use every day: DateTime.diff_years now handles leap years correctly, DString.replace handles empty needles and uninitialized strings without crashing, Formatter saturates instead of overflowing on absurd widths like %2147483648d, the URL parser stopped failing on user@host form and stopped silently dropping ports from bracketed IPv6 hosts, and process::run_capture_stdout no longer strips a character from output that doesn't end in \n.
Most of these are invisible if you've been using C3 successfully as they tighten the code paths nobody normally hits. But the stdlib now does the obviously right thing in considerably more corners than it did in 0.8.0.
Summarizing¶
To summarize: 0.8.1 is basically a stdlib release. Over 100 fixes — most are corner cases, a few are real (the Zip-Slip vulnerability in the zip extractor being the most notable). The user-visible additions worth a look: UnboundedChannel, try_push/try_pop on the existing channels, and a preview of ThreadGroup. The old FixedThreadPool and ThreadPool are deprecated; replacements are planned for later in 0.8.x.
So, same language as 0.8.0, just a bit sturdier library under it.
Thank yous¶
Again, this release wouldn't have been possible without the C3 community. I'd like to extend a deep thank you to all who have contributed, both through filed issues, PRs and just plain discussions.
PR contributors for this release¶
Stdlib: Alexandru Paniș, as2te, cmann1, Elusive239, Eugene Blikh, Fernando López Guevara, Johannes Müller, Kevin Hovsäter, Manu Linares, Maxine Bonnette and Zathyy
Compiler & toolchain: Akshat, Book-reader, Fernando López Guevara, Johannes Müller, Kevin Hovsäter, Manu Linares and Matthew Nagy
CI/Infrastructure: Fernando López Guevara, Manu Linares and plapinski
Change Log¶
Click for full change log
Changes / improvements¶
- Add
$$PROJECT_PATH, accessible throughenv::PROJECT_PATH. - Deprecate
$field.get(a)and$field.set(a, b). Replaced bya.$fieldanda.$field = b. - Add
a.$eval($field)as a variant ofa.$field. - Add JSON pretty print.
$$atomic_storeand$$atomic_loadtake an alignment parameter.$vaarg[^1]is supported. #3276- Improve error message when a keyword is used as a block parameter. #3275
- Correct tag method error messages from
tagof/has_tagoftoget_tagandhas_tag - Don't resume parsing when implicit module names yield invalid names.
Stdlib changes¶
- Add math::TAU / math::TWO_PI
- Add
values::expandto turn strings containing expressions into values. - Enhanced
path::lsfunctionality, like searching for wildcard. LinkedHashMaprenamedOrderedMap,LinkedHashSetrenamedOrderedSet. Old names are deprecated.- Added initial cpudetect on Linux / MacOS Aarch64.
- Enable libc::errno for FreeBSD.
- Checking filesize on Win32 now correctly reports errors. Getting the filesize now rejects directories.
ini::parseand related takes anerror_lineargument to identify the line with error.- JSON marshaling will return INVALID_NUMBER when encountering an inf or NaN for a float.
- JSON decoding will reject
1.literals. spawnnow allows binding I/O and using different settings per pipe.@loop_over_aiwould leak fds, deprecated and replaced by@loop_over_addresses.- Correctly return error on native_fwrite and native_fread.
- Prevent infinite spin on
io::read_fully,File.load_buffer,File.loadandFile.save. io::write_allnow retries on incomplete writes.GrowableBitSet.max_bit_setadded.- Added
UnboundedChannel. BufferedChannelandUnbufferedChannelget non-blocking push/pop.FixedThreadPoolandThreadPooldeprecated.
Fixes¶
@volatile_storeon arrays were sometimes incorrectly lowered.- NPOT vectors as associated variables were incorrectly lowered on load. #3228
.get_tagand.has_tagdid not work properly for globals and locals.- Vectors stored in unions lowered incorrectly causing an assert #3234
- Segmentation fault during library fetch when the "dependencies" key is missing in project.json. #3233
.tagswould crash if no attribute with arguments were present.Rect.merge_pointwould sometimes result in a point outside of the rect.- Possible array overflow in
SortedMap. - Possible memory overwrite in BackingArenaAllocator on realloc.
- Realloc could cause data corruption in DynamicArenaAllocator.
- OnStackAllocator would not correctly clear memory on calloc.
- Vmem temp allocator would not correctly free all vmem on destroy.
- Wasm memory allocation could overallocate unnecessarily.
- VirtualMemory contract off by one error.
- CPU detect of leaf7 on x86 incorrect.
- Fixed project benchmark target parsing. #3237
- Incorrect type on
UIntLEandUIntBE. - CVaList would behave different incorrectly for types larger than 8 bytes on some platforms.
- UTF32 BOM detection was broken.
- Sort from DString.less was inconsistent.
- Fix io::skip using 'read' vs 'read_byte', causing an error.
Slice2d.sliceincorrectly handled slices with x/y offset and 0/negative length together.String.to_integerincorrectly accepted some invalid characters for hex.- Removed broken
StringIterator.get. - Fix to refcount behaviour, preventing issue on release.
File.closeshould always invalidate the pointer on close, even on failures.- Overlong conversions to unicode for
%cat boundaries. - Do not rely on implicit allocation for getcwd.
- Skipping symlinks wasn't properly implemented for Win32.
- Reverse indexing a value that overloads indexing would index an anonymous copy of the value.
- Fix case where member.set would hit an assert.
- Same type casts would not become rvalues.
- Hex decoding would leak memory on failure.
Codepage.by_namewould not use normalized name.@return? bar!didn't work if the identifier matched a macro.- Copying compile time strings during compile time folding with strings containing 0 would sometimes get truncated. #3267
- Pem parsing did not correctly handle an empty body, nor when the first line was too short.
- Additional pem parsing bugs on malformed data handled.
- Compiler would crash when getting the
kind,qname, oralignmentof anuntypedlist. untypedlistincorrectly hadsizeproperty.- JSON handling of UTF16 surrogate pairs fixed.
base32,base64andcodepagewould leak memory on encode/decode errors.- Indexing into a type with a
$reflectvalue would sometimes cause a crash. - Using a faultdef hidden behind
@ifwould cause a crash. - Taking the type of a macro method would cause a crash.
- Cap array size to avoid overflow when making multidimensional arrays that are too large.
- DynamicArenaAllocator would incorrectly handle some reuse cases.
__atomic_compare_exchangehad an incorrect implementation.channel::create_unbufferedwould not correctly zero out memory, potentially yielding unpredictable results.lock_timeouton Posix would sleep the entire sleep before retrying, and it would fail if it managed to sleep.stack_sizesetting for threads was ignored on Posix.- Setting thread priority on Win32 was off by one.
- Non-power-of-two-sized member of @bigendian bitstruct backed by char array wasn't working #3283.
- Binary bitwise operations were not considered simple.
$expandwas incorrectly made generic in generic modules. #3274- Mangle lambdas in macros without
@to ensure they work correctly on elf #3217. DString.replace("", "X");would crash.DString.read_from_streamwould not return the correct length whenavailablewas not supported by the stream.@str_camelcasewould yield same result as@str_pascalcase. #3287conv::utf8to32would not zero terminate when the zero would be at the end of the buffer.char16_to_utf8_unsafewould not load low byte unaligned when required.- Not all invalid UTF8 was detected.
- UTF16 length detection was incorrect for utf16 with surrogate pairs.
- Initializing a variable which has the type of an optional struct using a const value would fail codegen. #3288
- Parsing a malformed hex float would not correctly get reported.
- Parsing an integer with trailing space would incorrectly be reported as an error.
String.escapeused the incorrect default for stripping quotes.- mem::equals would not correctly compare slices with element size > 1.
AsciiCharset.containsincorrectly handled char > 127.- Reuse of recently freed DynamicArenaAllocator allocations failed.
- Crash in codegen in some cases when RHS of a
&&or||was unreachable at lowering. - Visibility modifiers were incorrectly allowed on enum/constdef members.
- Datetime format could not handle negative offsets with non-zero minutes.
- NormalDist.random could occasionally return inf.
- Url parser would fail on
[email protected]. - Url parser would drop the port on
http://[::1]:8080. - Ipv6 classification - is_link_local etc, was incorrect
- env::get/set_var for Win32 would appear to fail when succeeding.
- env::get_var had a race condition on Win32.
- process::run_capture_stdout would remove the last character, even when it wasn't
\n. - Add missing
__powisf2to compiler_rt. //would count newlines twice when parsing JSONC.Path::for_posix(".a/..")was not parsed correctly.SortedMap.clearandSortedMap.freewould work incorrectly on map initialized with ONHEAP.GrowableBitSetwould yield the wrong length.GrowableBitSetwould not work correctly on backing types bigger than uint.DString.replacewould not work correctly in some cases.ByteWriter.ensure_capacitydid realloc unnecessarily when the data exactly matched capacity.DString.equalsusedintrather thanszfor len comparison.DString.replace_charwould crash on empty DString.io::read_varintandio::write_varint: handling for signed integers was broken.io::write_tiny_bytearrayandio::write_short_bytearraycould have incomplete writes.- Splatting a partially raw array into a macro would miscompile. #3302
- Getting the tag for an enum parameter caused a crash. #3307
- Json marshalling of floats would lose precision.
- Crash when initializing a bitstruct from an untyped list.
- Shifting a vector by a non-numeric type would cause a crash rather than a compiler error.
- Recursive macros were not detected when going by way of a lambda.
- Compile time concatenation with an empty slice was lacking checks, causing a compiler crash.
- Fix zip slip vulnerability.
- Fixed issues with
Object.to_value. DString.lenwas incorrectly marked@dynamic.- Qoi decoder wasn't correctly signaling all invalid data.
- Casting a constant string to a float vector was buggy, causing a compiler crash.
- Codepage detection could fail values after the last element.
- Xml parsing could leak memory if root was preceeded by Pi nodes.
DateTime.diff_yearswould not handle leap years properly.Deque.freewould not reset the capacity, making it break if later reused.Formatterwould overflow in cases like%2147483648d.- Distributions would drop convergence control setting on recursion.
- In some rare cases
available()could leave the stream in an unexpected state.
Want To Dive Into C3?¶
Check out the documentation or download it and try it out.
Have questions? Come and chat with us on Discord.
Discuss this article on Reddit.