Skip to content

Releases: marcobambini/gravity

Gravity v0.9.8

Choose a tag to compare

@marcobambini marcobambini released this 05 Aug 13:56

Security and memory-safety release. Most of the crashes below were reported by external researchers fuzzing the compiler and the bytecode loader, and each fix ships with a regression test.

Fixed

Bytecode loader (gravity -x / gravity_vm_loadbuffer)

These are reachable from attacker-controlled serialized bytecode, so they matter to any embedder that loads a .g file it did not produce itself.

  • NULL dereference in gravity_vm_loadbuffer — a function object without an identifier field, such as {"x":{"type":"function"}}, reached strlen(NULL) and killed the process. The loader now validates the shape of a JSON executable before trusting it: the root and every entry must be objects, the identifier must appear exactly once and be a string, and unknown object types are rejected. Malformed input becomes a load error instead of a crash (#444)
  • Signed 64-bit integer overflow in json_parse_ex — the integer and exponent accumulators multiplied by 10 per digit with no range check, so any literal past 19 significant digits overflowed. Signed overflow is undefined in C: the parser stored a wrapped value, and -fsanitize=undefined builds trapped with SIGILL. Both accumulators are range-checked now (#447)
  • Pointer-arithmetic overflow in the JSON scan loop — the scanner incremented its cursor unconditionally, so input ending while still inside a string or comment advanced the pointer past one-past-the-end, which is undefined behaviour. The loop now stops at the end of the buffer whatever state the scanner is in (#448)

Compiler

  • Heap out-of-bounds read in parse_number_expression — the 0x/0b/0o prefix check read value[1] without confirming the token had two bytes, so a file whose last token was a bare 0 read one byte past the buffer (#446)
  • Crash (SIGFPE) folding a floating-point remainder — the optimizer folded % by truncating both operands to int64_t, so any divisor with 0 < |divisor| < 1 became an integer division by zero and killed the compiler on input as small as 1 % 0.5. Float remainder is now folded with remainder(), matching the runtime, and mixed Int/Float remainders are left to the VM because REM dispatches on the class of the left operand. This also corrects a silent wrong answer: 5.5 % 2.0 folded to 1 where the VM evaluates -0.5 (#443)
  • Undefined behaviour in Int arithmetic — Gravity Ints wrap on overflow, but the wrap was done on signed operands in the VM fast path, in the operator_int_* methods and in the constant folder. New GRAVITY_INT_ADD/SUB/MUL/NEG/DIV/REM helpers do the arithmetic on the unsigned counterpart, so the values are unchanged but no longer undefined. They also cover GRAVITY_INT_MIN op -1, which on x86 faults in idiv rather than merely wrapping (#443)
  • Wrong line numbers on sources saved with CR+LF endingsis_newline() read PEEK_CURRENT to get the character following the one under examination, which only holds where the caller had already consumed it (the comment scanner and gravity_lexer_skip_line). In gravity_lexer_next the character was still the one at the current offset, so the CR of a CR+LF pair never saw the LF next to it: the pair counted as two line breaks and every row the compiler reported drifted by one per line read so far. An error on row 5 of a file written on Windows was reported on row 9. The lookahead is passed in explicitly now, and the string scanner keeps every byte of the terminator inside the token so that a literal spanning CR+LF lines is not shortened (#389, from the patch in #401)

Runtime

  • Heap buffer overflow in list_storeat — storing past the end of a list grows the backing array through marray_resize, which leaves the array untouched when the reallocation fails. The guard tested the pointer for NULL, but a failed realloc keeps the old, smaller, non-NULL buffer in place, so it never fired: the count was then set to the requested index and the fill loop wrote past the end of the allocation. The capacity actually obtained is checked instead, and the fill loop is bounded by it. Reachable from a script — x[4444444444444444444] = 0 is enough

Windows

  • The Windows code paths were guarded by WIN32, not _WIN32gravity_utils.h already selected windows.h and its DIRREF on _WIN32, while gravity_utils.c, gravity_opt_file.c and the CLI tested WIN32, which no compiler defines on its own. The Visual Studio projects define it in exactly two of their twelve configurations, so every x64 build compiled the POSIX bodies — opendir/readdir — against a DIRREF that is a HANDLE. MinGW and tcc land in the same place (#411)

Memory lifetime

  • Optional classes never releasedMath, File, JSON and ENV were leaked by every embedder that created and destroyed a VM, because gravity_core_free dropped a reference without the matching balance (#442)
  • Core reference leaked by every gravity_compiler_run — the compiler took a reference to the core classes on each run and never gave it back, so the count never reached zero and the core was never torn down (#442)
  • Double free of the inline source buffergravity -i handed its heap-allocated wrapper source to the compiler with is_static false, which passes ownership to the lexer; the lexer freed it and the CLI freed the same pointer again on the way out, aborting every inline run under a hardened allocator

Added

  • make staticlib builds libgravity.a from the same objects as make lib, so the archive carries the library without the CLI entry point (#427)
  • test/loadbuffer/ — malformed JSON executables that must each be rejected as a load error without crashing, plus json_bounds.c (make jsontest), 60 checks driving the JSON scanner directly. Run with test/loadbuffer/run_all.sh
  • test/unittest/bugfix_crlf_lineno.gravity — a source stored with CR+LF endings on purpose, asserting the row and column the compiler reports
  • A GitHub Actions workflow building with gcc and clang on Linux and macOS, plus a job built with -fsanitize=address,undefined that runs the unit tests, the fuzzing corpus and the loader tests through it

Changed

  • The usage text now prints the real default output name, gravity.g; README.md and CLAUDE.md documented a stale gravity.json
  • report_error carries the printf format attribute on gcc and clang in all three of its forms, so format/argument mismatches now fail the build rather than needing an external analyser. This replaces the CodeQL workflow, which still ran on github/codeql-action@v1, deprecated since January 2023, and reported green without being a current analysis. Its one open finding — a size_t passed to %d in gravity_codegen.c — is fixed

Verification

353/353 unit tests and 12/12 loader tests pass, and the unit tests plus the 742-input fuzzing corpus run clean under -fsanitize=address,undefined.

Thanks

To the reporters of #442, #443, #444, #446, #447 and #448 for the detailed write-ups and minimized reproducers, to @bardo84 for #389, and to @mwasplund (#401), @jockm (#427) and @tDwtp (#411) for the patches this release builds on.

Full Changelog: 0.9.7...0.9.8

Gravity v0.9.7

Choose a tag to compare

@marcobambini marcobambini released this 14 Apr 13:04

Full Changelog: 0.9.6...0.9.7

Gravity v0.9.6

Choose a tag to compare

@marcobambini marcobambini released this 14 Apr 12:06

What's Changed

  • fix(core): move gravity_opt_free after refcount check to prevent double-free by @barchett in #436

New Contributors

Full Changelog: v0.9.5...0.9.6

Gravity v0.9.5

Choose a tag to compare

@marcobambini marcobambini released this 23 Feb 12:25

Gravity v0.9.5

Extensive bug fixes, memory safety improvements, and new documentation.

Compiler

  • Fix off-by-one in lexer PEEK_NEXT/PEEK_NEXT2 boundary checks
  • Fix memory leaks in parser (parse_file_expression, parse_variable_declaration, string interpolation)
  • Fix wrong constant in semacheck2 upvalue limit error message
  • Fix wrong variable name in for-loop register allocation in codegen
  • Fix multiple early-return paths in visit_postfix_expr leaking arrays (replaced with goto cleanup)
  • Fix register clobbering in LOCATION_CLASS_IVAR_OUTER assignment
  • Fix constant folding and NEG optimization checking/writing wrong registers
  • Fix OPCODE_SET_FOUR8bit macro missing opcode field
  • Convert IR register allocation from bool[256] to compact 32-byte bitmask
  • Remove unnecessary NOP emission for empty statements

Runtime / VM

  • Fix boolean fast-path comparison incorrectly applied to ordered comparisons (LT, GT, LEQ, GEQ) — now restricted to EQ/NEQ only
  • Fix crash in gravity_vm_setslot/gravity_vm_getslot when no frames exist
  • Fix list_iterator_next out-of-bounds read
  • Fix range_contains not handling reversed ranges
  • Fix function_exec default-arg filling using wrong index formula
  • Fix int_random truncation from gravity_int_t to int
  • Fix string_count broken partial-match logic (rewrite using string_strnstr)
  • Fix string_upper/string_lower off-by-one (iterated one byte past end)
  • Fix string_loadat UTF-8 corruption when reversing multi-byte sequences
  • Fix string_loop iterating byte-by-byte instead of by UTF-8 character
  • Fix string_iterator/string_iterator_next missing bounds checks
  • Fix fiber_abort wrong nargs check
  • Fix system_input blindly stripping last char instead of checking for newline
  • Fix convert_map2string/convert_list2string losing pointer on realloc failure
  • Fix gravity_class_grow discarding existing ivar values when growing

Optional Modules

  • Math: fix atan2f for float builds; use POW macro consistently; fix division-by-zero in logx when base=1; fix integer overflow in random; fix pointer bugs in round
  • File: fix memory leaks in buildpath and directory scan; fix wrong nargs in open; fix leaked FILE* on failure; fix crash on negative read size; fix swapped fread/fwrite arguments
  • JSON: rewrite string escaping to properly handle backslashes, quotes, and control characters
  • ENV: add Windows compatibility for environ access

Shared / Data Structures

  • Fix marray_push updating capacity before confirming realloc success
  • Fix marray_resize/marray_resize0 to check realloc result
  • Fix gravity_hash_memsize counting buckets as nodes
  • Improve float hashing precision (%f → %.17g)
  • Add overflow guard to gravity_function_cpool_add

Utilities

  • Fix buffer overflow in debug DUMP_VM macros
  • Fix memory leak in gravity_disassemble
  • Fix escape buffer too small in JSON serializer
  • Fix swapped PathCombineA arguments on Windows
  • Fix file_name_frompath returning NULL when no separator
  • Add NULL check in string_dup
  • Fix signed arithmetic in number_from_bin

CLI

  • Fix memory leaks in unittest_scan and inline execution

Documentation

  • Add comprehensive ARCHITECTURE.md covering the full compilation pipeline, VM internals, value system, garbage collector, instruction set, and embedding API
  • Update README.md with Building, Usage, and Project Structure sections; update line counts; enhance Features list

Tests

  • Add 30 new unit tests covering all major bug fixes

Official 0.9.0 release

Choose a tag to compare

@marcobambini marcobambini released this 24 Jan 08:29
  • Several leaks and bugs fixed
  • New unit tests added

Official 0.8.5 release

Choose a tag to compare

@marcobambini marcobambini released this 22 Jul 08:38
Fixed a setter issue that affects some unit tests too

Official 0.8.3 release

Choose a tag to compare

@marcobambini marcobambini released this 28 Jun 19:00
Fixed an issue due to the introduction of lazy loading a superclass f…

Official 0.8.2 release

Choose a tag to compare

@marcobambini marcobambini released this 05 Jun 12:41
Added a way to lazy loading extern superclass at runtime

Official 0.8.1 release

Choose a tag to compare

@marcobambini marcobambini released this 16 Nov 08:58
Minor typo fixed

Official 0.8.0 release

Choose a tag to compare

@marcobambini marcobambini released this 09 Nov 07:59
Improved error handling and detection