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 plugin analyzer built on top of the framework.
In this documentation we will try to elaborate the framework APIs and showcase example analyzers.
The APIs described in this page is very experimental and subject to changes. And this documentation is also very WIP.
Interfaces
JET.JETInterface
— ModuleJETInterface
This baremodule
exports names that form the APIs of AbstractAnalyzer
Framework. using JET.JETInterface
loads all names that are necessary to define a plugin analysis.
JET.AbstractAnalyzer
— Typeabstract 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:
JETInterface.AnalyzerState(analyzer::NewAnalyzer) -> AnalyzerState
:
Returns theAnalyzerState
foranalyzer::NewAnalyzer
.
JETInterface.AbstractAnalyzer(analyzer::NewAnalyzer, state::AnalyzerState) -> NewAnalyzer
:
Constructs an newNewAnalyzer
instance in the middle of JET's top-level analysis or abstract interpretation, given the previousanalyzer::NewAnalyzer
andstate::AnalyzerState
.
JETInterface.ReportPass(analyzer::NewAnalyzer) -> ReportPass
:
ReturnsReportPass
used foranalyzer::NewAnalyzer
.
JETInterface.AnalysisCache(analyzer::NewAnalyzer) -> analysis_cache::AnalysisCache
:
Returns code cache used foranalyzer::NewAnalyzer
.
See also AnalyzerState
, ReportPass
and AnalysisCache
.
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
state::AnalyzerState
analysis_cache::AnalysisCache
report_pass::RP
end
# AbstractAnalyzer API requirements
JETInterface.AnalyzerState(analyzer::JETAnalyzer) = analyzer.state
JETInterface.AbstractAnalyzer(analyzer::JETAnalyzer, state::AnalyzerState) = JETAnalyzer(ReportPass(analyzer), state)
JETInterface.ReportPass(analyzer::JETAnalyzer) = analyzer.report_pass
JETInterface.AnalysisCache(analyzer::JETAnalyzer) = analyzer.analysis_cache
JET.AnalyzerState
— Typemutable struct AnalyzerState
...
end
The mutable object that holds various states that are consumed by all AbstractAnalyzer
s.
JETInterface.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 the general configurations passed as keyword arguments jetconfigs
of the NewAnalyzer(; jetconfigs...)
constructor, and the constructed AnalyzerState
is usually kept within NewAnalyzer
itself:
function NewAnalyzer(world::UInt=Base.get_world_counter(); jetconfigs...)
...
state = AnalyzerState(world; jetconfigs...)
return NewAnalyzer(..., state)
end
JETInterface.AnalyzerState(analyzer::NewAnalyzer) = analyzer.state
JET.ReportPass
— Typeabstract 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
.
JETInterface.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 part of the general configurations (see the documentation of AbstractAnalyzer
for an example implementation). And we can setup a custom report pass IgnoreAllExceptGlobalUndefVar
, that ignores all the reports that are otherwise collected by JETAnalyzer
except UndefVarErrorReport
:
# custom report pass that ignores all the reports except `UndefVarErrorReport`
struct IgnoreAllExceptGlobalUndefVar <: ReportPass end
# ignores all the reports analyzed by `JETAnalyzer`
(::IgnoreAllExceptGlobalUndefVar)(::Type{<:InferenceErrorReport}, @nospecialize(_...)) = return
# forward to `BasicPass` to collect `UndefVarErrorReport`
function (::IgnoreAllExceptGlobalUndefVar)(::Type{UndefVarErrorReport}, @nospecialize(args...))
BasicPass()(UndefVarErrorReport, 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() # "`undefvar` is not defined" error report will be reported
end
end
JET.AnalysisCache
— MethodAnalysisCache(analyzer::AbstractAnalyzer) -> analysis_cache::AnalysisCache
Returns AnalysisCache
for this analyzer::AbstractAnalyzer
. AbstractAnalyzer
instances can share the same cache if they perform the same analysis, otherwise their cache should be separated.
JET.valid_configurations
— FunctionJETInterface.valid_configurations(analyzer::AbstractAnalyzer) -> names or nothing
Returns a set of names that are valid as a configuration for analyzer
. names
should be an iterator of Symbol
. No validations are performed if nothing
is returned.
JET.aggregation_policy
— FunctionJETInterface.aggregation_policy(analyzer::AbstractAnalyzer)
Defines how analyzer
aggregates InferenceErrorReport
s. 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 InferenceErrorReport
s 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.
JET.VSCode.vscode_diagnostics_order
— Functionvscode_diagnostics_order(analyzer::AbstractAnalyzer) -> Bool
If true
(default) a diagnostic will be reported at entry site. Otherwise it's reported at error point.
JET.InferenceErrorReport
— MethodInferenceErrorReport
In order for Report <: InferenceErrorReport
to implement the interface, it should satisfy the following requirements:
Required fields
Report
should have the following fields, which explains where and how this error is reported:vst::VirtualStackTrace
: a virtual stack trace of the errorsig::Signature
: a signature of the error point
Note that
Report
can have additional fields other thanvst
andsig
to explain why this error is reported (mostly used forprint_report_message
).Required overloads
Optional overloads
Report <: InferenceErrorReport
is supposed to be constructed using the following constructor
Report(::AbstractAnalyzer, state, spec_args...) -> Report
where state
can be either of:
state::Tuple{Union{Core.Compiler.InferenceState, Core.Compiler.OptimizationState}, Int64}
: a state with the current program counter specifiedstate::InferenceState
: a state with the current program counter set tostate.currpc
state::InferenceResult
: a state with the current program counter unknownstate::MethodInstance
: a state with the current program counter unknown
See also: @jetreport
, VirtualStackTrace
, VirtualFrame
JET.copy_report
— FunctionJETInterface.copy_report(orig::Report) where Report<:InferenceErrorReport -> new::Report
Returns new new::Report
, that should be identical to the original orig::Report
, except that new.vst
is copied from orig.vst
so that the further modification on orig.vst
that may happen in later abstract interpretation doesn't affect new.vst
.
JET.report_color
— FunctionJETInterface.report_color(::Report) where Report<:InferenceErrorReport -> Symbol
Configures the color for Report
(defaults to :red
).
JET.analyze_and_report_call!
— Functionanalyze_and_report_call!(analyzer::AbstractAnalyzer, f, [types]; jetconfigs...) -> JETCallResult
analyze_and_report_call!(analyzer::AbstractAnalyzer, tt::Type{<:Tuple}; jetconfigs...) -> JETCallResult
analyze_and_report_call!(analyzer::AbstractAnalyzer, mi::MethodInstance; jetconfigs...) -> JETCallResult
A generic entry point to analyze a function call with AbstractAnalyzer
. Finally returns the analysis result as JETCallResult
. Note that this is intended to be used by developers of AbstractAnalyzer
only. General users should use high-level entry points like report_call
and report_opt
.
JET.call_test_ex
— Functioncall_test_ex(funcname::Symbol, testname::Symbol, ex0, __module__, __source__)
An internal utility function to implement a @test_call
-like macro. See the implementation of @test_call
.
JET.func_test
— Functionfunc_test(func, testname::Symbol, args...; jetconfigs...)
An internal utility function to implement a test_call
-like function. See the implementation of test_call
.
JET.analyze_and_report_file!
— Functionanalyze_and_report_file!(analyzer::AbstractAnalyzer, filename::AbstractString; jetconfigs...) -> JETToplevelResult
A generic entry point to analyze a file with AbstractAnalyzer
. Finally returns the analysis result as JETToplevelResult
. Note that this is intended to be used by developers of AbstractAnalyzer
only. General users should use high-level entry points like report_file
.
JET.analyze_and_report_package!
— Functionanalyze_and_report_package!(analyzer::AbstractAnalyzer,
package::Union{AbstractString,Module,Nothing} = nothing;
jetconfigs...) -> JETToplevelResult
A generic entry point to analyze a package with AbstractAnalyzer
. Finally returns the analysis result as JETToplevelResult
. Note that this is intended to be used by developers of AbstractAnalyzer
only. General users should use high-level entry points like report_package
.
JET.analyze_and_report_text!
— Functionanalyze_and_report_text!(analyzer::AbstractAnalyzer, text::AbstractString,
filename::AbstractString = "top-level";
jetconfigs...) -> JETToplevelResult
A generic entry point to analyze a top-level code with AbstractAnalyzer
. Finally returns the analysis result as JETToplevelResult
. Note that this is intended to be used by developers of AbstractAnalyzer
only. General users should use high-level entry points like report_text
.
JET.add_new_report!
— Functionadd_new_report!(analyzer::AbstractAnalyzer, result::InferenceResult, report::InferenceErrorReport)
Adds new report::InferenceErrorReport
associated with result::InferenceResult
.
JET.@jetreport
— Macro@jetreport struct NewReport <: InferenceErrorReport
...
end
A utility 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 NewReport <: InferenceErrorReport
is defined using @jetreport
, then NewReport
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)
.