Skip to content

Commit 252212f

Browse files
committed
Don't let setglobal! implicitly create bindings
PR #44231 (part of Julia 1.9) introduced the ability to modify globals with `Mod.sym = val` syntax. However, the intention of this syntax was always to modify *existing* globals in other modules. Unfortunately, as implemented, it also implicitly creates new bindings in the other module, even if the binding was not previously declared. This was not intended, but it's a bit of a syntax corner case, so nobody caught it at the time. After some extensive discussions and taking into account the near future direction we want to go with bindings (#54654 for both), the consensus was reached that we should try to undo the implicit creation of bindings (but not the ability to assign the *value* of globals in other modules). Note that this was always an error until Julia 1.9, so hopefully it hasn't crept into too many packages yet. We'll see what pkgeval says. If use is extensive, we may want to consider a softer removal strategy. Across base and stdlib, there's two cases affected by this change: 1. A left over debug statement in `precompile` that wanted to assign a new variable in Base for debugging. Removed in this PR. 2. Distributed wanting to create new bindings. This is a legimitate use case for wanting to create bindings in other modules. This is fixed in JuliaLang/Distributed.jl#102. As noted in that PR, the recommended replacement where implicit binding creation is desired is: ``` Core.eval(mod, Expr(:global, sym)) invokelatest(setglobal!, mod, sym, val) ``` The `invokelatest` is not presently required, but may be needed by #54654, so it's included in the recommendation now. Fixes #54607
1 parent 13635e1 commit 252212f

File tree

11 files changed

+86
-29
lines changed

11 files changed

+86
-29
lines changed

doc/src/manual/embedding.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -412,7 +412,7 @@ per pointer using
412412
```c
413413
jl_module_t *mod = jl_main_module;
414414
jl_sym_t *var = jl_symbol("var");
415-
jl_binding_t *bp = jl_get_binding_wr(mod, var);
415+
jl_binding_t *bp = jl_get_binding_wr(mod, var, 1);
416416
jl_checked_assignment(bp, mod, var, val);
417417
```
418418

src/builtins.c

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1355,7 +1355,7 @@ JL_CALLABLE(jl_f_setglobal)
13551355
jl_atomic_error("setglobal!: module binding cannot be written non-atomically");
13561356
else if (order >= jl_memory_order_seq_cst)
13571357
jl_fence();
1358-
jl_binding_t *b = jl_get_binding_wr(mod, var);
1358+
jl_binding_t *b = jl_get_binding_wr(mod, var, 0);
13591359
jl_checked_assignment(b, mod, var, args[2]); // release store
13601360
if (order >= jl_memory_order_seq_cst)
13611361
jl_fence();
@@ -1393,7 +1393,7 @@ JL_CALLABLE(jl_f_set_binding_type)
13931393
JL_TYPECHK(set_binding_type!, symbol, (jl_value_t*)s);
13941394
jl_value_t *ty = nargs == 2 ? (jl_value_t*)jl_any_type : args[2];
13951395
JL_TYPECHK(set_binding_type!, type, ty);
1396-
jl_binding_t *b = jl_get_binding_wr(m, s);
1396+
jl_binding_t *b = jl_get_binding_wr(m, s, 0);
13971397
jl_value_t *old_ty = NULL;
13981398
if (jl_atomic_cmpswap_relaxed(&b->ty, &old_ty, ty)) {
13991399
jl_gc_wb(b, ty);
@@ -1420,7 +1420,7 @@ JL_CALLABLE(jl_f_swapglobal)
14201420
if (order == jl_memory_order_notatomic)
14211421
jl_atomic_error("swapglobal!: module binding cannot be written non-atomically");
14221422
// is seq_cst already, no fence needed
1423-
jl_binding_t *b = jl_get_binding_wr(mod, var);
1423+
jl_binding_t *b = jl_get_binding_wr(mod, var, 0);
14241424
return jl_checked_swap(b, mod, var, args[2]);
14251425
}
14261426

