Skip to content

Effects

Onion tracks what a call can do to the world outside the program. Every method has an effect set, computed without running anything, and --effects prints it:

$ onion --effects report.on
Report#readCfg(java.lang.String): read
Report#main(java.lang.String[]): console,read

This page is the reference for the vocabulary, where the facts come from, and exactly what the analysis does and does not promise. The capability layer built on top of it (tool declarations, requires, --plan) is described in the guide once it lands; this data layer shipped first.

The vocabulary

An effect names one way of touching the outside world. A call has a set of them; the empty set prints as pure.

read is a filesystem read and write a filesystem write — an operation that does both, such as Files::copy, carries both. net is network traffic in either direction. exec covers starting, replacing or terminating a process, which is why System::exit carries it as well as Proc::run. env is a read of the process environment: environment variables, system properties, the working directory, JVM statistics. clock covers both reading the clock and blocking on it, so Timing::sleep and DateTime::now share it. rand is a draw from a random source, and console is any use of the attached stdin, stdout or stderr.

The ninth member is different in kind: unknown means the table cannot vouch for this call. An unlisted Java method is not known to be pure, and pretending otherwise would make every guarantee built on this vocabulary a lie. unknown is therefore an effect that propagates like any other, and the capability layer treats it as "needs an explicit allow", never as "harmless".

Where the facts come from

Effects cannot ride on annotations in this compiler — Java methods reach the type checker without theirs, nothing is emitted to bytecode, and the optimizer rebuilds methods without them — so the facts live out of band, in a resource shipped with the compiler (onion/effect-table.txt). Each line classifies one method, or a whole class via a wildcard, and a specific entry beats its class's wildcard, which is how a mostly-pure class declares its exceptions:

onion.DateTime#*=pure
onion.DateTime#now=clock
onion.Files#copy=read,write
onion.Cli#parse=console,exec

The table ships with the whole effectful surface of the standard library classified — Files, FileResource, Proc, Http, HttpResource, Config, IO, Cli, DateTime, Timing, Rand, OnionMath — plus the pure remainder of the stdlib, a pure baseline for common JDK value and collection types, and the JDK's well-known effect points (System::getenv, System::exit, Runtime::exec, Thread::sleep, PrintStream as the type of System::out).

Granularity is per-type, not per-instance. A PrintStream over a file would be a write, but in Onion code a PrintStream is System::out or System::err, so the type is classified console. When that distinction matters, the stdlib's own entry points (Files, IO) are the precise path.

How a method's set is computed

For a method defined in your program, the compiler walks the typed body and takes the union over every call in it: the table's verdict for external calls, and the callee's own inferred set for calls to other methods in the program, joined transitively. Recursion and mutual recursion converge by fixed point. Constructors participate the same way — new Loud() carries whatever Loud's constructor does, including its superclass chain.

Two rules are deliberate over-approximations, chosen to be sound for boundary checking rather than maximally precise. A closure's body is charged to the method that creates the closure, not the one that eventually invokes it — invoking a function value is pure in the table — because the creation site is the one the checker can always see. And any call that neither the table nor the compiled sources can account for contributes unknown rather than being dropped.

The result is an over-approximation in the other direction too: the analysis reports what a body can do, not what it will do on a given run. A branch that never executes still contributes its effects. That is the correct direction for the question the vocabulary exists to answer — "what do I authorize when I run this?" — where missing an effect would be a hole and listing an extra one is merely conservative.

What this does not promise

Reflection, ClassLoader tricks and native code are outside the model; they surface as unknown at the call that reaches them, not as clairvoyance about what they do. The table classifies the stdlib as shipped — a user-supplied class named onion.Files is not consulted against its source. And an effect set is not a security sandbox: nothing stops a program from running; the set is the honest input to the layers that decide whether it should.