AbstractAnalyzer Framework

JET offers an infrastructure to implement a "plugin" code analyzer. Actually, JET's default error analyzer is one specific instance of such a pluggin analyzer built on top of the framework.

In this documentation we will try to elaborate the framework APIs and showcase example analyzers.

Warning

The APIs described in this page is very experimental and subject to changes. And this documentation is also very WIP.

Interfaces

JET.AbstractAnalyzerType
abstract type AbstractAnalyzer <: AbstractInterpreter end

An interface type of analyzers that are built on top of JET's analyzer framework.

When a new type NewAnalyzer implements the AbstractAnalyzer interface, it should be declared as subtype of AbstractAnalyzer, and is expected to the following interfaces:


  1. NewAnalyzer(; jetconfigs...) -> NewAnalyzer:
    Constructs new analyzer given JET configurations passed as jetconfigs.

  1. AnalyzerState(analyzer::NewAnalyzer) -> AnalyzerState:
    Returns the AnalyzerState for analyzer::NewAnalyzer.

  1. AbstractAnalyzer(analyzer::NewAnalyzer, state::AnalyzerState) -> NewAnalyzer:
    Constructs an new NewAnalyzer instance in the middle of JET's top-level analysis or abstract interpretation, given the previous analyzer::NewAnalyzer and state::AnalyzerState.

  1. ReportPass(analyzer::NewAnalyzer) -> ReportPass:
    Returns ReportPass used for analyzer::NewAnalyzer.

  1. get_cache_key(analyzer::NewAnalyzer) -> cache_key::UInt:
    Returns cache_key::UInt used for analyzer::NewAnalyzer.

See also AnalyzerState, ReportPass and get_cache_key.

Example

JET.jl defines its default error analyzer JETAnalyzer <: AbstractAnalyzer as the following (modified a bit for the sake of simplicity):

# the default error analyzer for JET.jl
struct JETAnalyzer{RP<:ReportPass} <: AbstractAnalyzer
    report_pass::RP
    state::AnalyzerState
    __cache_key::UInt
end

# AbstractAnalyzer API requirements

function JETAnalyzer(;
    report_pass::ReportPass = BasicPass(),
    jetconfigs...)
    state = AnalyzerState(; jetconfigs...)
    cache_key = [...]
    return JETAnalyzer(report_pass, state, cache_key)
end
AnalyzerState(analyzer::JETAnalyzer) = analyzer.state
AbstractAnalyzer(analyzer::JETAnalyzer, state::AnalyzerState) = JETAnalyzer(ReportPass(analyzer), state)
ReportPass(analyzer::JETAnalyzer) = analyzer.report_pass
get_cache_key(analyzer::JETAnalyzer) = analyzer.__cache_key
source
JET.AnalyzerStateType
mutable struct AnalyzerState
    ...
end

The mutable object that holds various states that are consumed by all AbstractAnalyzers.


AnalyzerState(analyzer::AbstractAnalyzer) -> AnalyzerState

If NewAnalyzer implements the AbstractAnalyzer interface, NewAnalyzer should implement this AnalyzerState(analyzer::NewAnalyzer) -> AnalyzerState interface.

A new AnalyzerState is supposed to be constructed using JET configurations passed as keyword arguments jetconfigs of the NewAnalyzer(; jetconfigs...) constructor, and the constructed AnalyzerState is usually kept within NewAnalyzer itself:

function NewAnalyzer(; jetconfigs...)
    ...
    state = AnalyzerState(; jetconfigs...)
    return NewAnalyzer(..., state)
end
AnalyzerState(analyzer::NewAnalyzer) = analyzer.state
source
JET.ReportPassType
abstract type ReportPass end

An interface type that represents AbstractAnalyzer's report pass. analyzer::AbstractAnalyzer injects report passes using the (::ReportPass)(::Type{InferenceErrorReport}, ::AbstractAnalyzer, state, ...) interface, which provides a flexible and efficient layer to configure the analysis done by AbstractAnalyzer.


ReportPass(analyzer::AbstractAnalyzer) -> ReportPass

If NewAnalyzer implements the AbstractAnalyzer interface, NewAnalyzer should implement this ReportPass(analyzer::NewAnalyzer) -> ReportPass interface.

ReportPass allows NewAnalyzer to provide a very flexible configuration layer for NewAnalyzer's analysis; an user can define their own ReportPass to control how NewAnalyzer collects report errors while still using the analysis routine implemented by NewAnalyzer.

Example

For example, JETAnalyzer accepts a custom ReportPass passed as JET configurations (see the example documentation of AbstractAnalyzer for details). And we can setup a custom report pass IgnoreAllExceptGlobalUndefVar, that ignores all the reports that are otherwise collected by JETAnalyzer except GlobalUndefVarErrorReport:

# custom report pass that ignores all the reports except `GlobalUndefVarErrorReport`
struct IgnoreAllExceptGlobalUndefVar <: ReportPass end

# ignores all the reports analyzed by `JETAnalyzer`
(::IgnoreAllExceptGlobalUndefVar)(::Type{<:InferenceErrorReport}, @nospecialize(_...)) = return

# forward to `BasicPass` to collect `GlobalUndefVarErrorReport`
function (::IgnoreAllExceptGlobalUndefVar)(::Type{GlobalUndefVarErrorReport}, @nospecialize(args...))
    BasicPass()(GlobalUndefVarErrorReport, args...)
end

no_method_error()    = 1 + "1"
undef_global_error() = undefvar
report_call(; report_pass=IgnoreAllExceptGlobalUndefVar()) do
    if rand(Bool)
        return no_method_error()    # "no matching method found" error report won't be reported here
    else
        return undef_global_error() # "variable undefvar is not defined" error report will be reported
    end
end
source
JET.get_cache_keyFunction
get_cache_key(analyzer::AbstractAnalyzer) -> cache_key::UInt

Returns the cache key for this analyzer::AbstractAnalyzer. AbstractAnalyzers that have different cache keys will use different cache so that their analysis results are completely separated.

See also JET_CACHE.

source
JET.VSCode.vscode_diagnostics_orderFunction
vscode_diagnostics_order(analyzer::AbstractAnalyzer) -> Bool

If true (default) a diagnostic will be reported at entry site. Otherwise it's reported at error point.

source
JET.InferenceErrorReportType
InferenceErrorReport

An interface type of error reports that JET collects by abstract interpration. In order for R<:InferenceErrorReport to implement the interface, it should satisfy the following requirements:

R<:InferenceErrorReport is supposed to be constructed using the following constructor

R(::AbstractAnalyzer, state, spec_args...) -> R

where state can be either of:

  • state::Tuple{Union{Core.Compiler.InferenceState, Core.Compiler.OptimizationState}, Int64}: a state with the current program counter specified
  • state::InferenceState: a state with the current program counter set to state.currpc
  • state::InferenceResult: a state with the current program counter unknown
  • state::MethodInstance: a state with the current program counter unknown

See also: @jetreport, VirtualStackTrace, VirtualFrame

source
JET.copy_reportFunction
copy_report(report::R) where R<:InferenceErrorReport -> new::R

Returns new new::R, that should be identical to the original report::R, except that new.vst is copied from report.vst so that the further modifcation on report.vst that may happen in later abstract interpretation doesn't affect new.vst.

source
JET.print_report_messageFunction
print_report_message(io::IO, report::R) where R<:InferenceErrorReport

Prints to io and describes why report is reported.

source
JET.print_signatureFunction
print_signature(::R) where R<:InferenceErrorReport -> Bool

Configures whether or not to print the report signature when printing R (defaults to true).

source
JET.report_colorFunction
report_color(::R) where R<:InferenceErrorReport -> Symbol

Configures the color for R (defaults to :red).

source
JET.aggregation_policyFunction
aggregation_policy(analyzer::AbstractAnalyzer)

Defines how analyzer aggregates InferenceErrorReports. Defaults to default_aggregation_policy.


default_aggregation_policy(report::InferenceErrorReport) -> DefaultReportIdentity

Returns the default identity of report::InferenceErrorReport, where DefaultReportIdentity aggregates reports based on "error location" of each report. DefaultReportIdentity aggregates InferenceErrorReports aggressively in a sense that it ignores the identity of error point's MethodInstance, under the assumption that errors are identical as far as they're collected at the same file and line.

source
JET.@jetreportMacro
@jetreport struct T <: InferenceErrorReport
    ...
end

An utilitiy macro to define InferenceErrorReport. It can be very tedious to manually satisfy the InferenceErrorReport interfaces. JET internally uses this @jetreport utility macro, which takes a struct definition of InferenceErrorReport without the required fields specified, and automatically defines the struct as well as constructor definitions. If the report T <: InferenceErrorReport is defined using @jetreport, then T just needs to implement the print_report_message interface.

For example, JETAnalyzer's MethodErrorReport is defined as follows:

@jetreport struct MethodErrorReport <: InferenceErrorReport
    @nospecialize t # ::Union{Type, Vector{Type}}
    union_split::Int
end
function print_report_message(io::IO, (; t, union_split)::MethodErrorReport)
    print(io, "no matching method found for ")
    if union_split == 0
        print_callsig(io, t)
    else
        ts = t::Vector{Any}
        nts = length(ts)
        for i = 1:nts
            print_callsig(io, ts[i])
            i == nts || print(io, ", ")
        end
        print(io, " (", nts, '/', union_split, " union split)")
    end
end

and constructed as like MethodErrorReport(sv::InferenceState, atype::Any, 0).

source

Examples