Pirate Hunter


#1

I made a script to hunt out type piracy.
It isn’t perfect, but its not bad.

invoke it by calling hunt on a module, function or list of methods.
Or if you just call hunt() from the REPL it will search all modules currenlty loaded from Main.
There are 27 pirate actions detected within the standard libraries.

These will likely show up when searching your own modules as you probably reference at very least some standard libraries at some point.
So you might want to do some post processing to filter tthem out

# PirateHunter.jl

###############


function all_methods(mainmod::Module=Main)
    found_methods = Vector{Method}()

    # We begin with the assumption nothing in Core.Compiler should be indexed
    done_modules = Set{Module}()
    done_functions = Set()
        

    function index(mod::Module)
        mod ∈ done_modules && return
        push!(done_modules, mod)
        @info "Indexing" mod=mod

        for name in names(mod; all=true)
            val = try
                String(name)[1]=='#' && continue
                Core.eval(mod, name)
            catch err
                @warn "while working out what things was, had error" thing=name mod=mod exception=err
            end

            val isa Core.Compiler.BitArray{1} && continue  # This type is unhashable and unprintable

            index(val)
        end
    end

    function index_func(f)
        try
            f ∈ done_functions && return
            push!(done_functions, f)
            append!(found_methods, methods(f))
        catch err
            @warn exception=err
        end
    end

    function index(x)
        index_func(x) 
    end

    function index(x::Function)
        index_func(x)
        index_func(typeof(x))  # all functionas have a type
    end



    #########
    # Invoke it
    index(mainmod)  # first arg is dummy

    @info "indexing complete" length(found_methods)
    return found_methods
end


##################################

union_types(U) = getproperty.(U, propertynames(U))

get_module(x) = get_module(typeof(x))
get_module(meth::Method) = meth.module
get_module(U::UnionAll) = get_module(U.body)
get_module(T::DataType) = T.name.module
function get_module(U::Union)
    modules = unique(get_module, union_types)
    if length(modules)==1
        return first(modules)
    else
        error("There is not a common module for all types in $U")
    end
end


hunt(mod::Module=Main) = hunt(all_methods(mod))
hunt(f) = hunt(methods(f))

function hunt(methods::Union{Base.MethodList, AbstractVector{Method}})
    function is_pirate(meth::Method)
        method_module = get_module(meth)
        method_pkg = Base.PkgId(method_module)

        meth.sig isa UnionAll && return false # TODO: Deal with this properly
        length(meth.sig.parameters) < 2 && return false  # can't pirate without args


        function_type = meth.sig.parameters[1]
        local function_module
        try
            function_module = get_module(function_type)
            function_pkg = Base.PkgId(function_module)
            Base.PkgId(function_module) == method_pkg && return false  # can't pirate own function

            # Base and Core can't pirate each other
            ((function_pkg == Base.PkgId(Base) &&  method_pkg == Base.PkgId(Core)) ||
            function_pkg == Base.PkgId(Core) &&  method_pkg == Base.PkgId(Base)) && return false
        catch err
            # Not actually a big issue, as this can not lead to a difference result
            @warn "Failed to find function's module" function_type exception=err
            function_module = "The module that defined $(function_type)"
        end

        
        function is_own(arg_type::DataType)
            try
                arg_module = get_module(arg_type)
                arg_pkg = Base.PkgId(arg_module)
                arg_pkg == method_pkg && return true
            catch err
                @warn "Failed to find arg types 's module" arg_type exception=err
            end

            return any(is_own, arg_type.parameters)
        end
        

        is_own(U::Union) = all(is_own, union_types(U))
        is_own(::UnionAll) = return true  # TODO: Deal with this properly
        is_own(x) = is_own(typeof(x)) # for Int, Symbol and other bit types that show up in type-params

        arg_types_type = meth.sig.parameters[2:end]
        any(is_own, arg_types_type) && return false
        @error "Pirate Found!" meth pirate=method_module victim=function_module
        return true  # has failed to prove that they are not a pirate.
    end

    return filter(is_pirate, methods)
end


#2

Package it.

However, I do get this error when I test it on my own package

julia> hunt(Grassmann)
┌ Info: Indexing
└   mod = Grassmann
┌ Warning: while working out what things was, had error
│   thing = :isudal
│   mod = Grassmann
│   exception = UndefVarError: isudal not defined
└ @ Main REPL[2]:24
┌ Info: indexing complete
└   length(found_methods) = 271
ERROR: MethodError: no method matching hunt(::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method, ::Method)
Closest candidates are:
  hunt(::Any) at REPL[10]:1
Stacktrace:
 [1] hunt(::Module) at ./REPL[9]:1
 [2] top-level scope at none:0

#3

You’ll get many warning when using it,
because the way it searches for things is not exactly nice.
It goes looking for methods everywhere.

I see I had an errant ..., in one of the hunt methods.
Will be fixed in 30s


#4

Does it work with Arrrrrrray tyoes?


#5

Alas, the UnionAll omission

overlooks things like

function (*)(A::AbstractMatrix, B::AbstractMatrix)

which is in LinearAlgebra. We matrix-wranglers are proud privateers, but look forward to elaboration of @oxinabox’ functions to make sure we plunder only deserving victims.


#6

I should move this to a package so that I can take PRs.
I am sure someone can workout the logic behind how to check UnionAlls