Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Don't let setglobal! implicitly create bindings #54678

Merged
merged 1 commit into from
Jun 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions base/docs/basedocs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2510,12 +2510,17 @@ cases.
See also [`setproperty!`](@ref Base.setproperty!) and [`getglobal`](@ref)

# Examples
```jldoctest
julia> module M end;
```jldoctest; filter = r"Stacktrace:(\\n \\[[0-9]+\\].*)*"
julia> module M; global a; end;

julia> M.a # same as `getglobal(M, :a)`
ERROR: UndefVarError: `a` not defined in `M`
Suggestion: check for spelling errors or missing imports.
Suggestion: add an appropriate import or assignment. This global was declared but not assigned.
Stacktrace:
[1] getproperty(x::Module, f::Symbol)
@ Base ./Base.jl:42
[2] top-level scope
@ none:1

julia> setglobal!(M, :a, 1)
1
Expand Down
2 changes: 1 addition & 1 deletion doc/src/manual/embedding.md
Original file line number Diff line number Diff line change
Expand Up @@ -412,7 +412,7 @@ per pointer using
```c
jl_module_t *mod = jl_main_module;
jl_sym_t *var = jl_symbol("var");
jl_binding_t *bp = jl_get_binding_wr(mod, var);
jl_binding_t *bp = jl_get_binding_wr(mod, var, 1);
jl_checked_assignment(bp, mod, var, val);
```

Expand Down
12 changes: 6 additions & 6 deletions src/builtins.c
Original file line number Diff line number Diff line change
Expand Up @@ -1355,7 +1355,7 @@ JL_CALLABLE(jl_f_setglobal)
jl_atomic_error("setglobal!: module binding cannot be written non-atomically");
else if (order >= jl_memory_order_seq_cst)
jl_fence();
jl_binding_t *b = jl_get_binding_wr(mod, var);
jl_binding_t *b = jl_get_binding_wr(mod, var, 0);
jl_checked_assignment(b, mod, var, args[2]); // release store
if (order >= jl_memory_order_seq_cst)
jl_fence();
Expand Down Expand Up @@ -1393,7 +1393,7 @@ JL_CALLABLE(jl_f_set_binding_type)
JL_TYPECHK(set_binding_type!, symbol, (jl_value_t*)s);
jl_value_t *ty = nargs == 2 ? (jl_value_t*)jl_any_type : args[2];
JL_TYPECHK(set_binding_type!, type, ty);
jl_binding_t *b = jl_get_binding_wr(m, s);
jl_binding_t *b = jl_get_binding_wr(m, s, 0);
jl_value_t *old_ty = NULL;
if (jl_atomic_cmpswap_relaxed(&b->ty, &old_ty, ty)) {
jl_gc_wb(b, ty);
Expand All @@ -1420,7 +1420,7 @@ JL_CALLABLE(jl_f_swapglobal)
if (order == jl_memory_order_notatomic)
jl_atomic_error("swapglobal!: module binding cannot be written non-atomically");
// is seq_cst already, no fence needed
jl_binding_t *b = jl_get_binding_wr(mod, var);
jl_binding_t *b = jl_get_binding_wr(mod, var, 0);
return jl_checked_swap(b, mod, var, args[2]);
}