@@ -1438,7 +1438,7 @@ JL_CALLABLE(jl_f_modifyglobal)
14381438
JL_TYPECHK(modifyglobal!, symbol, (jl_value_t*)var);
14391439
if (order == jl_memory_order_notatomic)
14401440
jl_atomic_error("modifyglobal!: module binding cannot be written non-atomically");
1441-
jl_binding_t *b = jl_get_binding_wr(mod, var);
1441+
jl_binding_t *b = jl_get_binding_wr(mod, var, 0);
14421442
// is seq_cst already, no fence needed
14431443
return jl_checked_modify(b, mod, var, args[2], args[3]);
14441444
}
@@ -1467,7 +1467,7 @@ JL_CALLABLE(jl_f_replaceglobal)
14671467
jl_atomic_error("replaceglobal!: module binding cannot be written non-atomically");
14681468
if (failure_order == jl_memory_order_notatomic)
14691469
jl_atomic_error("replaceglobal!: module binding cannot be accessed non-atomically");
1470-
jl_binding_t *b = jl_get_binding_wr(mod, var);
1470+
jl_binding_t *b = jl_get_binding_wr(mod, var, 0);
14711471
// is seq_cst already, no fence needed
14721472
return jl_checked_replace(b, mod, var, args[2], args[3]);
14731473
}
@@ -1496,7 +1496,7 @@ JL_CALLABLE(jl_f_setglobalonce)
14961496
jl_atomic_error("setglobalonce!: module binding cannot be written non-atomically");
14971497
if (failure_order == jl_memory_order_notatomic)
14981498
jl_atomic_error("setglobalonce!: module binding cannot be accessed non-atomically");
1499-
jl_binding_t *b = jl_get_binding_wr(mod, var);
1499+
jl_binding_t *b = jl_get_binding_wr(mod, var, 0);
15001500
// is seq_cst already, no fence needed
15011501
jl_value_t *old = jl_checked_assignonce(b, mod, var, args[2]);
15021502
return old == NULL ? jl_true : jl_false;

