Skip to content
Merged
28 changes: 28 additions & 0 deletions .github/workflows/ci-dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ on:
pull_request:
branches:
- main
# Lets the -memcheck step (see the build job below) run on demand,
# from the Actions tab or `gh workflow run`, without adding its own
# several-minutes-plus cost to every ordinary push/PR.
workflow_dispatch: {}

jobs:
build:
Expand Down Expand Up @@ -142,6 +146,30 @@ jobs:
export FUN_STDLIB_DIR="$PWD/stdlib"
mkdir -p coverage
./fun-out/bin/fun test . -cover -cover-min 80 -cover-exclude examples,scripts,editors,src/cli/main.fn,src/fls/main.fn,src/fls/server.fn,src/cli/coverage_samples,stdlib/std/sync_backend_windows.fn -cover-report lcov=coverage/lcov.info,cobertura=coverage/cobertura.xml,json=coverage/coverage.json,html=coverage/index.html,text=coverage/summary.txt | tee coverage/test-output.txt
# Every allocation across the whole suite redirected through the
# built-in memory checker (see stdlib/std/memcheck.fn): a
# double-free, use-after-free, or buffer overflow anywhere in the
# compiler, stdlib, or their own tests fails this step (leak
# reporting stays off, see Codegen.memcheck_leaks's own note - a
# test's own process is short-lived by design, so "still
# allocated when it exits a moment later anyway" isn't a bug).
# One platform only, same reasoning as coverage just above: a
# double-free/UAF/overflow is a real bug regardless of which
# platform's build happens to hit it, so the other two lanes
# would only pay the extra time for no extra coverage. Manual-
# trigger only (`workflow_dispatch`, the GitHub Actions
# equivalent of GitLab's manual job): the guard-page allocator
# is markedly slower than a real malloc (every allocation costs
# a real mmap/mprotect syscall pair), enough that this step alone
# can run well past 30 minutes under load - too costly to add to
# every ordinary push/PR, but still there on demand from the
# Actions tab or `gh workflow run ci-dev.yml`.
- name: Run the test suite on ${{ matrix.lane }} under -memcheck
if: matrix.os == 'ubuntu-24.04' && github.event_name == 'workflow_dispatch'
timeout-minutes: 60
run: |
export FUN_STDLIB_DIR="$PWD/stdlib"
./fun-out/bin/fun test . -memcheck
- name: Summarize coverage
if: always() && matrix.os == 'ubuntu-24.04'
run: |
Expand Down
16 changes: 16 additions & 0 deletions docs/language.md
Original file line number Diff line number Diff line change
Expand Up @@ -1076,6 +1076,22 @@ fun main() {
}
```

### Ternary Conditional

`cond ? then : else` is a conditional expression, not a statement - it
produces a value, for use anywhere an expression can go:

```fun
fun main() {
num x = 5;
str size = x > 10 ? "big" : x > 0 ? "small" : "non-positive";
}
```

Both branches must share a type (the same rule an array literal's
elements already follow); the condition must be a `flag`. `?`/`:` chain
right-associatively, so `a ? b : c ? d : e` reads as `a ? b : (c ? d : e)`.

### For

- Array: `for item : arr { ... }`
Expand Down
50 changes: 49 additions & 1 deletion docs/tooling.md
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,53 @@ test:
path: cobertura.xml
```

## Memory Checker

`-memcheck` redirects every `malloc`/`calloc`/`realloc`/`free` in the
compiled program through a built-in, native-Fun checker, catching leaks,
double-frees, use-after-free, and buffer overflows, with no external
sanitizer and no change to your own source:

```text
fun -in file.fn -memcheck
fun test [<path>] -memcheck
```

- Each allocation gets its own guarded page(s): freeing one revokes
access to the whole span forever rather than returning it to the
system, so a later read or write through it faults immediately and
deterministically, naming the real `.fn` file and line (both where it
was allocated and, for a double-free or use-after-free, where it was
first freed):

```text
[memcheck] use-after-free: allocation #0 (16 bytes), allocated at main.fn:5, freed at main.fn:8
[memcheck] double-free: allocation #0, 16 bytes, allocated at main.fn:5, already freed at main.fn:6, double-freed at main.fn:7
[memcheck] buffer overflow: past the end of allocation #0 (16 bytes), allocated at main.fn:5
```

A fault or a double-free aborts the process immediately (a nonzero,
CI-visible exit).
- Leak reporting (every allocation still live when the process exits) is
**off by default**, including under `-test`: a short-lived process -
every CLI invocation this compiler itself makes, and every `-test`
binary - never has to free a transient allocation before it exits a
moment later anyway, so treating that as a leak would flag ordinary,
harmless code. Pass `-memcheck-leaks` alongside `-memcheck` to turn
it back on for a program that's meant to run long enough that "still
allocated when it finally exits" is a real bug (a long-running server
or daemon):

```text
fun -in server.fn -memcheck -memcheck-leaks
```

```text
[memcheck] leak: allocation #0, 16 bytes, allocated at main.fn:5, never freed
```
- Works with any C compiler this project already supports (`clang`,
`gcc`, `cl.exe`), on Windows, macOS and Linux.

## Fuzzing

```fun
Expand Down Expand Up @@ -393,11 +440,12 @@ plain text in the GitHub UI.
## CLI

```text
fun -in <input_file> [-out <output_file>] [-no-exec] [-outf] [-ast] [-g] [-warn-unused] [-warn-unused-lenient] [-D name=value] [-test] [-fuzz] [-fuzz-target <name>] [-help] [-version] [-- <program args>]
fun -in <input_file> [-out <output_file>] [-no-exec] [-outf] [-ast] [-g] [-warn-unused] [-warn-unused-lenient] [-D name=value] [-test] [-fuzz] [-fuzz-target <name>] [-memcheck] [-memcheck-leaks] [-help] [-version] [-- <program args>]
fun test <input_file> (shorthand for `fun -in <input_file> -test`)
fun test [<dir>] (runs every `test` block under <dir>, default '.'; aggregate summary)
fun test [<path>] -cover [-cover-report <format[=path],...>] [-cover-min <pct>] [-cover-exclude <path,...>]
(measures code coverage, see Code Coverage)
fun test [<path>] -memcheck (runs under the memory checker, see Memory Checker)
fun fuzz <input_file> [<target>] (shorthand for `fun -in <input_file> -fuzz [-fuzz-target <target>]`)
fun fuzz [<dir>] (runs every `fuzz` target under <dir> for FUN_FUZZ_DEFAULT_SECONDS each, default '.'/30s)
fun lint [<path>] [-summary] (reports every warning under <path>, a file or directory, default '.')
Expand Down
Loading
Loading