Precompilation can also run arbitrary code even if it’s not cacheable, not nearly as restricted as typical AOT compile-time computation.
Not generally feasible to send all the information about the current process over to another one, and the new process can be hacked anyway. Precompilation does this, but only by reusing identifiable caches.
I was under the impression the compiler backend couldn’t be swapped out without lower-level injection, but not sure about the frontend. I too would like to know if it’s possible for the default parser+compiler to be ruined at runtime.
That wouldn’t actually be unusual because we can already break the runtime with very little type piracy. Julia can be written in Julia very deep down, and there’s very rarely any deterrence of new world ages. For comparison, I can straightforwardly redefine Float64 addition, changing backspacing in an empty REPL line as a side effect:
julia> Base.:+(x::Float64, y::Float64) = (println("hi"); Base.add_float(x, y))
julia> 1.2 + 3.4
hi
4.6
julia> hi
julia> hi
but I run into a frozen class feature when trying a similar thing in Python:
>>> def blah(self, x): pass # not going to bother with a hack
...
>>> float.__add__ = blah
Traceback (most recent call last):
File "<python-input-3>", line 1, in <module>
float.__add__ = blah
^^^^^^^^^^^^^
TypeError: cannot set '__add__' attribute of immutable type 'float'
A higher reliance on compiled C also protect lower-level implementations. Still, any dynamic language implementing itself on any level is vulnerable to really bad monkey patching:
>>> import builtins
>>> oldabs = builtins.abs
>>> def newabs(x): return -oldabs(x)
...
>>> builtins.abs = newabs
>>> abs(1.2), abs(-3.4)
(-1.2, -3.4)
In conclusion, there’s a lot more to worry about than the parser and compiler, and executing malware is going to hurt regardless.