src/codegen.cpp

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -946,7 +946,7 @@ static const auto jlgetbindingwrorerror_func = new JuliaFunction<>{
946946
[](LLVMContext &C) {
947947
auto T_pjlvalue = JuliaType::get_pjlvalue_ty(C);
948948
return FunctionType::get(T_pjlvalue,
949-
{T_pjlvalue, T_pjlvalue}, false);
949+
{T_pjlvalue, T_pjlvalue, getInt32Ty(C)}, false);
950950
},
951951
nullptr,
952952
};
@@ -2099,7 +2099,7 @@ static Type *julia_type_to_llvm(jl_codectx_t &ctx, jl_value_t *jt, bool *isboxed
20992099
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);
21002100
static jl_cgval_t emit_expr(jl_codectx_t &ctx, jl_value_t *expr, ssize_t ssaval = -1);
21012101
static Value *global_binding_pointer(jl_codectx_t &ctx, jl_module_t *m, jl_sym_t *s,
2102-
jl_binding_t **pbnd, bool assign);
2102+
jl_binding_t **pbnd, bool assign, bool alloc);
21032103
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);
21042104
static jl_cgval_t emit_sparam(jl_codectx_t &ctx, size_t i);
21052105
static Value *emit_condition(jl_codectx_t &ctx, const jl_cgval_t &condV, const Twine &msg);
@@ -3189,7 +3189,7 @@ static jl_value_t *jl_ensure_rooted(jl_codectx_t &ctx, jl_value_t *val)
31893189
static jl_cgval_t emit_globalref(jl_codectx_t &ctx, jl_module_t *mod, jl_sym_t *name, AtomicOrdering order)
31903190
{
31913191
jl_binding_t *bnd = NULL;
3192-
Value *bp = global_binding_pointer(ctx, mod, name, &bnd, false);
3192+
Value *bp = global_binding_pointer(ctx, mod, name, &bnd, false, false);
31933193
if (bp == NULL)
31943194
return jl_cgval_t();
31953195
bp = julia_binding_pvalue(ctx, bp);
@@ -3208,10 +3208,10 @@ static jl_cgval_t emit_globalref(jl_codectx_t &ctx, jl_module_t *mod, jl_sym_t *
32083208
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,
32093209
AtomicOrdering Order, AtomicOrdering FailOrder,
32103210
bool issetglobal, bool isreplaceglobal, bool isswapglobal, bool ismodifyglobal, bool issetglobalonce,
3211-
const jl_cgval_t *modifyop)
3211+
const jl_cgval_t *modifyop, bool alloc)
32123212
{
32133213
jl_binding_t *bnd = NULL;
3214-
Value *bp = global_binding_pointer(ctx, mod, sym, &bnd, true);
3214+
Value *bp = global_binding_pointer(ctx, mod, sym, &bnd, true, alloc);
32153215
if (bp == NULL)
32163216
return jl_cgval_t();
32173217
if (bnd && !bnd->constp) {
@@ -3760,7 +3760,8 @@ static bool emit_f_opglobal(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f,
37603760
isswapglobal,
37613761
ismodifyglobal,
37623762
issetglobalonce,
3763-
modifyop);
3763+
modifyop,
3764+
false);
37643765
return true;
37653766
}
37663767
}
@@ -5448,7 +5449,7 @@ static void emit_hasnofield_error_ifnot(jl_codectx_t &ctx, Value *ok, jl_sym_t *
54485449
// if the reference currently bound or assign == true,
54495450
// pbnd will also be assigned with the binding address
54505451
static Value *global_binding_pointer(jl_codectx_t &ctx, jl_module_t *m, jl_sym_t *s,
5451-
jl_binding_t **pbnd, bool assign)
5452+
jl_binding_t **pbnd, bool assign, bool alloc)
54525453
{
54535454
jl_binding_t *b = jl_get_module_binding(m, s, 1);
54545455
if (assign) {
@@ -5478,9 +5479,17 @@ static Value *global_binding_pointer(jl_codectx_t &ctx, jl_module_t *m, jl_sym_t
54785479
ctx.builder.CreateCondBr(iscached, have_val, not_found);
54795480
not_found->insertInto(ctx.f);
54805481
ctx.builder.SetInsertPoint(not_found);
5481-
Value *bval = ctx.builder.CreateCall(prepare_call(assign ? jlgetbindingwrorerror_func : jlgetbindingorerror_func),
5482-
{ literal_pointer_val(ctx, (jl_value_t*)m),
5483-
literal_pointer_val(ctx, (jl_value_t*)s) });
5482+
Value *bval = nullptr;
5483+
if (assign) {
5484+
bval = ctx.builder.CreateCall(prepare_call(jlgetbindingwrorerror_func),
5485+
{ literal_pointer_val(ctx, (jl_value_t*)m),
5486+
literal_pointer_val(ctx, (jl_value_t*)s),
5487+
ConstantInt::get(getInt32Ty(ctx.builder.getContext()), alloc)});
5488+
} else {
5489+
bval = ctx.builder.CreateCall(prepare_call(jlgetbindingorerror_func),
5490+
{ literal_pointer_val(ctx, (jl_value_t*)m),
5491+
literal_pointer_val(ctx, (jl_value_t*)s)});
5492+
}
54845493
setName(ctx.emission_context, bval, jl_symbol_name(m->name) + StringRef(".") + jl_symbol_name(s) + ".found");
54855494
ctx.builder.CreateAlignedStore(bval, bindinggv, Align(sizeof(void*)))->setOrdering(AtomicOrdering::Release);
54865495
ctx.builder.CreateBr(have_val);
@@ -5992,17 +6001,20 @@ static void emit_assignment(jl_codectx_t &ctx, jl_value_t *l, jl_value_t *r, ssi
59926001

59936002
jl_module_t *mod;
59946003
jl_sym_t *sym;
6004+
bool toplevel = jl_is_module(ctx.linfo->def.value);
6005+
bool alloc = toplevel;
59956006
if (jl_is_symbol(l)) {
59966007
mod = ctx.module;
59976008
sym = (jl_sym_t*)l;
59986009
}
59996010
else {
60006011
assert(jl_is_globalref(l));
6012+
alloc &= jl_globalref_mod(l) == ctx.module;
60016013
mod = jl_globalref_mod(l);
60026014
sym = jl_globalref_name(l);
60036015
}
60046016
emit_globalop(ctx, mod, sym, rval_info, jl_cgval_t(), AtomicOrdering::Release, AtomicOrdering::NotAtomic,
6005-
true, false, false, false, false, nullptr);
6017+
true, false, false, false, false, nullptr, alloc);
60066018
// Global variable. Does not need debug info because the debugger knows about
60076019
// its memory location.
60086020
}
@@ -6456,7 +6468,7 @@ static jl_cgval_t emit_expr(jl_codectx_t &ctx, jl_value_t *expr, ssize_t ssaidx_
64566468
}
64576469
if (jl_is_symbol(sym)) {
64586470
jl_binding_t *bnd = NULL;
6459-
Value *bp = global_binding_pointer(ctx, mod, sym, &bnd, true);
6471+
Value *bp = global_binding_pointer(ctx, mod, sym, &bnd, true, true);
64606472
if (bp)
64616473
ctx.builder.CreateCall(prepare_call(jldeclareconst_func),
64626474
{ bp, literal_pointer_val(ctx, (jl_value_t*)mod), literal_pointer_val(ctx, (jl_value_t*)sym) });

src/interpreter.c

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -569,17 +569,21 @@ static jl_value_t *eval_body(jl_array_t *stmts, interpreter_state *s, size_t ip,
569569
else {
570570
jl_module_t *modu;
571571
jl_sym_t *sym;
572+
// Plain assignment is allowed to create bindings at
573+
// toplevel and only for the current module
574+
int alloc = toplevel;
572575
if (jl_is_globalref(lhs)) {
573576
modu = jl_globalref_mod(lhs);
574577
sym = jl_globalref_name(lhs);
578+
alloc &= modu == s->module;
575579
}
576580
else {
577581
assert(jl_is_symbol(lhs));
578582
modu = s->module;
579583
sym = (jl_sym_t*)lhs;
580584
}
581585
JL_GC_PUSH1(&rhs);
582-
jl_binding_t *b = jl_get_binding_wr(modu, sym);
586+
jl_binding_t *b = jl_get_binding_wr(modu, sym, alloc);
583587
jl_checked_assignment(b, modu, sym, rhs);
584588
JL_GC_POP();
585589
}

src/julia-syntax.scm

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4233,6 +4233,7 @@ f(x) = yt(x)
42334233
(put! globals ref #t)
42344234
`(block
42354235
(toplevel-only set_binding_type! ,(cadr e))
4236+
(global ,ref)
42364237
(call (core set_binding_type!) ,(cadr ref) (inert ,(caddr ref)) ,(caddr e))))
42374238
`(call (core typeassert) ,@(cdr e))))
42384239
fname lam namemap defined toplevel interp opaq globals locals))))

