What's New

Gravity v0.9.8

2026-08-05T13:56:29Z

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 Programming Language

Gravity is a powerful, dynamically typed, lightweight, embeddable programming language written in C without any external dependencies (except for stdlib). It is a class-based concurrent scripting language with modern Swift-like syntax.

Gravity supports procedural programming, object-oriented programming, functional programming, and data-driven programming. Thanks to special built-in methods, it can also be used as a prototype-based programming language.

Gravity has been developed from scratch for the Creo project in order to offer an easy way to write portable code for the iOS and Android platforms. It is written in portable C code that can be compiled on any platform using a C99 compiler. The VM code is about 6.5K lines long, the multipass compiler code is about 10K lines and the shared code is about 4.7K lines long. The compiler and virtual machine combined add less than 200KB to the executable on a 64-bit system.

What Gravity code looks like

class Vector {
	// instance variables
	var x = 0;
	var y = 0;
	var z = 0;

	// constructor
	func init (a = 0, b = 0, c = 0) {
		x = a; y = b; z = c;
	}

	// instance method (built-in operator overriding)
	func + (v) {
		if (v is Int) return Vector(x+v, y+v, z+v);
		else if (v is Vector) return Vector(x+v.x, y+v.y, z+v.z);
		return null;
	}

	// instance method (built-in String conversion overriding)
	func String() {
	        // string interpolation support
		return "[\(x),\(y),\(z)]";
	}
}

func main() {
	// initialize a new vector object
	var v1 = Vector(1,2,3);
	
	// initialize a new vector object
	var v2 = Vector(4,5,6);
	
	// call + function in the vector object
	var v3 = v1 + v2;
	
	// returns string "[1,2,3] + [4,5,6] = [5,7,9]"
    	return "\(v1) + \(v2) = \(v3)";
 }

Features

  • multipass compiler with optimizer
  • dynamic typing
  • classes and inheritance
  • higher-order functions and classes
  • lexical scoping
  • coroutines (via fibers)
  • nested classes
  • closures
  • garbage collection (mark-and-sweep)
  • operator overriding
  • string interpolation
  • enums, modules, and structs (value types)
  • switch/case and ranges
  • optional modules (Math, File, JSON, ENV)
  • powerful embedding API with bridging support
  • built-in unit tests
  • built-in JSON serializer/deserializer
  • optional semicolons

Building

Make (Linux / macOS / BSD)

make                    # Build the gravity CLI executable
make mode=debug         # Debug build with symbols
make lib                # Build shared library (libgravity.dylib/so/dll)
make staticlib          # Build static library (libgravity.a)
make example            # Build the C embedding API example
make clean              # Clean all build artifacts

CMake (cross-platform, including Windows)

cmake -B build
cmake --build build
# Optionally disable the CLI and build the library only:
cmake -B build -DBUILD_CLI=OFF
cmake --build build

Requires a C99 compiler. No external dependencies.

Usage

./gravity file.gravity                  # Compile and execute a source file
./gravity -c file.gravity               # Compile to bytecode (outputs gravity.g)
./gravity -o out.json -c file.gravity   # Compile to a specific output file
./gravity -x gravity.g                  # Execute precompiled bytecode
./gravity -i 'return 2 + 3'             # Execute inline code
./gravity -t test/unittest              # Run unit tests

Testing

./gravity -t test/unittest              # Run all unit tests via the VM
./test/unittest/run_all.sh              # Run all unit tests via shell script (with per-test timeouts)
./gravity test/unittest/somefile.gravity # Run a single test file

The test/ directory also contains fuzzy/ (randomised fuzzing inputs) and infiniteloop/ (tests that must terminate with a runtime error rather than hang).

Project Structure

src/
├── cli/            Command-line interface
├── compiler/       Lexer, parser, AST, semantic analysis, IR, optimizer, codegen
├── runtime/        Stack-based VM, built-in types and core methods
├── shared/         Value representation, opcodes, hash table, array, memory/GC
├── optionals/      Optional modules: Math, File, JSON, ENV
└── utils/          Debug disassembler, JSON serialization, file I/O, UTF-8

For a comprehensive technical deep-dive into the implementation, see ARCHITECTURE.md.

Embedding API

Gravity is designed to be embedded inside a host application. The complete API lives in src/runtime/gravity_vm.h and src/compiler/gravity_compiler.h. A minimal example:

#include "gravity_compiler.h"
#include "gravity_core.h"
#include "gravity_vm.h"

static void report_error(gravity_vm *vm, error_type_t type,
                         const char *description, error_desc_t desc, void *xdata) {
    printf("%s\n", description);
}

int main(void) {
    const char *source = "func main() { return 6 * 7; }";

    gravity_delegate_t delegate = {.error_callback = report_error};

    // compile
    gravity_compiler_t *compiler = gravity_compiler_create(&delegate);
    gravity_closure_t *closure   = gravity_compiler_run(compiler, source, strlen(source), 0, true, true);

    // create VM and transfer compiler-owned objects into it
    gravity_vm *vm = gravity_vm_new(&delegate);
    gravity_compiler_transfer(compiler, vm);
    gravity_compiler_free(compiler);

    // execute and read result
    if (gravity_vm_runmain(vm, closure)) {
        gravity_value_t result = gravity_vm_result(vm);
        gravity_value_dump(vm, result, NULL, 0);  // prints: 42
    }

    gravity_vm_free(vm);
    gravity_core_free();
    return 0;
}

See examples/example.c and the embedding documentation for the full bridging API.

Special thanks

Gravity was supported by a couple of open-source projects. The inspiration for closures comes from the elegant Lua programming language; specifically from the document Closures in Lua. For fibers, upvalues handling and some parts of the garbage collector, my gratitude goes to Bob Nystrom and his excellent Wren programming language. A very special thanks should also go to my friend Andrea Donetti who helped me debugging and testing various aspects of the language.

Documentation

The Getting Started page is a guide for downloading and compiling the language. There is also a more extensive language documentation. Official wiki is used to collect related projects and tools. For implementation internals, see the Architecture Document.

Where Gravity is used

Changelog

See CHANGELOG.md for a summary of changes across versions.

Community

GitHub Discussions

Questions, ideas, and general discussion are welcome in GitHub Discussions.

Contributing

Contributions to Gravity are welcomed and encouraged!
More information is available in the official CONTRIBUTING file.

License

Gravity is available under the permissive MIT license.

Description

  • Swift Tools
View More Packages from this Author

Dependencies

  • None
Last updated: Mon Sep 07 2026 02:02:02 GMT-0900 (Hawaii-Aleutian Daylight Time)