diff --git a/src/codeedges.jl b/src/codeedges.jl index 10181c8..0efdf93 100644 --- a/src/codeedges.jl +++ b/src/codeedges.jl @@ -243,24 +243,25 @@ function direct_links!(cl::CodeLinks, src::CodeInfo) icl = CodeLinks(cl.thismod, arg1) add_inner!(cl, icl, i) continue - elseif isexpr(stmt, :method) - if length(stmt.args) === 1 + elseif isexpr(stmt, :method) || is_define_method_call_2arg(stmt) || is_define_method_call_4arg(stmt) + if ismethod1(stmt) # A function with no methods was defined. Associate its new binding to it. - name = stmt.args[1] - if isa(name, Symbol) - name = GlobalRef(cl.thismod, name) + name = method_name(stmt) + if isa(name, Symbol) || isa(name, QuoteNode) + name = isa(name, QuoteNode) ? name.value : name + mmod = method_module(stmt) + name = GlobalRef(mmod !== nothing ? mmod : cl.thismod, name::Symbol) end - if !isa(name, GlobalRef) - error("name ", typeof(name), " not recognized") + if isa(name, GlobalRef) + assign = get!(Vector{Int}, cl.nameassigns, name) + push!(assign, i) + targetstore = get!(Links, cl.namepreds, name) + target = P(name, targetstore) + add_links!(target, stmt, cl) end - assign = get!(Vector{Int}, cl.nameassigns, name) - push!(assign, i) - targetstore = get!(Links, cl.namepreds, name) - target = P(name, targetstore) - add_links!(target, stmt, cl) - elseif length(stmt.args) === 3 && (arg3 = stmt.args[3]; arg3 isa CodeInfo) # method definition + elseif ismethod3(stmt) && (mbody = method_body(stmt); mbody isa CodeInfo) # method definition # A method was defined for an existing function. - icl = CodeLinks(cl.thismod, arg3) + icl = CodeLinks(cl.thismod, mbody) add_inner!(cl, icl, i) end rhs = stmt @@ -957,7 +958,7 @@ function add_typedefs!(isrequired, src::CodeInfo, edges::CodeEdges, (typedef_blo for s in var.succs s ∈ norequire && continue stmt2 = stmts[s] - if isexpr(stmt2, :method) && (fname = (stmt2::Expr).args[1]; fname === false || fname === nothing) + if ismethod(stmt2) && (fname = method_name(stmt2::Expr); fname === false || fname === nothing) isrequired[s] = true end end @@ -973,7 +974,7 @@ function add_typedefs!(isrequired, src::CodeInfo, edges::CodeEdges, (typedef_blo while i <= length(stmts) && !ismethod3(stmts[i]) i += 1 end - if i <= length(stmts) && (stmts[i]::Expr).args[1] == false + if i <= length(stmts) && method_name(stmts[i]::Expr) == false tpreds = terminal_preds(i, edges) if minimum(tpreds) == idx && i ∉ norequire changed |= !isrequired[i] diff --git a/src/signatures.jl b/src/signatures.jl index 6fe00ac..8270d61 100644 --- a/src/signatures.jl +++ b/src/signatures.jl @@ -39,7 +39,7 @@ If no 3-argument `:method` expression is found, `nothing` will be returned in pl function signature(interp::Interpreter, frame::Frame, @nospecialize(stmt), pc::Int) mod = moduleof(frame) lastpc = frame.pc = pc - while !isexpr(stmt, :method, 3) # wait for the 3-arg version + while !ismethod3(stmt) # wait for the 3-arg :method or 4-arg define_method if isanonymous_typedef(stmt) lastpc = pc = step_through_methoddef(interp, frame, stmt) # define an anonymous function elseif is_Typeof_for_anonymous_methoddef(stmt, frame.framecode.src.code, mod) @@ -52,8 +52,20 @@ function signature(interp::Interpreter, frame::Frame, @nospecialize(stmt), pc::I stmt = pc_expr(frame, pc) end isa(stmt, Expr) || return nothing, pc - mt = extract_method_table(frame, stmt) - sigsv = lookup(interp, frame, stmt.args[2])::SimpleVector + if is_define_method_call_4arg(stmt) + # For define_method(mod, name_or_mt, sigdata, body), args[3] may be a MethodTable + arg = stmt.args[3] + mt = isa(arg, Core.MethodTable) ? arg : nothing + else + mt = extract_method_table(frame, stmt) + end + if is_define_method_call_4arg(stmt) + # define_method(mod, name, sigdata, codeinfo): sigdata is a svec(types, sparams, lnn) + sigdata = lookup(interp, frame, stmt.args[4])::SimpleVector + sigsv = Core.svec(sigdata[1], sigdata[2]) + else + sigsv = lookup(interp, frame, stmt.args[2])::SimpleVector + end sigt = signature(sigsv) return MethodInfoKey(mt, sigt), lastpc end @@ -88,11 +100,14 @@ end function signature_top(frame, stmt::Expr, pc) @assert ismethod3(stmt) + if is_define_method_call_4arg(stmt) + return minid(stmt.args[4], frame.framecode.src.code, pc) + end return minid(stmt.args[2], frame.framecode.src.code, pc) end function step_through_methoddef(interp::Interpreter, frame::Frame, @nospecialize(stmt)) - while !isexpr(stmt, :method) + while !ismethod(stmt) pc = step_expr!(interp, frame, stmt, true) stmt = pc_expr(frame, pc) end @@ -166,8 +181,9 @@ function identify_framemethod_calls(frame::Frame) end end elseif ismethod1(stmt) - key = stmt.args[1] - key = normalize_defsig(key, frame) + key = method_name(stmt) + mmod = method_module(stmt) + key = normalize_defsig(key, mmod !== nothing ? mmod : moduleof(frame)) key = key::GlobalRef mi = get(methodinfos, key, nothing) if mi === nothing @@ -176,8 +192,9 @@ function identify_framemethod_calls(frame::Frame) mi.stop == -1 && (mi.start = i) # advance the statement # unless we've seen the method3 end elseif ismethod3(stmt) - key = stmt.args[1] - key = normalize_defsig(key, frame) + key = method_name(stmt) + mmod = method_module(stmt) + key = normalize_defsig(key, mmod !== nothing ? mmod : moduleof(frame)) if key isa GlobalRef # XXX A temporary hack to fix https://github.com/JuliaDebug/LoweredCodeUtils.jl/issues/80 # We should revisit it. @@ -186,7 +203,7 @@ function identify_framemethod_calls(frame::Frame) elseif key isa Expr # this is a module-scoped call. We don't have to worry about these because they are named continue end - msrc = stmt.args[3] + msrc = method_body(stmt) if msrc isa CodeInfo # XXX: Properly support interpolated `Core.MethodTable`. This will require using # `stmt.args[2]` instead of `stmt.args[1]` to identify the parent function. @@ -242,7 +259,8 @@ end # try to normalize `def` to `GlobalRef` representation function normalize_defsig(@nospecialize(def), mod::Module) if def isa QuoteNode - def = nameof(def.value) + val = def.value + def = val isa Symbol ? val : nameof(val) end if def isa Symbol def = GlobalRef(mod, def) @@ -337,7 +355,7 @@ function _rename_framemethods!(interp::Interpreter, frame::Frame, linetop, linebody, callee, caller = sc.linetop, sc.linebody, sc.callee, sc.caller cname = get(replacements, callee, nothing) if cname !== nothing && cname !== callee - replacename!((src.code[linetop].args[3])::CodeInfo, callee=>cname) + replacename!(method_body(src.code[linetop])::CodeInfo, callee=>cname) end end return methodinfos @@ -372,8 +390,8 @@ function find_name_caller_sig(interp::Interpreter, frame::Frame, pc::Int, name:: pc === nothing && return nothing stmt = pc_expr(frame, pc) end - body = stmt.args[3] - if normalize_defsig(stmt.args[1], frame) !== name && isa(body, CodeInfo) + body = method_body(stmt) + if normalize_defsig(method_name(stmt), frame) !== name && isa(body, CodeInfo) # This might be the GeneratedFunctionStub for a @generated method for (i, bodystmt) in enumerate(body.code) if isexpr(bodystmt, :meta) && (bodystmt::Expr).args[1] === :generated @@ -433,7 +451,7 @@ end function get_running_name(interp::Interpreter, frame::Frame, pc::Int, name::GlobalRef) nameinfo = find_name_caller_sig(interp, frame, pc, name) if nameinfo === nothing - pc = skip_until(@nospecialize(stmt)->isexpr(stmt, :method, 3), frame, pc) + pc = skip_until(@nospecialize(stmt)->ismethod3(stmt), frame, pc) pc = next_or_nothing(interp, frame, pc) return name, pc, nothing end @@ -542,7 +560,7 @@ function methoddef!(interp::Interpreter, signatures::Vector{MethodInfoKey}, fram framecode, pcin = frame.framecode, pc if ismethod3(stmt) pc3 = pc - arg1 = stmt.args[1] + arg1 = method_name(stmt) (mt, sigt), pc = signature(interp, frame, stmt, pc) meth = whichtt(sigt, mt) if isa(meth, Method) && (meth.sig <: sigt && sigt <: meth.sig) @@ -578,7 +596,8 @@ function methoddef!(interp::Interpreter, signatures::Vector{MethodInfoKey}, fram return pc, pc3 end ismethod1(stmt) || Base.invokelatest(error, "expected method opening, got ", stmt) - name = normalize_defsig(stmt.args[1], frame) + mmod = method_module(stmt) + name = normalize_defsig(method_name(stmt), mmod !== nothing ? mmod : moduleof(frame)) if isa(name, Bool) error("not valid for anonymous methods") elseif name === missing @@ -601,14 +620,15 @@ function methoddef!(interp::Interpreter, signatures::Vector{MethodInfoKey}, fram while true # methods containing inner methods may need multiple trips through this loop methinfo, pc = signature(interp, frame, stmt, pc) stmt = pc_expr(frame, pc) - while !isexpr(stmt, :method, 3) + while !ismethod3(stmt) pc = next_or_nothing(interp, frame, pc) # this should not check define, we've probably already done this once pc === nothing && return nothing # this was just `function foo end`, signal "no def" stmt = pc_expr(frame, pc) end pc3 = pc stmt = stmt::Expr - name3 = normalize_defsig(stmt.args[1], frame) + mmod3 = method_module(stmt) + name3 = normalize_defsig(method_name(stmt), mmod3 !== nothing ? mmod3 : moduleof(frame)) methinfo === nothing && (error("expected a signature"); return next_or_nothing(interp, frame, pc)), pc3 mt, sigt = methinfo # Methods like f(x::Ref{<:Real}) that use gensymmed typevars will not have the *exact* diff --git a/src/utils.jl b/src/utils.jl index e912d80..0e3dcbc 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -66,16 +66,68 @@ end ismethod(frame::Frame) = ismethod(pc_expr(frame)) ismethod3(frame::Frame) = ismethod3(pc_expr(frame)) -ismethod(stmt) = isexpr(stmt, :method) -ismethod1(stmt) = isexpr(stmt, :method, 1) -ismethod3(stmt) = isexpr(stmt, :method, 3) +# Check if a call argument refers to Core.define_method +function is_define_method_ref(@nospecialize(f)) + is_global_ref(f, Core, :define_method) && return true + @static if isdefined(Core, :define_method) + is_quotenode_egal(f, Core.define_method) && return true + end + return false +end + +# define_method(mod, name) — 2-arg form creates generic function binding +function is_define_method_call_2arg(@nospecialize(stmt)) + isexpr(stmt, :call) || return false + length(stmt.args) == 3 || return false + return is_define_method_ref(stmt.args[1]) +end + +# define_method(mod, name_or_mt, sigdata, codeinfo) — 4-arg form defines a method +function is_define_method_call_4arg(@nospecialize(stmt)) + isexpr(stmt, :call) || return false + length(stmt.args) >= 5 || return false + return is_define_method_ref(stmt.args[1]) +end + +ismethod(stmt) = isexpr(stmt, :method) || is_define_method_call_2arg(stmt) || is_define_method_call_4arg(stmt) +ismethod1(stmt) = isexpr(stmt, :method, 1) || is_define_method_call_2arg(stmt) +ismethod3(stmt) = isexpr(stmt, :method, 3) || is_define_method_call_4arg(stmt) + +# Extract the "name" argument from a method-definition statement. +# For Expr(:method, name, ...) it's args[1]; for define_method(mod, name, ...) it's args[3]. +function method_name(@nospecialize(stmt)) + if is_define_method_call_2arg(stmt) || is_define_method_call_4arg(stmt) + return stmt.args[3] + else + return stmt.args[1] + end +end + +# Extract the module from a define_method call, or nothing for :method expressions. +function method_module(@nospecialize(stmt)) + if is_define_method_call_2arg(stmt) || is_define_method_call_4arg(stmt) + return stmt.args[2] # define_method(mod, name, ...) + end + return nothing +end + +# Extract the CodeInfo body from a method3 statement. +# For Expr(:method, name, sig, body) it's args[3]; for define_method(mod, name, sigdata, body) it's args[5]. +function method_body(@nospecialize(stmt)) + if is_define_method_call_4arg(stmt) + return stmt.args[5] + else + return stmt.args[3] + end +end + function ismethod_with_name(src, stmt, target::AbstractString; reentrant::Bool=false) if reentrant name = stmt else ismethod3(stmt) || return false - name = stmt.args[1] - if name === nothing + name = method_name(stmt) + if name === nothing && isexpr(stmt, :method) name = stmt.args[2] end end diff --git a/test/codeedges.jl b/test/codeedges.jl index 902756d..5fd5590 100644 --- a/test/codeedges.jl +++ b/test/codeedges.jl @@ -15,6 +15,7 @@ function hastrackedexpr(@nospecialize(stmt)) haseval = f === :eval || (callee_matches(f, Base, :getproperty) && is_quotenode(stmt.args[2], :eval)) callee_matches(f, Core, :_typebody!) && return true, haseval callee_matches(f, Core, :_setsuper!) && return true, haseval + LoweredCodeUtils.is_define_method_ref(f) && return true, haseval f === :include && return true, haseval elseif stmt.head === :thunk any(s->any(hastrackedexpr(s)), stmt.args[1].code) && return true, haseval