src/julia.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1946,7 +1946,7 @@ JL_DLLEXPORT jl_binding_t *jl_get_binding_or_error(jl_module_t *m, jl_sym_t *var
19461946
JL_DLLEXPORT jl_value_t *jl_module_globalref(jl_module_t *m, jl_sym_t *var);
19471947
JL_DLLEXPORT jl_value_t *jl_get_binding_type(jl_module_t *m, jl_sym_t *var);
19481948
// get binding for assignment
1949-
JL_DLLEXPORT jl_binding_t *jl_get_binding_wr(jl_module_t *m JL_PROPAGATES_ROOT, jl_sym_t *var);
1949+
JL_DLLEXPORT jl_binding_t *jl_get_binding_wr(jl_module_t *m JL_PROPAGATES_ROOT, jl_sym_t *var, int alloc);
19501950
JL_DLLEXPORT jl_binding_t *jl_get_binding_for_method_def(jl_module_t *m JL_PROPAGATES_ROOT, jl_sym_t *var);
19511951
JL_DLLEXPORT int jl_boundp(jl_module_t *m, jl_sym_t *var);
19521952
JL_DLLEXPORT int jl_defines_or_exports_p(jl_module_t *m, jl_sym_t *var);

src/module.c

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -219,13 +219,16 @@ static void check_safe_newbinding(jl_module_t *m, jl_sym_t *var)
219219
static jl_module_t *jl_binding_dbgmodule(jl_binding_t *b, jl_module_t *m, jl_sym_t *var) JL_GLOBALLY_ROOTED;
220220

221221
// get binding for assignment
222-
JL_DLLEXPORT jl_binding_t *jl_get_binding_wr(jl_module_t *m JL_PROPAGATES_ROOT, jl_sym_t *var)
222+
JL_DLLEXPORT jl_binding_t *jl_get_binding_wr(jl_module_t *m JL_PROPAGATES_ROOT, jl_sym_t *var, int alloc)
223223
{
224224
jl_binding_t *b = jl_get_module_binding(m, var, 1);
225225
jl_binding_t *b2 = jl_atomic_load_relaxed(&b->owner);
226226
if (b2 != b) {
227-
if (b2 == NULL)
227+
if (b2 == NULL) {
228228
check_safe_newbinding(m, var);
229+
if (!alloc)
230+
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));
231+
}
229232
if (b2 != NULL || (!jl_atomic_cmpswap(&b->owner, &b2, b) && b2 != b)) {
230233
jl_module_t *from = jl_binding_dbgmodule(b, m, var);
231234
if (from == m)
@@ -791,7 +794,7 @@ JL_DLLEXPORT jl_value_t *jl_get_global(jl_module_t *m, jl_sym_t *var)
791794

792795
JL_DLLEXPORT void jl_set_global(jl_module_t *m JL_ROOTING_ARGUMENT, jl_sym_t *var, jl_value_t *val JL_ROOTED_ARGUMENT)
793796
{
794-
jl_binding_t *bp = jl_get_binding_wr(m, var);
797+
jl_binding_t *bp = jl_get_binding_wr(m, var, 0);
795798
jl_checked_assignment(bp, m, var, val);
796799
}
797800

src/toplevel.c

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ static jl_value_t *jl_eval_module_expr(jl_module_t *parent_module, jl_expr_t *ex
155155
}
156156
}
157157
else {
158-
jl_binding_t *b = jl_get_binding_wr(parent_module, name);
158+
jl_binding_t *b = jl_get_binding_wr(parent_module, name, 1);
159159
jl_declare_constant(b, parent_module, name);
160160
jl_value_t *old = NULL;
161161
if (!jl_atomic_cmpswap(&b->value, &old, (jl_value_t*)newm)) {
@@ -325,7 +325,7 @@ void jl_eval_global_expr(jl_module_t *m, jl_expr_t *ex, int set_type) {
325325
gs = (jl_sym_t*)arg;
326326
}
327327
if (!jl_binding_resolved_p(gm, gs)) {
328-
jl_binding_t *b = jl_get_binding_wr(gm, gs);
328+
jl_binding_t *b = jl_get_binding_wr(gm, gs, 1);
329329
if (set_type) {
330330
jl_value_t *old_ty = NULL;
331331
// maybe set the type too, perhaps
@@ -638,7 +638,7 @@ static void import_module(jl_module_t *JL_NONNULL m, jl_module_t *import, jl_sym
638638
jl_symbol_name(name), jl_symbol_name(m->name));
639639
}
640640
else {
641-
b = jl_get_binding_wr(m, name);
641+
b = jl_get_binding_wr(m, name, 1);
642642
}
643643
jl_declare_constant(b, m, name);
644644
jl_checked_assignment(b, m, name, (jl_value_t*)import);
@@ -897,7 +897,7 @@ JL_DLLEXPORT jl_value_t *jl_toplevel_eval_flex(jl_module_t *JL_NONNULL m, jl_val
897897
gm = m;
898898
gs = (jl_sym_t*)arg;
899899
}
900-
jl_binding_t *b = jl_get_binding_wr(gm, gs);
900+
jl_binding_t *b = jl_get_binding_wr(gm, gs, 1);
901901
jl_declare_constant(b, gm, gs);
902902
JL_GC_POP();
903903
return jl_nothing;

test/core.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8145,6 +8145,7 @@ end
81458145
@test_broken Int isa Union{Union, Type{Union{Int,T1}} where {T1}}
81468146

81478147
let M = @__MODULE__
8148+
Core.eval(M, :(global a_typed_global))
81488149
@test Core.set_binding_type!(M, :a_typed_global, Tuple{Union{Integer,Nothing}}) === nothing
81498150
@test Core.get_binding_type(M, :a_typed_global) === Tuple{Union{Integer,Nothing}}
81508151
@test Core.set_binding_type!(M, :a_typed_global, Tuple{Union{Integer,Nothing}}) === nothing

test/precompile.jl

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1023,7 +1023,6 @@ precompile_test_harness("code caching") do dir
10231023
@test mi.specTypes.parameters[end] === Integer ? !hv : hv
10241024
end
10251025

1026-
setglobal!(Main, :inval, invalidations)
10271026
idxs = findall(==("verify_methods"), invalidations)
10281027
idxsbits = filter(idxs) do i
10291028
mi = invalidations[i-1]

test/syntax.jl

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3683,3 +3683,40 @@ end
36833683
# Issue #53729 - Lowering recursion into Expr(:toplevel)
36843684
@test eval(Expr(:let, Expr(:block), Expr(:block, Expr(:toplevel, :(f53729(x) = x)), :(x=1)))) == 1
36853685
@test f53729(2) == 2
3686+
3687+
# Issue #54607 - binding creation in foreign modules should not be permitted
3688+
module Foreign54607
3689+
# Syntactic, not dynamic
3690+
try_to_create_binding1() = (Foreign54607.foo = 2)
3691+
@eval try_to_create_binding2() = ($(GlobalRef(Foreign54607, :foo)) = 2)
3692+
function global_create_binding()
3693+
global bar
3694+
bar = 3
3695+
end
3696+
baz = 4
3697+
begin;
3698+
for i = 1:10; end
3699+
compiled_assign = 5
3700+
end
3701+
@eval $(GlobalRef(Foreign54607, :gr_assign)) = 6
3702+
end
3703+
@test_throws ErrorException (Foreign54607.foo = 1)
3704+
@test_throws ErrorException Foreign54607.try_to_create_binding1()
3705+
@test_throws ErrorException Foreign54607.try_to_create_binding2()
3706+
@test_throws ErrorException begin
3707+
for i = 1:10; end
3708+
(Foreign54607.foo = 1)
3709+
end
3710+
@test_throws ErrorException @eval (GlobalRef(Foreign54607, :gr_assign2)) = 7
3711+
Foreign54607.global_create_binding()
3712+
@test isdefined(Foreign54607, :bar)
3713+
@test isdefined(Foreign54607, :baz)
3714+
@test isdefined(Foreign54607, :compiled_assign)
3715+
@test isdefined(Foreign54607, :gr_assign)
3716+
Foreign54607.bar = 8
3717+
@test Foreign54607.bar == 8
3718+
begin
3719+
for i = 1:10; end
3720+
Foreign54607.bar = 9
3721+
end
3722+
@test Foreign54607.bar == 9

0 commit comments

Comments
 (0)