Stream: helpdesk (published)

Topic: Sandbox Julia?


view this post on Zulip Colin Caine (Feb 27 2021 at 19:40):

I'd like to be able to have an environment where only symbols I specify are defined.

This is for an educational purpose where I want to define
E.g. an environment with only a nand(a, b) function and then the user is expected to define a bunch of combinatorial logic from that.

view this post on Zulip Mason Protter (Feb 27 2021 at 19:44):

Have you looked into baremodule?

view this post on Zulip Colin Caine (Feb 27 2021 at 19:44):

Yeah, but it still imports too much.

view this post on Zulip Mason Protter (Feb 27 2021 at 19:45):

How is the user meant to interact with Julia?

view this post on Zulip Mason Protter (Feb 27 2021 at 19:45):

It sounds like maybe you just should make a little DSL

view this post on Zulip Colin Caine (Feb 27 2021 at 19:47):

The idea was to just get Julia semantics but with a clean slate that doesn't contain any of Core or Base.

Behind the scenes, they're still there, but the user can only interact with the names I give them or that they define themselves.

view this post on Zulip Colin Caine (Feb 27 2021 at 19:48):

Maybe I can just build some machinery that tracks what symbols are in use and an eval that errors if you use any "undefined" symbol.

view this post on Zulip Mason Protter (Feb 27 2021 at 19:48):

Again, how is the user meant to interact? repl? Some input box on a website?

view this post on Zulip Mason Protter (Feb 27 2021 at 19:48):

Colin Caine said:

Maybe I can just build some machinery that tracks what symbols are in use and an eval that errors if you use any "undefined" symbol.

yeah, that's what I meant by a "little dsl"

view this post on Zulip Colin Caine (Feb 27 2021 at 19:50):

As for input, files and a REPL.

view this post on Zulip Mason Protter (Feb 27 2021 at 20:00):

What names are in a baremodule that you don't want? I think you might be able to get away with just using a baremodule and then having a blacklist for the remaining names, e.g. Core and eval

view this post on Zulip Colin Caine (Feb 27 2021 at 20:23):

I think I'd just want to expose nand(a, b), true and false. And all of the string and number literals would be absent.

Small languages are used a lot in some programming courses (e.g. Beginner's Student Language defined in racket), though I'm not sure one as basic as I'm suggesting would actually be helpful :)

view this post on Zulip Colin Caine (Feb 27 2021 at 20:25):

Anyway, this doesn't seem super hard, maybe I'll just build something and see if it's easy and useful.

view this post on Zulip Mason Protter (Feb 27 2021 at 20:44):

Okay, here's a demo with ReplMaker.jl that I think works:

using ReplMaker, MLStyle

baremodule Sandbox end
@eval Sandbox nand(x, y) = $(!)($(&)(x, y))

function sandbox_parser(s::String)
    ex = Meta.parse(s)
    filterer(ex) = @match ex begin
        :Core => println("that's not allowed")
        :eval => println("that's not allowed")
        x::Bool => x
        ::Number => println("that's not allowed")
        ::String => println("that's not allowed")
        ::AbstractArray => println("that's not allowed")
        x::Expr => Expr(x.head, filterer.(x.args)...)
        x => x
    end
    :(@eval Sandbox $(filterer(ex)))
end


function valid_julia(s)
    input = String(take!(copy(ReplMaker.LineEdit.buffer(s))))
    ex = Meta.parse(input)
    if ex isa Expr && ex.head == :incomplete
        false
    else
        true
    end
end

sandbox_mode = initrepl(sandbox_parser;
                        prompt_text="Sandbox> ",
                        prompt_color = :yellow,
                        startup_text = false,
                        mode_name = :sandbox,
                        valid_input_checker = valid_julia)

enter_mode!(sandbox_mode)

view this post on Zulip Mason Protter (Feb 27 2021 at 20:47):

if you run that, you'll find yourself in the sandbox> prompt and then you can run various stuff:

Sandbox> t = true; f = false;

Sandbox> nand(t,t)
false

Sandbox> nand(f,f)
true

Sandbox> not(x) = nand(x, true)
not (generic function with 1 method)

Sandbox> and(x, y) = not(nand(x, y))
and (generic function with 1 method)

Sandbox> and(t, t)
true

Sandbox> 1
that's not allowed

view this post on Zulip Colin Caine (Feb 27 2021 at 20:51):

That's very neat. I was thinking I would have to do some name mangling as well as pattern matching

view this post on Zulip Mason Protter (Feb 27 2021 at 20:53):

One potential issue is that they can always just hit the backspace button to exit the prompt and then do whatever they want

view this post on Zulip Mason Protter (Feb 27 2021 at 20:54):

but I think you could delve into sandbox_mode.keymap_dict and potentially remove that ability? I'm not sure.

view this post on Zulip Mason Protter (Feb 27 2021 at 20:55):

Maybe safer would be to just make your own barebones repl that they can't escape from?

view this post on Zulip Mason Protter (Feb 27 2021 at 20:56):

https://github.com/hanslub42/rlwrap will make it somewhat easy to make your own repl that supports things like history and such

view this post on Zulip Colin Caine (Feb 27 2021 at 20:58):

Yeah, there's a few other things, like you can still import whatever modules you like, and there's some more tricky security things if I wanted to make a more secure sandbox.

But this is a very nice start, thank you :)

