From 5d33f2c476b8164243d3a542d9652b8fbb7b902c Mon Sep 17 00:00:00 2001 From: Keno Fischer Date: Thu, 2 Oct 2025 23:00:40 +0000 Subject: [PATCH] Extend backdating checks for imports and globals Previously, backdated binding warnings were only emitted for constants. This commit extends the backdating mechanism to handle imports and globals. This change was requested by triage to improve compatibility of the new binding partition mechanism given issues like #58511. With this change the behavior is as follows: ```julia julia> function foo() include_string(Main, "a = 42") return a end julia> foo() WARNING: Detected access to binding `Main.a` in a world prior to its definition world. Julia 1.12 has introduced more strict world age semantics for global bindings. !!! This code may malfunction under Revise. !!! This code will error in future versions of Julia. Hint: Add an appropriate `invokelatest` around the access to this binding. To make this warning an error, and hence obtain a stack trace, use `julia --depwarn=error`. 42 ``` With `julia --depwarn=error`, the access is rejected: ```julia julia> foo() ERROR: UndefVarError: `a` not defined in `Main` ``` I am still not a huge fan of the backdating mechanism in general, but I think this is the least objectionable of the alternatives and provides a big warning to let people know that something is wrong. --- Compiler/src/Compiler.jl | 4 +- Compiler/src/abstractinterpretation.jl | 11 +- base/runtime_internals.jl | 7 +- base/show.jl | 6 + src/ast.c | 7 +- src/julia.h | 20 +-- src/julia_internal.h | 26 +++- src/module.c | 166 ++++++++++++++++--------- src/toplevel.c | 9 +- test/worlds.jl | 86 +++++++++++++ 10 files changed, 259 insertions(+), 83 deletions(-) diff --git a/Compiler/src/Compiler.jl b/Compiler/src/Compiler.jl index 55293277d7f64..ba5f893a0e971 100644 --- a/Compiler/src/Compiler.jl +++ b/Compiler/src/Compiler.jl @@ -44,7 +44,7 @@ using Core: ABIOverride, Builtin, CodeInstance, IntrinsicFunction, MethodInstanc typename, unsafe_write, write, stdout, stderr using Base: @_foldable_meta, @_gc_preserve_begin, @_gc_preserve_end, @nospecializeinfer, - PARTITION_KIND_GLOBAL, PARTITION_KIND_UNDEF_CONST, PARTITION_KIND_BACKDATED_CONST, PARTITION_KIND_DECLARED, + PARTITION_KIND_GLOBAL, PARTITION_KIND_UNDEF_CONST, PARTITION_KIND_BACKDATED_CONST, PARTITION_KIND_BACKDATED_IMPORT, PARTITION_KIND_BACKDATED_GLOBAL, PARTITION_KIND_DECLARED, PARTITION_FLAG_DEPWARN, Base, BitVector, Bottom, Callable, DataTypeFieldDesc, EffectsOverride, Filter, Generator, NUM_EFFECTS_OVERRIDES, @@ -55,7 +55,7 @@ using Base: @_foldable_meta, @_gc_preserve_begin, @_gc_preserve_end, @nospeciali datatype_pointerfree, decode_effects_override, diff_names, fieldindex, visit, generating_output, get_nospecializeinfer_sig, get_world_counter, has_free_typevars, hasgenerator, hasintersect, indexed_iterate, isType, is_file_tracked, is_function_def, - is_meta_expr, is_meta_expr_head, is_nospecialized, is_nospecializeinfer, is_defined_const_binding, + is_meta_expr, is_meta_expr_head, is_nospecialized, is_nospecializeinfer, is_backdated, is_defined_const_binding, is_some_const_binding, is_some_guard, is_some_imported, is_some_explicit_imported, is_some_binding_imported, is_valid_intrinsic_elptr, isbitsunion, isconcretedispatch, isdispatchelem, isexpr, isfieldatomic, isidentityfree, iskindtype, ismutabletypename, ismutationfree, issingletontype, isvarargtype, isvatuple, diff --git a/Compiler/src/abstractinterpretation.jl b/Compiler/src/abstractinterpretation.jl index 37d54e66d4ac6..bca8ea797600b 100644 --- a/Compiler/src/abstractinterpretation.jl +++ b/Compiler/src/abstractinterpretation.jl @@ -3650,12 +3650,13 @@ function abstract_eval_partition_load(interp::Union{AbstractInterpreter,Nothing} end end + if is_backdated(kind) + # Infer this as guard. We do not want a later definition to retroactively improve + # inference results in an earlier world. + return RTEffects(Any, UndefVarError, local_getglobal_effects) + end + if is_defined_const_binding(kind) - if kind == PARTITION_KIND_BACKDATED_CONST - # Infer this as guard. We do not want a later const definition to retroactively improve - # inference results in an earlier world. - return RTEffects(Any, UndefVarError, local_getglobal_effects) - end rt = Const(partition_restriction(partition)) return RTEffects(rt, Union{}, Effects(EFFECTS_TOTAL, inaccessiblememonly=is_mutation_free_argtype(rt) ? ALWAYS_TRUE : ALWAYS_FALSE, diff --git a/base/runtime_internals.jl b/base/runtime_internals.jl index ba224acf897d4..07e16d1e3394a 100644 --- a/base/runtime_internals.jl +++ b/base/runtime_internals.jl @@ -246,6 +246,8 @@ const PARTITION_KIND_DECLARED = 0x8 const PARTITION_KIND_GUARD = 0x9 const PARTITION_KIND_UNDEF_CONST = 0xa const PARTITION_KIND_BACKDATED_CONST = 0xb +const PARTITION_KIND_BACKDATED_IMPORT = 0xc +const PARTITION_KIND_BACKDATED_GLOBAL = 0xd const PARTITION_FLAG_EXPORTED = 0x10 const PARTITION_FLAG_DEPRECATED = 0x20 @@ -256,11 +258,12 @@ const PARTITION_MASK_FLAG = 0xf0 const BINDING_FLAG_ANY_IMPLICIT_EDGES = 0x8 +is_backdated(kind::UInt8) = (kind == PARTITION_KIND_BACKDATED_CONST || kind == PARTITION_KIND_BACKDATED_IMPORT || kind == PARTITION_KIND_BACKDATED_GLOBAL) is_defined_const_binding(kind::UInt8) = (kind == PARTITION_KIND_CONST || kind == PARTITION_KIND_CONST_IMPORT || kind == PARTITION_KIND_IMPLICIT_CONST || kind == PARTITION_KIND_BACKDATED_CONST) is_some_const_binding(kind::UInt8) = (is_defined_const_binding(kind) || kind == PARTITION_KIND_UNDEF_CONST) -is_some_imported(kind::UInt8) = (kind == PARTITION_KIND_IMPLICIT_GLOBAL || kind == PARTITION_KIND_IMPLICIT_CONST || kind == PARTITION_KIND_EXPLICIT || kind == PARTITION_KIND_IMPORTED) +is_some_imported(kind::UInt8) = (kind == PARTITION_KIND_IMPLICIT_GLOBAL || kind == PARTITION_KIND_IMPLICIT_CONST || kind == PARTITION_KIND_EXPLICIT || kind == PARTITION_KIND_IMPORTED || kind == PARTITION_KIND_BACKDATED_IMPORT) is_some_implicit(kind::UInt8) = (kind == PARTITION_KIND_IMPLICIT_GLOBAL || kind == PARTITION_KIND_IMPLICIT_CONST || kind == PARTITION_KIND_GUARD || kind == PARTITION_KIND_FAILED) -is_some_explicit_imported(kind::UInt8) = (kind == PARTITION_KIND_EXPLICIT || kind == PARTITION_KIND_IMPORTED) +is_some_explicit_imported(kind::UInt8) = (kind == PARTITION_KIND_EXPLICIT || kind == PARTITION_KIND_IMPORTED || kind == PARTITION_KIND_BACKDATED_IMPORT) is_some_binding_imported(kind::UInt8) = is_some_explicit_imported(kind) || kind == PARTITION_KIND_IMPLICIT_GLOBAL is_some_guard(kind::UInt8) = (kind == PARTITION_KIND_GUARD || kind == PARTITION_KIND_FAILED || kind == PARTITION_KIND_UNDEF_CONST) diff --git a/base/show.jl b/base/show.jl index 1c3f270333f60..d5fe8a3723f2d 100644 --- a/base/show.jl +++ b/base/show.jl @@ -3309,6 +3309,12 @@ function print_partition(io::IO, partition::Core.BindingPartition) if kind == PARTITION_KIND_BACKDATED_CONST print(io, "backdated constant binding to ") print(io, partition_restriction(partition)) + elseif kind == PARTITION_KIND_BACKDATED_IMPORT + print(io, "backdated explicit `import` from ") + print(io, partition_restriction(partition).globalref) + elseif kind == PARTITION_KIND_BACKDATED_GLOBAL + print(io, "backdated global variable with type ") + print(io, partition_restriction(partition)) elseif kind == PARTITION_KIND_CONST print(io, "constant binding to ") print(io, partition_restriction(partition)) diff --git a/src/ast.c b/src/ast.c index e8cdf57ad8194..4b640d77b59b8 100644 --- a/src/ast.c +++ b/src/ast.c @@ -1224,9 +1224,10 @@ JL_DLLEXPORT jl_value_t *jl_fl_lower(jl_value_t *expr, jl_module_t *inmodule, JL_DLLEXPORT jl_value_t *jl_lower(jl_value_t *expr, jl_module_t *inmodule, const char *filename, int line, size_t world, bool_t warn) { - jl_value_t *core_lower = NULL; - if (jl_core_module) - core_lower = jl_get_global_value(jl_core_module, jl_symbol("_lower"), jl_current_task->world_age); + if (!jl_core_module || !jl_boundp(jl_core_module, jl_symbol("_lower"), jl_current_task->world_age)) { + return jl_fl_lower(expr, inmodule, filename, line, world, warn); + } + jl_value_t *core_lower = jl_get_global_value(jl_core_module, jl_symbol("_lower"), jl_current_task->world_age); if (!core_lower || core_lower == jl_nothing) { return jl_fl_lower(expr, inmodule, filename, line, world, warn); } diff --git a/src/julia.h b/src/julia.h index 4e0ed0d549f20..ddcd0b87fc9f7 100644 --- a/src/julia.h +++ b/src/julia.h @@ -694,11 +694,11 @@ typedef struct _jl_weakref_t { // // We may make this list more permissive in the future. // -// Finally, PARTITION_KIND_BACKDATED_CONST is a special case, and the only case where we may replace an -// existing partition by a different partition kind in the same world age. As such, it needs special -// support in inference. Any partition kind that may be replaced by a PARTITION_KIND_BACKDATED_CONST -// must be inferred accordingly. PARTITION_KIND_BACKDATED_CONST is intended as a temporary compatibility -// measure. The following kinds may be replaced by PARTITION_KIND_BACKDATED_CONST: +// Finally, PARTITION_KIND_BACKDATED_* are special cases, and the only cases where we may replace an +// existing partition by a different partition kind in the same world age. As such, they need special +// support in inference. Any partition kind that may be replaced by a PARTITION_KIND_BACKDATED_* +// must be inferred accordingly. PARTITION_KIND_BACKDATED_* are intended as a temporary compatibility +// measure. The following kinds may be replaced by any backdated partition kind: // - PARTITION_KIND_GUARD // - PARTITION_KIND_FAILED // - PARTITION_KIND_DECLARED @@ -743,11 +743,17 @@ enum jl_partition_kind { // Backated constant. A constant that was backdated for compatibility. In all other // ways equivalent to PARTITION_KIND_CONST, but prints a warning on access PARTITION_KIND_BACKDATED_CONST = 0xb, + // Backdated import. An explicit import that was backdated for compatibility. In all other + // ways equivalent to PARTITION_KIND_IMPORTED, but prints a warning on access + PARTITION_KIND_BACKDATED_IMPORT = 0xc, + // Backdated global. A global that was backdated for compatibility. In all other + // ways equivalent to PARTITION_KIND_GLOBAL, but prints a warning on access + PARTITION_KIND_BACKDATED_GLOBAL = 0xd, // This is not a real binding kind, but can be used to ask for a re-resolution // of the implicit binding kind - PARTITION_FAKE_KIND_IMPLICIT_RECOMPUTE = 0xc, - PARTITION_FAKE_KIND_CYCLE = 0xd + PARTITION_FAKE_KIND_IMPLICIT_RECOMPUTE = 0xe, + PARTITION_FAKE_KIND_CYCLE = 0xf }; static const uint8_t PARTITION_MASK_KIND = 0x0f; diff --git a/src/julia_internal.h b/src/julia_internal.h index 4488388bb9875..b6fb6645c036e 100644 --- a/src/julia_internal.h +++ b/src/julia_internal.h @@ -979,6 +979,9 @@ JL_DLLEXPORT jl_binding_partition_t *jl_replace_binding_locked(jl_binding_t *b J jl_binding_partition_t *old_bpart, jl_value_t *restriction_val, enum jl_partition_kind kind, size_t new_world) JL_GLOBALLY_ROOTED; JL_DLLEXPORT jl_binding_partition_t *jl_replace_binding_locked2(jl_binding_t *b JL_PROPAGATES_ROOT, jl_binding_partition_t *old_bpart, jl_value_t *restriction_val, size_t kind, size_t new_world) JL_GLOBALLY_ROOTED; +jl_binding_partition_t *jl_backdate_binding_partition(jl_binding_t *b JL_PROPAGATES_ROOT, + jl_binding_partition_t *bpart, jl_binding_partition_t *new_bpart, + jl_value_t *restriction, enum jl_partition_kind backdated_kind, size_t new_world) JL_GLOBALLY_ROOTED; JL_DLLEXPORT void jl_update_loaded_bpart(jl_binding_t *b, jl_binding_partition_t *bpart); extern jl_array_t *jl_module_init_order JL_GLOBALLY_ROOTED; extern htable_t jl_current_modules JL_GLOBALLY_ROOTED; @@ -998,11 +1001,11 @@ jl_method_t *jl_make_opaque_closure_method(jl_module_t *module, jl_value_t *name JL_DLLEXPORT int jl_is_valid_oc_argtype(jl_tupletype_t *argt, jl_method_t *source); STATIC_INLINE int jl_bkind_is_some_import(enum jl_partition_kind kind) JL_NOTSAFEPOINT { - return kind == PARTITION_KIND_IMPLICIT_CONST || kind == PARTITION_KIND_IMPLICIT_GLOBAL || kind == PARTITION_KIND_EXPLICIT || kind == PARTITION_KIND_IMPORTED; + return kind == PARTITION_KIND_IMPLICIT_CONST || kind == PARTITION_KIND_IMPLICIT_GLOBAL || kind == PARTITION_KIND_EXPLICIT || kind == PARTITION_KIND_IMPORTED || kind == PARTITION_KIND_BACKDATED_IMPORT; } STATIC_INLINE int jl_bkind_is_some_explicit_import(enum jl_partition_kind kind) JL_NOTSAFEPOINT { - return kind == PARTITION_KIND_EXPLICIT || kind == PARTITION_KIND_IMPORTED; + return kind == PARTITION_KIND_EXPLICIT || kind == PARTITION_KIND_IMPORTED || kind == PARTITION_KIND_BACKDATED_IMPORT; } STATIC_INLINE int jl_bkind_is_some_guard(enum jl_partition_kind kind) JL_NOTSAFEPOINT { @@ -1025,6 +1028,10 @@ STATIC_INLINE int jl_bkind_is_real_constant(enum jl_partition_kind kind) JL_NOTS return kind == PARTITION_KIND_IMPLICIT_CONST || kind == PARTITION_KIND_CONST || kind == PARTITION_KIND_CONST_IMPORT; } +STATIC_INLINE int jl_bkind_is_backdated(enum jl_partition_kind kind) JL_NOTSAFEPOINT { + return kind == PARTITION_KIND_BACKDATED_CONST || kind == PARTITION_KIND_BACKDATED_IMPORT || kind == PARTITION_KIND_BACKDATED_GLOBAL; +} + JL_DLLEXPORT jl_binding_partition_t *jl_get_binding_partition(jl_binding_t *b JL_PROPAGATES_ROOT, size_t world) JL_GLOBALLY_ROOTED; JL_DLLEXPORT jl_binding_partition_t *jl_get_binding_partition_with_hint(jl_binding_t *b JL_PROPAGATES_ROOT, jl_binding_partition_t *previous_part, size_t world) JL_GLOBALLY_ROOTED; JL_DLLEXPORT jl_binding_partition_t *jl_get_binding_partition_all(jl_binding_t *b JL_PROPAGATES_ROOT, size_t min_world, size_t max_world) JL_GLOBALLY_ROOTED; @@ -1059,6 +1066,21 @@ STATIC_INLINE void jl_walk_binding_inplace(jl_binding_t **bnd, jl_binding_partit } } +extern void check_backdated_binding(jl_binding_t *b, enum jl_partition_kind kind) JL_NOTSAFEPOINT; + +STATIC_INLINE void jl_walk_binding_inplace_stop_at_backdated(jl_binding_t **bnd, jl_binding_partition_t **bpart, size_t world, int stop_at_backdated) JL_NOTSAFEPOINT +{ + while (1) { + enum jl_partition_kind kind = jl_binding_kind(*bpart); + if (stop_at_backdated && jl_bkind_is_backdated(kind)) + return; + if (!jl_bkind_is_some_explicit_import(kind) && kind != PARTITION_KIND_IMPLICIT_GLOBAL) + return; + *bnd = (jl_binding_t*)(*bpart)->restriction; + *bpart = jl_get_binding_partition(*bnd, world); + } +} + STATIC_INLINE void jl_walk_binding_inplace_depwarn(jl_binding_t **bnd, jl_binding_partition_t **bpart, size_t world, int *depwarn) JL_NOTSAFEPOINT { int passed_explicit = 0; diff --git a/src/module.c b/src/module.c index 24ac81266da47..3ecd34fcb06e1 100644 --- a/src/module.c +++ b/src/module.c @@ -301,7 +301,7 @@ struct implicit_search_resolution jl_resolve_implicit_import(jl_binding_t *b, mo imp_resolution.binding_or_const = tempbpart->restriction; imp_resolution.debug_only_ultimate_binding = (jl_binding_t*)tempbpart->restriction; imp_resolution.ultimate_kind = PARTITION_KIND_IMPLICIT_GLOBAL; - } else if (kind == PARTITION_KIND_GLOBAL || kind == PARTITION_KIND_DECLARED || kind == PARTITION_KIND_BACKDATED_CONST) { + } else if (kind == PARTITION_KIND_GLOBAL || kind == PARTITION_KIND_DECLARED || kind == PARTITION_KIND_BACKDATED_CONST || kind == PARTITION_KIND_BACKDATED_GLOBAL) { imp_resolution.binding_or_const = (jl_value_t *)tempb; imp_resolution.debug_only_ultimate_binding = tempb; imp_resolution.ultimate_kind = PARTITION_KIND_IMPLICIT_GLOBAL; @@ -553,6 +553,86 @@ jl_module_t *jl_new_module_(jl_sym_t *name, jl_module_t *parent, uint8_t default return m; } +// Helper function to backdate a binding partition +// Precondition: world_counter_lock is held +// Returns the new backdated partition list head, or NULL if backdating is not needed +jl_binding_partition_t *jl_backdate_binding_partition( + jl_binding_t *b, jl_binding_partition_t *bpart, jl_binding_partition_t *new_bpart, + jl_value_t *restriction, enum jl_partition_kind backdated_kind, size_t new_world) +{ + // Check if we should backdate + int need_backdate = new_world; + if (need_backdate) { + // We will backdate as long as this partition was never explicitly + // declared const, global, or imported. + jl_binding_partition_t *prev_bpart = bpart; + for (;;) { + enum jl_partition_kind prev_kind = jl_binding_kind(prev_bpart); + if (jl_bkind_is_some_constant(prev_kind) || prev_kind == PARTITION_KIND_GLOBAL || + jl_bkind_is_some_import(prev_kind)) { + need_backdate = 0; + break; + } + size_t prev_bpart_min_world = jl_atomic_load_relaxed(&prev_bpart->min_world); + if (prev_bpart_min_world == 0) + break; + prev_bpart = jl_get_binding_partition(b, prev_bpart_min_world - 1); + } + } + + if (!need_backdate) + return NULL; + + // If backdate is required, replace each existing partition by a new one. + // We can't use one binding to cover the entire range, because we need to + // keep the flags partitioned. + // Only overwrite partitions that are guards (GUARD, FAILED, or DECLARED). + // Do not overwrite implicit imports. + jl_binding_partition_t *prev_bpart = bpart; + jl_binding_partition_t *backdate_bpart = new_binding_partition(); + jl_binding_partition_t *new_prev_bpart = backdate_bpart; + while (1) { + enum jl_partition_kind prev_kind = jl_binding_kind(prev_bpart); + enum jl_partition_kind kind_to_use; + + // Determine the appropriate backdated kind based on the original kind + // Only overwrite guard partitions (GUARD, FAILED, DECLARED) + // Do not overwrite implicit imports + if (jl_bkind_is_some_guard(prev_kind) || prev_kind == PARTITION_KIND_DECLARED) { + kind_to_use = backdated_kind; + } else if (prev_kind == PARTITION_KIND_IMPLICIT_CONST || prev_kind == PARTITION_KIND_IMPLICIT_GLOBAL) { + // Do not backdate implicit imports + kind_to_use = prev_kind; + } else { + kind_to_use = prev_kind; + } + + backdate_bpart->kind = (size_t)kind_to_use | (prev_bpart->kind & 0xf0); + if (kind_to_use == backdated_kind) { + backdate_bpart->restriction = restriction; + if (restriction) + jl_gc_wb_fresh(backdate_bpart, restriction); + } else { + backdate_bpart->restriction = prev_bpart->restriction; + if (prev_bpart->restriction) + jl_gc_wb_fresh(backdate_bpart, prev_bpart->restriction); + } + jl_atomic_store_relaxed(&backdate_bpart->min_world, + jl_atomic_load_relaxed(&prev_bpart->min_world)); + jl_atomic_store_relaxed(&backdate_bpart->max_world, + jl_atomic_load_relaxed(&prev_bpart->max_world)); + prev_bpart = jl_atomic_load_relaxed(&prev_bpart->next); + if (!prev_bpart) + break; + jl_binding_partition_t *next_prev_bpart = new_binding_partition(); + jl_atomic_store_relaxed(&backdate_bpart->next, next_prev_bpart); + jl_gc_wb(backdate_bpart, next_prev_bpart); + backdate_bpart = next_prev_bpart; + } + jl_atomic_store_release(&new_bpart->next, new_prev_bpart); + jl_gc_wb(new_bpart, new_prev_bpart); + return new_prev_bpart; +} // Precondition: world_counter_lock is held JL_DLLEXPORT jl_binding_partition_t *jl_declare_constant_val3( @@ -595,49 +675,8 @@ JL_DLLEXPORT jl_binding_partition_t *jl_declare_constant_val3( } else { new_bpart = jl_replace_binding_locked(b, bpart, val, constant_kind, new_world); } - int need_backdate = new_world && val; - if (need_backdate) { - // We will backdate as long as this partition was never explicitly - // declared const, global, or imported. - jl_binding_partition_t *prev_bpart = bpart; - for (;;) { - enum jl_partition_kind prev_kind = jl_binding_kind(prev_bpart); - if (jl_bkind_is_some_constant(prev_kind) || prev_kind == PARTITION_KIND_GLOBAL || - jl_bkind_is_some_import(prev_kind)) { - need_backdate = 0; - break; - } - size_t prev_bpart_min_world = jl_atomic_load_relaxed(&prev_bpart->min_world); - if (prev_bpart_min_world == 0) - break; - prev_bpart = jl_get_binding_partition(b, prev_bpart_min_world - 1); - } - } - // If backdate is required, replace each existing partition by a new one. - // We can't use one binding to cover the entire range, because we need to - // keep the flags partitioned. - if (need_backdate) { - jl_binding_partition_t *prev_bpart = bpart; - jl_binding_partition_t *backdate_bpart = new_binding_partition(); - new_prev_bpart = backdate_bpart; - while (1) { - backdate_bpart->kind = (size_t)PARTITION_KIND_BACKDATED_CONST | (prev_bpart->kind & 0xf0); - backdate_bpart->restriction = val; - jl_atomic_store_relaxed(&backdate_bpart->min_world, - jl_atomic_load_relaxed(&prev_bpart->min_world)); - jl_gc_wb_fresh(backdate_bpart, val); - jl_atomic_store_relaxed(&backdate_bpart->max_world, - jl_atomic_load_relaxed(&prev_bpart->max_world)); - prev_bpart = jl_atomic_load_relaxed(&prev_bpart->next); - if (!prev_bpart) - break; - jl_binding_partition_t *next_prev_bpart = new_binding_partition(); - jl_atomic_store_relaxed(&backdate_bpart->next, next_prev_bpart); - jl_gc_wb(backdate_bpart, next_prev_bpart); - backdate_bpart = next_prev_bpart; - } - jl_atomic_store_release(&new_bpart->next, new_prev_bpart); - jl_gc_wb(new_bpart, new_prev_bpart); + if (new_world && val) { + new_prev_bpart = jl_backdate_binding_partition(b, bpart, new_bpart, val, PARTITION_KIND_BACKDATED_CONST, new_world); } } JL_GC_POP(); @@ -878,9 +917,9 @@ static NOINLINE void print_backdate_admonition(jl_binding_t *b) JL_NOTSAFEPOINT jl_symbol_name(b->globalref->mod->name), jl_symbol_name(b->globalref->name)); } -static inline void check_backdated_binding(jl_binding_t *b, enum jl_partition_kind kind) JL_NOTSAFEPOINT +void check_backdated_binding(jl_binding_t *b, enum jl_partition_kind kind) JL_NOTSAFEPOINT { - if (__unlikely(kind == PARTITION_KIND_BACKDATED_CONST)) { + if (__unlikely(jl_bkind_is_backdated(kind))) { // We don't want functions that inference executes speculatively to print this warning, so turn those into // an error for inference purposes. if (jl_current_task->ptls->in_pure_callback || jl_options.depwarn == JL_OPTIONS_DEPWARN_ERROR) @@ -898,12 +937,13 @@ JL_DLLEXPORT jl_value_t *jl_get_binding_value(jl_binding_t *b) JL_DLLEXPORT jl_value_t *jl_get_binding_value_in_world(jl_binding_t *b, size_t world) { jl_binding_partition_t *bpart = jl_get_binding_partition(b, world); - jl_walk_binding_inplace(&b, &bpart, world); + int stop_at_backdated = (jl_current_task->ptls->in_pure_callback || jl_options.depwarn == JL_OPTIONS_DEPWARN_ERROR); + check_backdated_binding(b, jl_binding_kind(bpart)); + jl_walk_binding_inplace_stop_at_backdated(&b, &bpart, world, stop_at_backdated); enum jl_partition_kind kind = jl_binding_kind(bpart); if (jl_bkind_is_some_guard(kind)) return NULL; if (jl_bkind_is_some_constant(kind)) { - check_backdated_binding(b, kind); return bpart->restriction; } assert(!jl_bkind_is_some_import(kind)); @@ -919,14 +959,13 @@ static jl_value_t *jl_get_binding_value_depwarn(jl_binding_t *b, size_t world) if (needs_depwarn) jl_binding_deprecation_warning(b); } - else { - jl_walk_binding_inplace(&b, &bpart, world); - } + int stop_at_backdated = (jl_current_task->ptls->in_pure_callback || jl_options.depwarn == JL_OPTIONS_DEPWARN_ERROR); + check_backdated_binding(b, jl_binding_kind(bpart)); + jl_walk_binding_inplace_stop_at_backdated(&b, &bpart, world, stop_at_backdated); enum jl_partition_kind kind = jl_binding_kind(bpart); if (jl_bkind_is_some_guard(kind)) return NULL; if (jl_bkind_is_some_constant(kind)) { - check_backdated_binding(b, kind); return bpart->restriction; } assert(!jl_bkind_is_some_import(kind)); @@ -937,12 +976,13 @@ JL_DLLEXPORT jl_value_t *jl_get_binding_value_seqcst(jl_binding_t *b) { size_t world = jl_current_task->world_age; jl_binding_partition_t *bpart = jl_get_binding_partition(b, world); - jl_walk_binding_inplace(&b, &bpart, world); + int stop_at_backdated = (jl_current_task->ptls->in_pure_callback || jl_options.depwarn == JL_OPTIONS_DEPWARN_ERROR); + check_backdated_binding(b, jl_binding_kind(bpart)); + jl_walk_binding_inplace_stop_at_backdated(&b, &bpart, world, stop_at_backdated); enum jl_partition_kind kind = jl_binding_kind(bpart); if (jl_bkind_is_some_guard(kind)) return NULL; if (jl_bkind_is_some_constant(kind)) { - check_backdated_binding(b, kind); return bpart->restriction; } assert(!jl_bkind_is_some_import(kind)); @@ -1270,9 +1310,15 @@ JL_DLLEXPORT void jl_module_import(jl_task_t *ct, jl_module_t *to, jl_module_t * jl_binding_partition_t *btopart = jl_get_binding_partition(bto, new_world); enum jl_partition_kind btokind = jl_binding_kind(btopart); if (jl_bkind_is_some_implicit(btokind)) { - jl_binding_partition_t *new_bpart = jl_replace_binding_locked(bto, btopart, (jl_value_t*)b, (explici != 0) ? PARTITION_KIND_IMPORTED : PARTITION_KIND_EXPLICIT, new_world); + enum jl_partition_kind import_kind = (explici != 0) ? PARTITION_KIND_IMPORTED : PARTITION_KIND_EXPLICIT; + jl_binding_partition_t *new_bpart = jl_replace_binding_locked(bto, btopart, (jl_value_t*)b, import_kind, new_world); if (jl_atomic_load_relaxed(&new_bpart->max_world) == ~(size_t)0) jl_add_binding_backedge(b, (jl_value_t*)bto); + + // Backdate imports similar to constants + // Only overwrite guard partitions, not implicit imports + jl_backdate_binding_partition(bto, btopart, new_bpart, (jl_value_t*)b, PARTITION_KIND_BACKDATED_IMPORT, new_world); + jl_atomic_store_release(&jl_world_counter, new_world); } else { @@ -1471,20 +1517,20 @@ JL_DLLEXPORT int jl_boundp(jl_module_t *m, jl_sym_t *var, int allow_import) // u jl_binding_partition_t *bpart = jl_get_binding_partition(b, jl_current_task->world_age); if (!bpart) return 0; + int stop_at_backdated = (jl_current_task->ptls->in_pure_callback || jl_options.depwarn == JL_OPTIONS_DEPWARN_ERROR); if (!allow_import) { if (!bpart || jl_bkind_is_some_import(jl_binding_kind(bpart))) return 0; } else { - jl_walk_binding_inplace(&b, &bpart, jl_current_task->world_age); + jl_walk_binding_inplace_stop_at_backdated(&b, &bpart, jl_current_task->world_age, stop_at_backdated); } enum jl_partition_kind kind = jl_binding_kind(bpart); if (jl_bkind_is_some_guard(kind)) return 0; + if (__unlikely(jl_bkind_is_backdated(kind))) { + return !stop_at_backdated; + } if (jl_bkind_is_defined_constant(kind)) { - if (__unlikely(kind == PARTITION_KIND_BACKDATED_CONST)) { - return !(jl_current_task->ptls->in_pure_callback || jl_options.depwarn == JL_OPTIONS_DEPWARN_ERROR); - } - // N.B.: No backdated admonition for isdefined return 1; } return jl_atomic_load(&b->value) != NULL; diff --git a/src/toplevel.c b/src/toplevel.c index 4622a9e8b4ce0..99815978f2427 100644 --- a/src/toplevel.c +++ b/src/toplevel.c @@ -308,15 +308,20 @@ void jl_declare_global(jl_module_t *m, jl_value_t *arg, jl_value_t *set_type, in goto check_type; } check_safe_newbinding(gm, gs); + jl_binding_partition_t *new_bpart = NULL; if (jl_atomic_load_relaxed(&bpart->min_world) == new_world) { bpart->kind = new_kind | (bpart->kind & PARTITION_MASK_FLAG); bpart->restriction = global_type; if (global_type) jl_gc_wb(bpart, global_type); - continue; + new_bpart = bpart; } else { - jl_replace_binding_locked(b, bpart, global_type, new_kind, new_world); + new_bpart = jl_replace_binding_locked(b, bpart, global_type, new_kind, new_world); } + + // Backdate globals similar to constants and imports + // Only overwrite guard partitions, not implicit imports + jl_backdate_binding_partition(b, bpart, new_bpart, global_type, PARTITION_KIND_BACKDATED_GLOBAL, new_world); break; } else if (set_type) { if (jl_bkind_is_some_constant(kind)) { diff --git a/test/worlds.jl b/test/worlds.jl index 96685129c9e99..03d300ac9339b 100644 --- a/test/worlds.jl +++ b/test/worlds.jl @@ -616,3 +616,89 @@ end # Test that the hash function works without world age issues @test hash(bar59429, UInt(0)) isa UInt end + +# Backdating tests +module BackdateConstTest + using Test + const world_before = Base.get_world_counter() + f() = BACKDATED_CONST # defined in old world + const world_after_f = Base.get_world_counter() + const BACKDATED_CONST = 42 # defined in new world + @test BACKDATED_CONST == 42 + if Base.JLOptions().depwarn <= 1 + # depwarn=no or yes: backdating works, emits warning + # Call f from the old world (before BACKDATED_CONST was defined) + @test_warn "Detected access to binding `BackdateConstTest.BACKDATED_CONST`" Base.invoke_in_world(world_after_f, f) + @test Base.invoke_in_world(world_before, isdefinedglobal, @__MODULE__, :BACKDATED_CONST) + else + # depwarn=error: backdating disabled + @test !Base.invoke_in_world(world_before, isdefinedglobal, @__MODULE__, :BACKDATED_CONST) + @test_throws UndefVarError Base.invoke_in_world(world_after_f, f) + end +end + +module BackdateImportTest + using Test + module Source + exported_value = 42 # Use a value, not a function + end + const world_before = Base.get_world_counter() + g() = exported_value # defined in old world + const world_after_g = Base.get_world_counter() + import .Source: exported_value # import in new world + @test exported_value == 42 + if Base.JLOptions().depwarn <= 1 + @test_warn "Detected access to binding `BackdateImportTest.exported_value`" Base.invoke_in_world(world_after_g, g) + @test Base.invoke_in_world(world_before, isdefinedglobal, @__MODULE__, :exported_value) + else + @test !Base.invoke_in_world(world_before, isdefinedglobal, @__MODULE__, :exported_value) + @test_throws UndefVarError Base.invoke_in_world(world_after_g, g) + end +end + +module BackdateGlobalTest + using Test + const world_before = Base.get_world_counter() + h() = backdated_global # defined in old world + const world_after_h = Base.get_world_counter() + global backdated_global::Int = 123 # defined in new world + @test backdated_global == 123 + if Base.JLOptions().depwarn <= 1 + @test_warn "Detected access to binding `BackdateGlobalTest.backdated_global`" Base.invoke_in_world(world_after_h, h) + @test Base.invoke_in_world(world_before, isdefinedglobal, @__MODULE__, :backdated_global) + else + @test !Base.invoke_in_world(world_before, isdefinedglobal, @__MODULE__, :backdated_global) + @test_throws UndefVarError Base.invoke_in_world(world_after_h, h) + end +end + +# Test case from issue #58511 +module BackdateIssue58511 + using Test + function foo() + eval(:(a = 42)) + return a + end + if Base.JLOptions().depwarn <= 1 + @test_warn "Detected access to binding `BackdateIssue58511.a`" foo() + else + @test_throws UndefVarError foo() + end +end + +# Test that implicit imports are NOT backdated +module BackdateNoImplicitTest + using Test + module Source1 + export shared_name + shared_name() = 1 + end + module Source2 + export shared_name + shared_name() = 2 + end + const world_before = Base.get_world_counter() + using .Source1, .Source2 + @test_throws UndefVarError shared_name() + @test !Base.invoke_in_world(world_before, isdefinedglobal, @__MODULE__, :shared_name) +end