aviatesk/JET.jl

GitHub: aviatesk/JET.jl

JET.jl 是一个利用 Julia 类型推断系统在无需额外类型注解的情况下自动检测代码类型错误和性能不稳定性的静态分析工具。

Stars: 868 | Forks: 44

# JET.jl [![](https://img.shields.io/badge/docs-latest-blue.svg)](https://aviatesk.github.io/JET.jl/dev/) [![](https://github.com/aviatesk/JET.jl/actions/workflows/ci.yml/badge.svg)](https://github.com/aviatesk/JET.jl/actions/workflows/ci.yml) [![](https://codecov.io/gh/aviatesk/JET.jl/branch/master/graph/badge.svg)](https://codecov.io/gh/aviatesk/JET.jl) [![](https://img.shields.io/badge/%F0%9F%9B%A9%EF%B8%8F_tested_with-JET.jl-233f9a)](https://github.com/aviatesk/JET.jl) JET 采用 Julia 的类型推断系统来检测潜在的错误和类型不稳定性。 ## 快速开始 请在[文档](https://aviatesk.github.io/JET.jl/dev/)中查看更多命令、选项和说明。 ### 安装 JET 是一个标准的 Julia 包。 因此,你可以直接通过 Julia 内置的包管理器进行安装,并像使用其他任何包一样使用它: ``` julia> using Pkg; Pkg.add("JET") [ some output elided ] julia> using JET ``` ### 使用 `@report_opt` 检测类型不稳定性 使用 `@report_opt` 宏可以在函数调用中检测类型不稳定性,其工作原理与 `@code_warntype` 宏类似。 请注意,因为 JET 依赖于 Julia 的类型推断,如果推断链由于动态分发而中断,那么编译器将无法获知所有后续的函数调用,因此 JET 无法对其进行分析。 ``` julia> @report_opt foldl(+, Any[]; init=0) ═════ 2 possible errors found ═════ ┌ kwcall(::@NamedTuple{init::Int64}, ::typeof(foldl), op::typeof(+), itr::Vector{Any}) @ Base ./reduce.jl:198 │┌ foldl(op::typeof(+), itr::Vector{Any}; kw::@Kwargs{init::Int64}) @ Base ./reduce.jl:198 ││┌ kwcall(::@NamedTuple{init::Int64}, ::typeof(mapfoldl), f::typeof(identity), op::typeof(+), itr::Vector{Any}) @ Base ./reduce.jl:175 │││┌ mapfoldl(f::typeof(identity), op::typeof(+), itr::Vector{Any}; init::Int64) @ Base ./reduce.jl:175 ││││┌ mapfoldl_impl(f::typeof(identity), op::typeof(+), nt::Int64, itr::Vector{Any}) @ Base ./reduce.jl:44 │││││┌ foldl_impl(op::Base.BottomRF{typeof(+)}, nt::Int64, itr::Vector{Any}) @ Base ./reduce.jl:48 ││││││┌ _foldl_impl(op::Base.BottomRF{typeof(+)}, init::Int64, itr::Vector{Any}) @ Base ./reduce.jl:58 │││││││┌ (::Base.BottomRF{typeof(+)})(acc::Int64, x::Any) @ Base ./reduce.jl:86 ││││││││ runtime dispatch detected: +(acc::Int64, x::Any)::Any │││││││└──────────────────── ││││││┌ _foldl_impl(op::Base.BottomRF{typeof(+)}, init::Int64, itr::Vector{Any}) @ Base ./reduce.jl:62 │││││││┌ (::Base.BottomRF{typeof(+)})(acc::Any, x::Any) @ Base ./reduce.jl:86 ││││││││ runtime dispatch detected: +(acc::Any, x::Any)::Any │││││││└──────────────────── ``` ### 使用 `@report_call` 检测类型错误 这在类型稳定的代码上效果最好,因此在使用 `@report_call` 之前,请尽量先使用 `@report_opt`。 ``` julia> @report_call foldl(+, Char[]) ═════ 2 possible errors found ═════ ┌ foldl(op::typeof(+), itr::Vector{Char}) @ Base ./reduce.jl:198 │┌ foldl(op::typeof(+), itr::Vector{Char}; kw::@Kwargs{}) @ Base ./reduce.jl:198 ││┌ mapfoldl(f::typeof(identity), op::typeof(+), itr::Vector{Char}) @ Base ./reduce.jl:175 │││┌ mapfoldl(f::typeof(identity), op::typeof(+), itr::Vector{Char}; init::Base._InitialValue) @ Base ./reduce.jl:175 ││││┌ mapfoldl_impl(f::typeof(identity), op::typeof(+), nt::Base._InitialValue, itr::Vector{Char}) @ Base ./reduce.jl:44 │││││┌ foldl_impl(op::Base.BottomRF{typeof(+)}, nt::Base._InitialValue, itr::Vector{Char}) @ Base ./reduce.jl:48 ││││││┌ _foldl_impl(op::Base.BottomRF{typeof(+)}, init::Base._InitialValue, itr::Vector{Char}) @ Base ./reduce.jl:62 │││││││┌ (::Base.BottomRF{typeof(+)})(acc::Char, x::Char) @ Base ./reduce.jl:86 ││││││││ no matching method found `+(::Char, ::Char)`: (op::Base.BottomRF{typeof(+)}).rf::typeof(+)(acc::Char, x::Char) │││││││└──────────────────── │││││┌ foldl_impl(op::Base.BottomRF{typeof(+)}, nt::Base._InitialValue, itr::Vector{Char}) @ Base ./reduce.jl:49 ││││││┌ reduce_empty_iter(op::Base.BottomRF{typeof(+)}, itr::Vector{Char}) @ Base ./reduce.jl:383 │││││││┌ reduce_empty_iter(op::Base.BottomRF{typeof(+)}, itr::Vector{Char}, ::Base.HasEltype) @ Base ./reduce.jl:384 ││││││││┌ reduce_empty(op::Base.BottomRF{typeof(+)}, ::Type{Char}) @ Base ./reduce.jl:360 │││││││││┌ reduce_empty(::typeof(+), ::Type{Char}) @ Base ./reduce.jl:343 ││││││││││ no matching method found `zero(::Type{Char})`: zero(T::Type{Char}) │││││││││└──────────────────── ``` ### 使用 `report_package` 分析包 它会查找所有方法定义,并根据其签名分析函数调用。请注意,这不如 `@report_call` 准确,因为对于泛型方法来说,实际的输入类型是未知的。 ``` julia> using Pkg; Pkg.activate(; temp=true, io=devnull); Pkg.add("AbstractTrees"; io=devnull); julia> Pkg.status() Status `/private/var/folders/xh/6zzly9vx71v05_y67nm_s9_c0000gn/T/jl_h07K2m/Project.toml` [1520ce14] AbstractTrees v0.4.5 julia> using AbstractTrees julia> report_package(AbstractTrees) [toplevel-info] Analyzing top-level definition (progress: 256/256) [toplevel-info] Analyzed all top-level definitions (all: 256 | analyzed: 256 | cached: 0 | took: 7.116 sec) [ Info: tracking Base ═════ 7 possible errors found ═════ ┌ isroot(root::Any, x::Any) @ AbstractTrees /Users/aviatesk/.julia/packages/AbstractTrees/Ftf8W/src/base.jl:102 │ no matching method found `parent(::Any, ::Any)`: AbstractTrees.parent(root::Any, x::Any) └──────────────────── ┌ StableNode{T}(x::T, ch::Any) where T @ AbstractTrees /Users/aviatesk/.julia/packages/AbstractTrees/Ftf8W/src/base.jl:260 │┌ collect(::Type{StableNode{_A}} where _A, itr::Any) @ Base ./array.jl:641 ││┌ _collect(::Type{StableNode{_A}}, itr::Any, isz::Union{Base.HasLength, Base.HasShape}) where _A @ Base ./array.jl:643 │││┌ _array_for(::Type{StableNode{_A}} where _A, itr::Base.HasLength, isz::Any) @ Base ./array.jl:673 ││││┌ _similar_shape(itr::Base.HasLength, ::Base.HasLength) @ Base ./array.jl:657 │││││ no matching method found `length(::Base.HasLength)`: length(itr::Base.HasLength) ││││└──────────────────── ││││┌ _similar_shape(itr::Base.HasLength, ::Base.HasShape) @ Base ./array.jl:658 │││││┌ axes(A::Base.HasLength) @ Base ./abstractarray.jl:98 ││││││ no matching method found `size(::Base.HasLength)`: size(A::Base.HasLength) │││││└──────────────────── ┌ IndexNode(tree::Any) @ AbstractTrees /Users/aviatesk/.julia/packages/AbstractTrees/Ftf8W/src/indexing.jl:117 │ no matching method found `rootindex(::Any)`: rootindex(tree::Any) └──────────────────── ┌ parent(idx::IndexNode) @ AbstractTrees /Users/aviatesk/.julia/packages/AbstractTrees/Ftf8W/src/indexing.jl:127 │ no matching method found `parentindex(::Any, ::Any)`: pidx = parentindex((idx::IndexNode).tree::Any, (idx::IndexNode).index::Any) └──────────────────── ┌ nextsibling(idx::IndexNode) @ AbstractTrees /Users/aviatesk/.julia/packages/AbstractTrees/Ftf8W/src/indexing.jl:132 │ no matching method found `nextsiblingindex(::Any, ::Any)`: sidx = nextsiblingindex((idx::IndexNode).tree::Any, (idx::IndexNode).index::Any) └──────────────────── ┌ prevsibling(idx::IndexNode) @ AbstractTrees /Users/aviatesk/.julia/packages/AbstractTrees/Ftf8W/src/indexing.jl:137 │ no matching method found `prevsiblingindex(::Any, ::Any)`: sidx = prevsiblingindex((idx::IndexNode).tree::Any, (idx::IndexNode).index::Any) └──────────────────── julia> report_package(AbstractTrees; target_modules=(AbstractTrees,)) # ignore errors that occur outside the AbstractTrees module context [toplevel-info] Skipped analysis for cached definition (256/256) [toplevel-info] Analyzed all top-level definitions (all: 256 | analyzed: 0 | cached: 256 | took: 0.036 sec) ═════ 5 possible errors found ═════ ┌ isroot(root::Any, x::Any) @ AbstractTrees /Users/aviatesk/.julia/packages/AbstractTrees/Ftf8W/src/base.jl:102 │ no matching method found `parent(::Any, ::Any)`: AbstractTrees.parent(root::Any, x::Any) └──────────────────── ┌ IndexNode(tree::Any) @ AbstractTrees /Users/aviatesk/.julia/packages/AbstractTrees/Ftf8W/src/indexing.jl:117 │ no matching method found `rootindex(::Any)`: rootindex(tree::Any) └──────────────────── ┌ parent(idx::IndexNode) @ AbstractTrees /Users/aviatesk/.julia/packages/AbstractTrees/Ftf8W/src/indexing.jl:127 │ no matching method found `parentindex(::Any, ::Any)`: pidx = parentindex((idx::IndexNode).tree::Any, (idx::IndexNode).index::Any) └──────────────────── ┌ nextsibling(idx::IndexNode) @ AbstractTrees /Users/aviatesk/.julia/packages/AbstractTrees/Ftf8W/src/indexing.jl:132 │ no matching method found `nextsiblingindex(::Any, ::Any)`: sidx = nextsiblingindex((idx::IndexNode).tree::Any, (idx::IndexNode).index::Any) └──────────────────── ┌ prevsibling(idx::IndexNode) @ AbstractTrees /Users/aviatesk/.julia/packages/AbstractTrees/Ftf8W/src/indexing.jl:137 │ no matching method found `prevsiblingindex(::Any, ::Any)`: sidx = prevsiblingindex((idx::IndexNode).tree::Any, (idx::IndexNode).index::Any) └──────────────────── ``` ## 局限性 JET 会探索你直接调用的函数以及它们*可推断的*被调用者。然而,如果某次调用的参数类型无法推断,JET 将不会分析该被调用者。因此,报告 `No errors detected` 并不意味着你的整个代码库都没有错误。为了提高对 JET 结果的可信度,请使用 `@report_opt` 确保你的代码是可推断的。