Check whether a symbol can be safely evaluated

2019-08-11 06:37发布

I have a string x. I think that x is a string representation of a type that is a subtype of Number. For example, x might take the value "Float64". I could check this using:

eval(parse(x)) <: Number

However, it is possible that x contains something dangerous, like some variant on "rm(something_important)", so using eval is a bad idea until I'm sure x is safe.

Is there any way I can safely check whether x is a string representation of a subtype of Number?

(other than building an array of strings of all possible sub-types of Number and comparing...)

标签: julia
2条回答
甜甜的少女心
2楼-- · 2019-08-11 06:51

The HDF5.jl package has to deal with this. It tackles it by parsing the string and then inspecting the result before evaling it. If the parsed string is what it considers to be a valid_type_expression then it knows that it should be safe to evaluate in the Main namespace. This allows it to pickup custom types from the Main namespace that wouldn't be available from within a baremodule.


More details: After you parse an arbitrary string, you can inspect the returned object to see if it's "safe" to evaluate:

julia> dump(parse("Int"))
Symbol Int

julia> dump(parse("Vector{Int}"))
Expr
  head: Symbol curly
  args: Array(Any,(2,))
    1: Symbol Vector
    2: Symbol Int
  typ: Any

julia> dump(parse("""rm("/")"""))
Expr
  head: Symbol call
  args: Array(Any,(2,))
    1: Symbol rm
    2: ASCIIString "/"
  typ: Any

We want to ensure that we never eval an expression that could call arbitrary behaviors. Depending on how thoroughly you want to support the type syntax, your solution could be quite simple or it could be as complicated as the HDF5 solution I linked above. If you're just after simple, unparameterized types we can simplify things substantially:

is_valid_type_expression(ex::Symbol) = true
is_valid_type_expression(ex) = false

function julia_type(string)
    ex = parse(string)
    if is_valid_type_expression(ex)
        try
            typ = eval(Main, ex)
            isa(typ, Type) && typ <: Number && return typ
        end
    end
    error("unsupported type: $string")
end

julia> julia_type("String")
ERROR: unsupported type: String
 in julia_type at none:9

julia> julia_type("Int")
Int64

julia> julia_type("""rm("/")""")
ERROR: unsupported type: rm("/")
 in julia_type at none:9

Note that anything more complicated than a symbol isn't allowed to be eval'ed. And after evaling the expression, we check to ensure that the type is a Type and that it's a subtype of Number. This will also allow custom subtypes of Number in addition to the builtin ones, since we're evaluating it in the Main namespace.

查看更多
淡お忘
3楼-- · 2019-08-11 07:02

Edit: This solution is not safe. Read the comments. I leave it because it is still instructive.

eval optionally takes a module as its first argument.

You could evaluate it in a bare module (baremodule), so the command doesn't have access to the standard library (then it cannot do much harm).

julia> baremodule M
       end

julia> x = :(rm("/"))
:(rm("/"))

julia> eval(M, x)
ERROR: rm not defined

julia> x = :"Float64"
"Float64"

julia> eval(M, x)
"Float64"
查看更多
登录 后发表回答