Reputation: 18560
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...)
Upvotes: 2
Views: 469
Reputation: 31342
The HDF5.jl package has to deal with this. It tackles it by parsing the string and then inspecting the result before eval
ing 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 eval
ing 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.
Upvotes: 5
Reputation: 32893
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"
Upvotes: 1