view this post on Zulip Mason Protter (Feb 27 2021 at 20:59):

Mhm. You'd want to blacklist using and import for sure.

view this post on Zulip Mason Protter (Feb 27 2021 at 21:00):

I wonder if there's a way to remove all the names from Core, but still allow them to be defined, just not used.

view this post on Zulip Mason Protter (Feb 27 2021 at 21:12):

I opened a discourse thread asking if anyone knows a way. https://discourse.julialang.org/t/even-more-bare-baremodule/56156

view this post on Zulip Mason Protter (Feb 27 2021 at 21:13):

An alternative to a real module would be to repurpose some of the code from StaticModules.jl to make your own custom 'module' that does exactly what you want

view this post on Zulip Takafumi Arakaki (tkf) (Feb 27 2021 at 22:16):

I guess you'd also need to exclude Main, as otherwise, I can call, e.g., Main.Base.eval.

view this post on Zulip Takafumi Arakaki (tkf) (Feb 27 2021 at 22:16):

As a related note, I'm somewhat surprised that names(Sandbox; all = true, imported = true) does not output anything.

view this post on Zulip Takafumi Arakaki (tkf) (Feb 27 2021 at 22:21):

Oh, it also looks like you can ccall inside a baremodule (since it's a syntax).

view this post on Zulip Colin Caine (Feb 27 2021 at 22:22):

names(Sandbox) does give output for me on Julia 1.5

view this post on Zulip Takafumi Arakaki (tkf) (Feb 27 2021 at 22:25):

Ah, I wasn't accurate. I meant to say an array with more than one element; e.g., I was hoping to see Array etc.

view this post on Zulip Colin Caine (Feb 27 2021 at 22:26):

At least a couple of months ago there were some quite big differences between the output of Meta.parse on 1.5 and 1.6 (with 1.6 giving lowered IR for some things). I wonder if that was ever cleared up.

view this post on Zulip Colin Caine (Feb 27 2021 at 22:26):

@Takafumi Arakaki (tkf) ah, gotcha.

view this post on Zulip Colin Caine (Feb 27 2021 at 23:05):

Expanded on @Mason Protter's example :)

using ReplMaker, MLStyle

baremodule Sandbox end
@eval Sandbox nand(x, y) = $(!)($(&)(x, y))

function eval_in_sandbox(ex)
    forbidden_names = setdiff(names(Core; all=true, imported=true), names(Sandbox; all=true, imported=true))
    filterer(ex) = @match ex begin
        x::Bool => x

        x::LineNumberNode => x

        # Ban everything.
        # Copy anything you want to allow to above this point.

        # it's a bit complicated to work out when a symbol is being used as a
        # reference and what scope it is in. I think the JuliaVariables people
        # have maybe done something with that?
        s::Symbol => s in forbidden_names ? throw("Sorry! You can't use that name in the sandbox") : s

        # Not allowed to import stuff
        Expr(:using, _...) ||
        Expr(:import, _...) => error("Imports are not permitted in the sandbox")

        # Julia literals, these are mostly or entirely harmless (I think)
        # but you might want to omit them or rewrite them to something else or whatever.
        ::Bool ||
        ::Number ||
        ::String ||
        Expr(:string, _...) || # string interpolation
        ::QuoteNode ||
        Expr(:quote, _...) ||
        Expr(:ref, _...) || # a[i], but also Int[]
        Expr(:typed_vcat, _...) ||
        Expr(:typed_hcat, _...) ||
        Expr(:vect, _...) ||
        Expr(:vcat, _...) ||
        Expr(:hcat, _...) ||
        Expr(:tuple, _...) || # also covers named tuples
        Expr(:comprehension, _...) ||
        Expr(:typed_comprehension, _...) ||
        Expr(:generator, _...) => error("This literal is not permitted in the sandbox")

        # TODO
        # ccall (!!)

        # Recurse
        # Expr(head, args...) => Expr(head, filterer.(x.args)...)
        x::Expr => Expr(x.head, filterer.(x.args)...)

        # Fallback for other things
        x => error("Unknown thing: $x")
    end
    :(@eval Sandbox $(filterer(ex)))
end


function sandbox_parser(s::String)
    ex = Meta.parse(s)
    eval_in_sandbox(ex)
end


function valid_julia(s)
    input = String(take!(copy(ReplMaker.LineEdit.buffer(s))))
    ex = Meta.parse(input)
    !(ex isa Expr && ex.head == :incomplete)
end


sandbox_mode = initrepl(sandbox_parser;
                        prompt_text="Sandbox> ",
                        prompt_color = :yellow,
                        startup_text = false,
                        mode_name = :sandbox,
                        valid_input_checker = valid_julia)

enter_mode!(sandbox_mode)

view this post on Zulip Colin Caine (Feb 27 2021 at 23:07):

I guess what I should actually do is just transparently rewrite symbols that match anything in Core. That would be quite simple.

view this post on Zulip Colin Caine (Feb 28 2021 at 00:44):

For posterity: I posted a better version on discourse.

view this post on Zulip Colin Caine (Feb 28 2021 at 17:49):

I made a little package for fun https://github.com/cmcaine/Sandboxes.jl


Last updated: Oct 02 2023 at 04:34 UTC