diff --git a/base/runtime_internals.jl b/base/runtime_internals.jl index 645aa55c538b4..0765960c97ab3 100644 --- a/base/runtime_internals.jl +++ b/base/runtime_internals.jl @@ -966,6 +966,19 @@ typeintersect(@nospecialize(a), @nospecialize(b)) = (@_total_meta; ccall(:jl_typ morespecific(@nospecialize(a), @nospecialize(b)) = (@_total_meta; ccall(:jl_type_morespecific, Cint, (Any, Any), a::Type, b::Type) != 0) morespecific(a::Method, b::Method) = ccall(:jl_method_morespecific, Cint, (Any, Any), a, b) != 0 +""" + morespecific!(m::Method) + +Disallow adding methods more specific than `m`. +""" +function morespecific!(m::Method) + mt = get_methodtable(m) + # check that all Methods in this table are morespecific than m + # so we might avoid disabling a table that might get used for more than just subsets of m + all(m2 -> m === m2 || morespecific(m2, m), MethodList(mt)) || error("unsupported Method to disable") + setfield!(mt, :frozen, 0x1, :monotonic) +end + """ fieldoffset(type, i) diff --git a/src/datatype.c b/src/datatype.c index c78b00fdd2245..44dedce8a3c02 100644 --- a/src/datatype.c +++ b/src/datatype.c @@ -54,7 +54,7 @@ JL_DLLEXPORT jl_methtable_t *jl_new_method_table(jl_sym_t *name, jl_module_t *mo mt->backedges = NULL; JL_MUTEX_INIT(&mt->writelock, "methodtable->writelock"); mt->offs = 0; - mt->frozen = 0; + jl_atomic_store_relaxed(&mt->frozen, 0); return mt; } diff --git a/src/gf.c b/src/gf.c index fc2e62ebff96b..fcf25fb40dd39 100644 --- a/src/gf.c +++ b/src/gf.c @@ -331,7 +331,7 @@ jl_datatype_t *jl_mk_builtin_func(jl_datatype_t *dt, const char *name, jl_fptr_a (jl_value_t*)mi, 1, ~(size_t)0); jl_typemap_insert(&mt->cache, (jl_value_t*)mt, newentry, 0); - mt->frozen = 1; + jl_atomic_store_relaxed(&mt->frozen, 1); JL_GC_POP(); return dt; } @@ -772,7 +772,7 @@ static int reset_mt_caches(jl_methtable_t *mt, void *env) { // removes all method caches // this might not be entirely safe (GC or MT), thus we only do it very early in bootstrapping - if (!mt->frozen) { // make sure not to reset builtin functions + if (!jl_atomic_load_relaxed(&mt->frozen)) { // make sure not to reset frozen functions jl_atomic_store_release(&mt->leafcache, (jl_genericmemory_t*)jl_an_empty_memory_any); jl_atomic_store_release(&mt->cache, jl_nothing); } diff --git a/src/jltypes.c b/src/jltypes.c index 11f1d11a14edc..8b6e50dffdb8e 100644 --- a/src/jltypes.c +++ b/src/jltypes.c @@ -3038,9 +3038,9 @@ void jl_init_types(void) JL_GC_DISABLED jl_methtable_type->name->names = jl_perm_symsvec(11, "name", "defs", "leafcache", "cache", "max_args", "module", "backedges", - "", "", "offs", ""); + "", "", "offs", "frozen"); const static uint32_t methtable_constfields[1] = { 0x00000020 }; // (1<<5); - const static uint32_t methtable_atomicfields[1] = { 0x0000001e }; // (1<<1)|(1<<2)|(1<<3)|(1<<4); + const static uint32_t methtable_atomicfields[1] = { 0x00000041e }; // (1<<1)|(1<<2)|(1<<3)|(1<<4)|(1<<10); jl_methtable_type->name->constfields = methtable_constfields; jl_methtable_type->name->atomicfields = methtable_atomicfields; jl_precompute_memoized_dt(jl_methtable_type, 1); diff --git a/src/julia.h b/src/julia.h index 7bb5f31eda708..837724cc5253d 100644 --- a/src/julia.h +++ b/src/julia.h @@ -791,7 +791,7 @@ typedef struct _jl_methtable_t { jl_array_t *backedges; // (sig, caller::MethodInstance) pairs jl_mutex_t writelock; uint8_t offs; // 0, or 1 to skip splitting typemap on first (function) argument - uint8_t frozen; // whether this accepts adding new methods + _Atomic(uint8_t) frozen; // whether this accepts adding new methods } jl_methtable_t; typedef struct { diff --git a/src/method.c b/src/method.c index 629816319b334..a0badc5b11e8d 100644 --- a/src/method.c +++ b/src/method.c @@ -1226,8 +1226,8 @@ JL_DLLEXPORT jl_method_t* jl_method_def(jl_svec_t *argdata, mt = jl_method_table_for(argtype); if ((jl_value_t*)mt == jl_nothing) jl_error("Method dispatch is unimplemented currently for this method signature"); - if (mt->frozen) - jl_error("cannot add methods to a builtin function"); + if (jl_atomic_load_acquire(&mt->frozen)) + jl_error("cannot add methods to or modify methods of a frozen function"); assert(jl_is_linenode(functionloc)); jl_sym_t *file = (jl_sym_t*)jl_linenode_file(functionloc); @@ -1236,6 +1236,7 @@ JL_DLLEXPORT jl_method_t* jl_method_def(jl_svec_t *argdata, int32_t line = jl_linenode_line(functionloc); // TODO: derive our debug name from the syntax instead of the type + assert(mt != NULL); jl_methtable_t *kwmt = mt == jl_kwcall_mt ? jl_kwmethod_table_for(argtype) : mt; // if we have a kwcall, try to derive the name from the callee argument method table name = (kwmt ? kwmt : mt)->name; diff --git a/src/staticdata.c b/src/staticdata.c index af4527cbc143f..a432dd0ad3cde 100644 --- a/src/staticdata.c +++ b/src/staticdata.c @@ -975,7 +975,7 @@ static void jl_insert_into_serialization_queue(jl_serializer_state *s, jl_value_ } else if (jl_typetagis(v, jl_typename_type)) { jl_typename_t *tn = (jl_typename_t*)v; - if (tn->mt != NULL && !tn->mt->frozen) { + if (tn->mt != NULL && !jl_atomic_load_relaxed(&tn->mt->frozen)) { jl_methtable_t * new_methtable = (jl_methtable_t *)ptrhash_get(&new_methtables, tn->mt); if (new_methtable != HT_NOTFOUND) record_field_change((jl_value_t **)&tn->mt, (jl_value_t*)new_methtable); diff --git a/test/core.jl b/test/core.jl index 5ba0e99e730d4..8440f4f21902a 100644 --- a/test/core.jl +++ b/test/core.jl @@ -35,7 +35,7 @@ for (T, c) in ( (Core.CodeInstance, [:next, :min_world, :max_world, :inferred, :debuginfo, :ipo_purity_bits, :invoke, :specptr, :specsigflags, :precompile]), (Core.Method, [:primary_world, :deleted_world]), (Core.MethodInstance, [:cache, :flags]), - (Core.MethodTable, [:defs, :leafcache, :cache, :max_args]), + (Core.MethodTable, [:defs, :leafcache, :cache, :max_args, :frozen]), (Core.TypeMapEntry, [:next, :min_world, :max_world]), (Core.TypeMapLevel, [:arg1, :targ, :name1, :tname, :list, :any]), (Core.TypeName, [:cache, :linearcache]), @@ -2650,7 +2650,7 @@ for f in (:Any, :Function, :(Core.Builtin), :(Union{Nothing, Type}), :(Union{typ @test_throws ErrorException("Method dispatch is unimplemented currently for this method signature") @eval (::$f)() = 1 end for f in (:(Core.getfield), :((::typeof(Core.getfield))), :((::Core.IntrinsicFunction))) - @test_throws ErrorException("cannot add methods to a builtin function") @eval $f() = 1 + @test_throws ErrorException("cannot add methods to or modify methods of a frozen function") @eval $f() = 1 end # issue #33370 diff --git a/test/reflection.jl b/test/reflection.jl index 634390e0680d1..6855cf969b453 100644 --- a/test/reflection.jl +++ b/test/reflection.jl @@ -1298,3 +1298,16 @@ end @test Base.infer_return_type(code_lowered, (Any,Any)) == Vector{Core.CodeInfo} @test methods(Union{}) == Any[m.method for m in Base._methods_by_ftype(Tuple{Core.TypeofBottom, Vararg}, 1, Base.get_world_counter())] # issue #55187 + +# disallow adding new methods +f_sealed(x::Int) = x+1 +f_sealed(x::Integer) = x+2 +@test_throws( + ErrorException("unsupported Method to disable"), + Base.morespecific!(which(f_sealed, (Int,))) +) +Base.morespecific!(which(f_sealed, (Integer,))) +@test_throws( + ErrorException("cannot add methods to or modify methods of a frozen function"), + @eval f_sealed(x::Float64) = x+2 +)