Expand All @@ -1438,7 +1438,7 @@ JL_CALLABLE(jl_f_modifyglobal)
JL_TYPECHK(modifyglobal!, symbol, (jl_value_t*)var);
if (order == jl_memory_order_notatomic)
jl_atomic_error("modifyglobal!: module binding cannot be written non-atomically");
jl_binding_t *b = jl_get_binding_wr(mod, var);
jl_binding_t *b = jl_get_binding_wr(mod, var, 0);
// is seq_cst already, no fence needed
return jl_checked_modify(b, mod, var, args[2], args[3]);
}
Expand Down Expand Up @@ -1467,7 +1467,7 @@ JL_CALLABLE(jl_f_replaceglobal)
jl_atomic_error("replaceglobal!: module binding cannot be written non-atomically");
if (failure_order == jl_memory_order_notatomic)
jl_atomic_error("replaceglobal!: module binding cannot be accessed non-atomically");
jl_binding_t *b = jl_get_binding_wr(mod, var);
jl_binding_t *b = jl_get_binding_wr(mod, var, 0);
// is seq_cst already, no fence needed
return jl_checked_replace(b, mod, var, args[2], args[3]);
}
Expand Down Expand Up @@ -1496,7 +1496,7 @@ JL_CALLABLE(jl_f_setglobalonce)
jl_atomic_error("setglobalonce!: module binding cannot be written non-atomically");
if (failure_order == jl_memory_order_notatomic)
jl_atomic_error("setglobalonce!: module binding cannot be accessed non-atomically");
jl_binding_t *b = jl_get_binding_wr(mod, var);
jl_binding_t *b = jl_get_binding_wr(mod, var, 0);
// is seq_cst already, no fence needed
jl_value_t *old = jl_checked_assignonce(b, mod, var, args[2]);
return old == NULL ? jl_true : jl_false;
Expand Down
39 changes: 26 additions & 13 deletions src/codegen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -946,7 +946,7 @@ static const auto jlgetbindingwrorerror_func = new JuliaFunction<>{
[](LLVMContext &C) {
auto T_pjlvalue = JuliaType::get_pjlvalue_ty(C);
return FunctionType::get(T_pjlvalue,
{T_pjlvalue, T_pjlvalue}, false);
{T_pjlvalue, T_pjlvalue, getInt32Ty(C)}, false);
},
nullptr,
};
Expand Down Expand Up @@ -2100,7 +2100,7 @@ static Type *julia_type_to_llvm(jl_codectx_t &ctx, jl_value_t *jt, bool *isboxed
static jl_returninfo_t get_specsig_function(jl_codectx_t &ctx, Module *M, Value *fval, StringRef name, jl_value_t *sig, jl_value_t *jlrettype, bool is_opaque_closure, bool gcstack_arg, BitVector *used_arguments=nullptr, size_t *args_begin=nullptr);
static jl_cgval_t emit_expr(jl_codectx_t &ctx, jl_value_t *expr, ssize_t ssaval = -1);
static Value *global_binding_pointer(jl_codectx_t &ctx, jl_module_t *m, jl_sym_t *s,
jl_binding_t **pbnd, bool assign);
jl_binding_t **pbnd, bool assign, bool alloc);
static jl_cgval_t emit_checked_var(jl_codectx_t &ctx, Value *bp, jl_sym_t *name, jl_value_t *scope, bool isvol, MDNode *tbaa);
static jl_cgval_t emit_sparam(jl_codectx_t &ctx, size_t i);
static Value *emit_condition(jl_codectx_t &ctx, const jl_cgval_t &condV, const Twine &msg);
Expand Down Expand Up @@ -3190,7 +3190,7 @@ static jl_value_t *jl_ensure_rooted(jl_codectx_t &ctx, jl_value_t *val)
static jl_cgval_t emit_globalref(jl_codectx_t &ctx, jl_module_t *mod, jl_sym_t *name, AtomicOrdering order)
{
jl_binding_t *bnd = NULL;
Value *bp = global_binding_pointer(ctx, mod, name, &bnd, false);
Value *bp = global_binding_pointer(ctx, mod, name, &bnd, false, false);
if (bp == NULL)
return jl_cgval_t();
bp = julia_binding_pvalue(ctx, bp);
Expand All @@ -3209,10 +3209,10 @@ static jl_cgval_t emit_globalref(jl_codectx_t &ctx, jl_module_t *mod, jl_sym_t *
static jl_cgval_t emit_globalop(jl_codectx_t &ctx, jl_module_t *mod, jl_sym_t *sym, jl_cgval_t rval, const jl_cgval_t &cmp,
AtomicOrdering Order, AtomicOrdering FailOrder,
bool issetglobal, bool isreplaceglobal, bool isswapglobal, bool ismodifyglobal, bool issetglobalonce,
const jl_cgval_t *modifyop)
const jl_cgval_t *modifyop, bool alloc)
{
jl_binding_t *bnd = NULL;
Value *bp = global_binding_pointer(ctx, mod, sym, &bnd, true);
Value *bp = global_binding_pointer(ctx, mod, sym, &bnd, true, alloc);
if (bp == NULL)
return jl_cgval_t();
if (bnd && !bnd->constp) {
Expand Down Expand Up @@ -3761,7 +3761,8 @@ static bool emit_f_opglobal(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f,
isswapglobal,
ismodifyglobal,
issetglobalonce,
modifyop);
modifyop,
false);
return true;
}
}
Expand Down Expand Up @@ -5448,7 +5449,7 @@ static void emit_hasnofield_error_ifnot(jl_codectx_t &ctx, Value *ok, jl_sym_t *
// if the reference currently bound or assign == true,
// pbnd will also be assigned with the binding address
static Value *global_binding_pointer(jl_codectx_t &ctx, jl_module_t *m, jl_sym_t *s,
jl_binding_t **pbnd, bool assign)
jl_binding_t **pbnd, bool assign, bool alloc)
{
jl_binding_t *b = jl_get_module_binding(m, s, 1);
if (assign) {
Expand Down Expand Up @@ -5478,9 +5479,17 @@ static Value *global_binding_pointer(jl_codectx_t &ctx, jl_module_t *m, jl_sym_t
ctx.builder.CreateCondBr(iscached, have_val, not_found);
not_found->insertInto(ctx.f);
ctx.builder.SetInsertPoint(not_found);
Value *bval = ctx.builder.CreateCall(prepare_call(assign ? jlgetbindingwrorerror_func : jlgetbindingorerror_func),
{ literal_pointer_val(ctx, (jl_value_t*)m),
literal_pointer_val(ctx, (jl_value_t*)s) });
Value *bval = nullptr;
if (assign) {
bval = ctx.builder.CreateCall(prepare_call(jlgetbindingwrorerror_func),
{ literal_pointer_val(ctx, (jl_value_t*)m),
literal_pointer_val(ctx, (jl_value_t*)s),
ConstantInt::get(getInt32Ty(ctx.builder.getContext()), alloc)});
} else {
bval = ctx.builder.CreateCall(prepare_call(jlgetbindingorerror_func),
{ literal_pointer_val(ctx, (jl_value_t*)m),
literal_pointer_val(ctx, (jl_value_t*)s)});
}
setName(ctx.emission_context, bval, jl_symbol_name(m->name) + StringRef(".") + jl_symbol_name(s) + ".found");
ctx.builder.CreateAlignedStore(bval, bindinggv, Align(sizeof(void*)))->setOrdering(AtomicOrdering::Release);
ctx.builder.CreateBr(have_val);
Expand All @@ -5497,7 +5506,8 @@ static Value *global_binding_pointer(jl_codectx_t &ctx, jl_module_t *m, jl_sym_t
// this will fail at runtime, so defer to the runtime to create the error
ctx.builder.CreateCall(prepare_call(jlgetbindingwrorerror_func),
{ literal_pointer_val(ctx, (jl_value_t*)m),
literal_pointer_val(ctx, (jl_value_t*)s) });
literal_pointer_val(ctx, (jl_value_t*)s),
ConstantInt::get(getInt32Ty(ctx.builder.getContext()), alloc) });
CreateTrap(ctx.builder);
return NULL;
}
Expand Down Expand Up @@ -5992,17 +6002,20 @@ static void emit_assignment(jl_codectx_t &ctx, jl_value_t *l, jl_value_t *r, ssi

jl_module_t *mod;
jl_sym_t *sym;
bool toplevel = jl_is_module(ctx.linfo->def.value);
bool alloc = toplevel;
if (jl_is_symbol(l)) {
mod = ctx.module;
sym = (jl_sym_t*)l;
}
else {
assert(jl_is_globalref(l));
alloc &= jl_globalref_mod(l) == ctx.module;
mod = jl_globalref_mod(l);
sym = jl_globalref_name(l);
}
emit_globalop(ctx, mod, sym, rval_info, jl_cgval_t(), AtomicOrdering::Release, AtomicOrdering::NotAtomic,
true, false, false, false, false, nullptr);
true, false, false, false, false, nullptr, alloc);
// Global variable. Does not need debug info because the debugger knows about
// its memory location.
}
Expand Down Expand Up @@ -6456,7 +6469,7 @@ static jl_cgval_t emit_expr(jl_codectx_t &ctx, jl_value_t *expr, ssize_t ssaidx_
}
if (jl_is_symbol(sym)) {
jl_binding_t *bnd = NULL;
Value *bp = global_binding_pointer(ctx, mod, sym, &bnd, true);
Value *bp = global_binding_pointer(ctx, mod, sym, &bnd, true, true);
if (bp)
ctx.builder.CreateCall(prepare_call(jldeclareconst_func),
{ bp, literal_pointer_val(ctx, (jl_value_t*)mod), literal_pointer_val(ctx, (jl_value_t*)sym) });
Expand Down
6 changes: 5 additions & 1 deletion src/interpreter.c
Original file line number Diff line number Diff line change
Expand Up @@ -571,17 +571,21 @@ static jl_value_t *eval_body(jl_array_t *stmts, interpreter_state *s, size_t ip,
else {
jl_module_t *modu;
jl_sym_t *sym;
// Plain assignment is allowed to create bindings at
// toplevel and only for the current module
int alloc = toplevel;
if (jl_is_globalref(lhs)) {
modu = jl_globalref_mod(lhs);
sym = jl_globalref_name(lhs);
alloc &= modu == s->module;
}
else {
assert(jl_is_symbol(lhs));
modu = s->module;
sym = (jl_sym_t*)lhs;
}
JL_GC_PUSH1(&rhs);
jl_binding_t *b = jl_get_binding_wr(modu, sym);
jl_binding_t *b = jl_get_binding_wr(modu, sym, alloc);
jl_checked_assignment(b, modu, sym, rhs);
JL_GC_POP();
}
Expand Down
1 change: 1 addition & 0 deletions src/julia-syntax.scm
Original file line number Diff line number Diff line change
Expand Up @@ -4233,6 +4233,7 @@ f(x) = yt(x)
(put! globals ref #t)
`(block
(toplevel-only set_binding_type! ,(cadr e))
(global ,ref)
(call (core set_binding_type!) ,(cadr ref) (inert ,(caddr ref)) ,(caddr e))))
`(call (core typeassert) ,@(cdr e))))
fname lam namemap defined toplevel interp opaq globals locals))))
Expand Down
2 changes: 1 addition & 1 deletion src/julia.h
Original file line number Diff line number Diff line change
Expand Up @@ -1947,7 +1947,7 @@ JL_DLLEXPORT jl_binding_t *jl_get_binding_or_error(jl_module_t *m, jl_sym_t *var
JL_DLLEXPORT jl_value_t *jl_module_globalref(jl_module_t *m, jl_sym_t *var);
JL_DLLEXPORT jl_value_t *jl_get_binding_type(jl_module_t *m, jl_sym_t *var);
// get binding for assignment
JL_DLLEXPORT jl_binding_t *jl_get_binding_wr(jl_module_t *m JL_PROPAGATES_ROOT, jl_sym_t *var);
JL_DLLEXPORT jl_binding_t *jl_get_binding_wr(jl_module_t *m JL_PROPAGATES_ROOT, jl_sym_t *var, int alloc);
JL_DLLEXPORT jl_binding_t *jl_get_binding_for_method_def(jl_module_t *m JL_PROPAGATES_ROOT, jl_sym_t *var);
JL_DLLEXPORT int jl_boundp(jl_module_t *m, jl_sym_t *var);
JL_DLLEXPORT int jl_defines_or_exports_p(jl_module_t *m, jl_sym_t *var);
Expand Down
9 changes: 6 additions & 3 deletions src/module.c
Original file line number Diff line number Diff line change
Expand Up @@ -219,13 +219,16 @@ static void check_safe_newbinding(jl_module_t *m, jl_sym_t *var)
static jl_module_t *jl_binding_dbgmodule(jl_binding_t *b, jl_module_t *m, jl_sym_t *var) JL_GLOBALLY_ROOTED;

// get binding for assignment
JL_DLLEXPORT jl_binding_t *jl_get_binding_wr(jl_module_t *m JL_PROPAGATES_ROOT, jl_sym_t *var)
JL_DLLEXPORT jl_binding_t *jl_get_binding_wr(jl_module_t *m JL_PROPAGATES_ROOT, jl_sym_t *var, int alloc)
{
jl_binding_t *b = jl_get_module_binding(m, var, 1);
jl_binding_t *b2 = jl_atomic_load_relaxed(&b->owner);
if (b2 != b) {
if (b2 == NULL)
if (b2 == NULL) {
check_safe_newbinding(m, var);
if (!alloc)
jl_errorf("Global %s.%s does not exist and cannot be assigned. Declare it using `global` before attempting assignment.", jl_symbol_name(m->name), jl_symbol_name(var));
}
if (b2 != NULL || (!jl_atomic_cmpswap(&b->owner, &b2, b) && b2 != b)) {
jl_module_t *from = jl_binding_dbgmodule(b, m, var);
if (from == m)
Expand Down Expand Up @@ -793,7 +796,7 @@ JL_DLLEXPORT jl_value_t *jl_get_global(jl_module_t *m, jl_sym_t *var)

JL_DLLEXPORT void jl_set_global(jl_module_t *m JL_ROOTING_ARGUMENT, jl_sym_t *var, jl_value_t *val JL_ROOTED_ARGUMENT)
{
jl_binding_t *bp = jl_get_binding_wr(m, var);
jl_binding_t *bp = jl_get_binding_wr(m, var, 0);
jl_checked_assignment(bp, m, var, val);
}

Expand Down
8 changes: 4 additions & 4 deletions src/toplevel.c
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ static jl_value_t *jl_eval_module_expr(jl_module_t *parent_module, jl_expr_t *ex
}
}
else {
jl_binding_t *b = jl_get_binding_wr(parent_module, name);
jl_binding_t *b = jl_get_binding_wr(parent_module, name, 1);
jl_declare_constant(b, parent_module, name);
jl_value_t *old = NULL;
if (!jl_atomic_cmpswap(&b->value, &old, (jl_value_t*)newm)) {
Expand Down Expand Up @@ -325,7 +325,7 @@ void jl_eval_global_expr(jl_module_t *m, jl_expr_t *ex, int set_type) {
gs = (jl_sym_t*)arg;
}
if (!jl_binding_resolved_p(gm, gs)) {
jl_binding_t *b = jl_get_binding_wr(gm, gs);
jl_binding_t *b = jl_get_binding_wr(gm, gs, 1);
if (set_type) {
jl_value_t *old_ty = NULL;
// maybe set the type too, perhaps
Expand Down Expand Up @@ -638,7 +638,7 @@ static void import_module(jl_module_t *JL_NONNULL m, jl_module_t *import, jl_sym
jl_symbol_name(name), jl_symbol_name(m->name));
}
else {
b = jl_get_binding_wr(m, name);
b = jl_get_binding_wr(m, name, 1);
}
jl_declare_constant(b, m, name);
jl_checked_assignment(b, m, name, (jl_value_t*)import);
Expand Down Expand Up @@ -897,7 +897,7 @@ JL_DLLEXPORT jl_value_t *jl_toplevel_eval_flex(jl_module_t *JL_NONNULL m, jl_val
gm = m;
gs = (jl_sym_t*)arg;
}
jl_binding_t *b = jl_get_binding_wr(gm, gs);
jl_binding_t *b = jl_get_binding_wr(gm, gs, 1);
jl_declare_constant(b, gm, gs);
JL_GC_POP();
return jl_nothing;
Expand Down
1 change: 1 addition & 0 deletions test/core.jl
Original file line number Diff line number Diff line change
Expand Up @@ -8145,6 +8145,7 @@ end
@test_broken Int isa Union{Union, Type{Union{Int,T1}} where {T1}}

let M = @__MODULE__
Core.eval(M, :(global a_typed_global))
@test Core.set_binding_type!(M, :a_typed_global, Tuple{Union{Integer,Nothing}}) === nothing
@test Core.get_binding_type(M, :a_typed_global) === Tuple{Union{Integer,Nothing}}
@test Core.set_binding_type!(M, :a_typed_global, Tuple{Union{Integer,Nothing}}) === nothing
Expand Down
1 change: 0 additions & 1 deletion test/precompile.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1023,7 +1023,6 @@ precompile_test_harness("code caching") do dir
@test mi.specTypes.parameters[end] === Integer ? !hv : hv
end

setglobal!(Main, :inval, invalidations)
idxs = findall(==("verify_methods"), invalidations)
idxsbits = filter(idxs) do i
mi = invalidations[i-1]
Expand Down
37 changes: 37 additions & 0 deletions test/syntax.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3699,3 +3699,40 @@ end
@test f54701() == 3
@test !@isdefined(a54701)
@test !@isdefined(b54701)

# Issue #54607 - binding creation in foreign modules should not be permitted
module Foreign54607
# Syntactic, not dynamic
try_to_create_binding1() = (Foreign54607.foo = 2)
@eval try_to_create_binding2() = ($(GlobalRef(Foreign54607, :foo)) = 2)
function global_create_binding()
global bar
bar = 3
end
baz = 4
begin;
@Base.Experimental.force_compile
compiled_assign = 5
end
@eval $(GlobalRef(Foreign54607, :gr_assign)) = 6
end
@test_throws ErrorException (Foreign54607.foo = 1)
@test_throws ErrorException Foreign54607.try_to_create_binding1()
@test_throws ErrorException Foreign54607.try_to_create_binding2()
@test_throws ErrorException begin
@Base.Experimental.force_compile
(Foreign54607.foo = 1)
end
@test_throws ErrorException @eval (GlobalRef(Foreign54607, :gr_assign2)) = 7
Foreign54607.global_create_binding()
@test isdefined(Foreign54607, :bar)
@test isdefined(Foreign54607, :baz)
@test isdefined(Foreign54607, :compiled_assign)
@test isdefined(Foreign54607, :gr_assign)
Foreign54607.bar = 8
@test Foreign54607.bar == 8
begin
@Base.Experimental.force_compile
Foreign54607.bar = 9
end
@test Foreign54607.bar == 9
Loading