-
Notifications
You must be signed in to change notification settings - Fork 18.1k
x/tools/go/analysis: feature request: need a way of accessing analysis results from another go package #50265
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
There are 3 ways Analyzers can communicate at the moment: results, facts, and diagnostics. This gives a lot of flexibility for how information can be passed around. OTOH the analysis package is not suitable for all needs (notice that none of the x/tools/callgraph commands use it).
FWIW this may eventually need a proposal for acceptance. IMO this is exposing more internal details than were previously. Older Analyzers might not be considered backwards compatible. We can try to iron out the above questions first though. |
In the simplest form, what I'm trying to do is analyse a package's method calls, and serialize that into a separate YAML file. The type i'm using at the moment to move the results around is Perhaps using an Analyzer isn't appropriate for this case - what I'm essentially trying to do here is closer to code generation rather than code analysis or linting, which is what the analysis package appears to be geared towards. Are there more suitable packages for this, or is the suggestion that I "roll my own" and duplicate some of what the analyzer package is doing? I did try a couple of approaches using the supplied facts and diagnostics APIs: |
It is still not very clear to me what you are trying to do. Maybe you can zoom out a bit more? Or explain what you have tried? FWIW I find it is much easier to think of analysis by thinking about the communication mechanisms: Facts, Results, Diagnostics, and the scheduling relationships of passes than "being about linting/code analysis". This is an insider view but this is an under the hood feature request. Another tactic that may help is to describe what features you are trying to get out of being an Analyzer? This might indicate if it is simpler to roll your own infra. Responses and questions about more minor points:
Which packages do you want to analyze the methods of? If you run the checker on P and P imports Q, do you know you need the results of Q? Is just P okay? What about Q's imports?
This will admit very simple serializations that are stable over time. Facts may be appropriate.
{single,multi}checker do not guarantee each package is analyzed in the same process. This allows reducing to unitchecker to be picked up by
This is not supported. The assumption is that the packages.Packages can be loaded all at once before any Passes are run.
If you are communicating with an external file/DB that will need to be aware of different Passes, an (Analyzer, Package)-pairs. Potentially different executions of the same (Analyzer, Package).
2cents: This may have type incorrect call relationships. Not sure if this impacts your application |
Change https://go.dev/cl/411907 mentions this issue: |
Probably a bit late but I got permission to share concrete code of how we're using the analysis library here: https://gist.github.com/adammw/941d15c8b1730e3e89fc61138a6a4f24 Note that I had to essentially copy out the implementation of the internal |
Thank you for the example. To summarize your use case a bit:
The first bullet is going to be hard for a framework to schedule. We can debate whether there is some way of shoving this into the expectations of analysis/{single,mulit,unit}checker, but I am not sure that would amount to much. We will take this case into account when considering #53215 . |
The {single,multi}checker packages provide the main function for a complete application, as a black box. Many users want the ability to customize the analyzer behavior with additional logic, as described in the attached issues. This change creates a new package, go/analysis/checker, that exposes an Analyze pure function---one that avoids global flags, os.Exit, logging, profiling, and other side effects---that runs a set of analyzers on a set of packages loaded (by the client) using go/packages, and presents the graph of results in a form that allows postprocessing. This is just a sketch. API feedback welcome. DO NOT SUBMIT Updates golang/go#30231 Updates golang/go#30219 Updates golang/go#31007 Updates golang/go#31897 Updates golang/go#50265 Updates golang/go#53215 Updates golang/go#53336 Change-Id: I745d319a587dca506564a4624b52a7f1eb5f4751
Perhaps this needs a new issue altogether, but this is the closest thing I could find to what I am running into. We have a linter that we use today but cannot integrate with the existing analysis tools (we currently use We essentially have a linter that ensures specific structs are being initialized with all of their fields. Currently, certain community linters do this by taking in a "pattern" of struct name to match against, we've found this to be cumbersome and inefficient, and instead, our linter allows us to add a comment to the structs we wish this linting rule enforced for. Then for any literal we find, we ensure the rule is applied. Currently, the limitation of only processing one pkg at a time and comments not being attached to types (or anything we can grab in a |
@josebalius Please open a new issue. It is a different discussion. |
The {single,multi}checker packages provide the main function for a complete application, as a black box. Many users want the ability to customize the analyzer behavior with additional logic, as described in the attached issues. This change creates a new package, go/analysis/checker, that exposes an Analyze pure function---one that avoids global flags, os.Exit, logging, profiling, and other side effects---that runs a set of analyzers on a set of packages loaded (by the client) using go/packages, and presents the graph of results in a form that allows postprocessing. This is just a sketch. API feedback welcome. DO NOT SUBMIT Updates golang/go#30231 Updates golang/go#30219 Updates golang/go#31007 Updates golang/go#31897 Updates golang/go#50265 Updates golang/go#53215 Updates golang/go#53336 Change-Id: I745d319a587dca506564a4624b52a7f1eb5f4751
The {single,multi}checker packages provide the main function for a complete application, as a black box. Many users want the ability to customize the analyzer behavior with additional logic, as described in the attached issues. This change creates a new package, go/analysis/checker, that exposes an Analyze pure function---one that avoids global flags, os.Exit, logging, profiling, and other side effects---that runs a set of analyzers on a set of packages loaded (by the client) using go/packages, and presents the graph of results in a form that allows postprocessing. This is just a sketch. API feedback welcome. DO NOT SUBMIT Updates golang/go#30231 Updates golang/go#30219 Updates golang/go#31007 Updates golang/go#31897 Updates golang/go#50265 Updates golang/go#53215 Updates golang/go#53336 Change-Id: I745d319a587dca506564a4624b52a7f1eb5f4751
@addmmw It seems to me that if your goal is to compute some property of a package and all its transitive dependencies, for the purpose of code generation, you would be better off starting from the go/packages.Load function, which returns a graph of packages that you can analyze using custom logic. The analysis framework is oriented towards checkers, i.e. functions that produce diagnostics, which is not your desired output, and towards incremental analysis of large codebases, analogous to separate compilation in a compiler. But if your goal is code generation---something done relatively infrequently in the workflow---this optimization doesn't seem very important. No doubt it is possible to express your problem in the analysis framework (perhaps by encoding the result information into a diagnostics message and then parsing it) but it's forcing a round peg into a square hole. There have been requests for us to provide a more piecemeal set of components for building analysis tools, instead of the inflexible unitchecker and singlechecker packages which provide the complete main function for the application (everything but the checker plugins). Such an API might reduce the needed amount of force. But still it seems to me that what you're doing is most naturally addressed by starting from golang.org/x/tools.go/packages. |
Nice to hear from you @josebalius, I hope you are well. I think your problem should be very cleanly solvable using Facts in the go/analysis framework. Basically, your analyzer needs to do two things: A type is special if it was discovered by step (1) in this package, or if it was discovered when step (1) was previously applied to one of this package's dependencies, so it's a memoized call to ImportObjectFact. The printf checker should serve as a guide. In future we may come up with a more standard way to annotate declarations such as your struct type, but for now, looking for a special doc comment seems fine. Best of luck. |
I'm going to close this issue because I think the analysis framework is not appropriate; use go/packages instead as described in #50265 (comment). |
@adonovan hope you are doing well also! Yep that's exactly what I ended up doing after figuring it out by looking at some other linters' implementation. |
The {single,multi}checker packages provide the main function for a complete application, as a black box. Many users want the ability to customize the analyzer behavior with additional logic, as described in the attached issues. This change creates a new package, go/analysis/checker, that exposes an Analyze pure function---one that avoids global flags, os.Exit, logging, profiling, and other side effects---that runs a set of analyzers on a set of packages loaded (by the client) using go/packages, and presents the graph of results in a form that allows postprocessing. package checker func Analyze([]*analysis.Analyzer, []*packages.Package, *Options) (*Graph, error) type Graph struct { Roots []*Action } type Action struct { ... } // information about each step func (*Graph) WriteJSONDiagnostics(io.Writer) error func (*Graph) WriteTextDiagnostics(io.Writer, contextLines int) error func (*Graph) All() iter.Seq[*Action] // (was Visit in the proposal) See the example_test.go file for typical API usage. API feedback welcome. This change should have no effect on the behavior of existing programs. Logic changes have been kept to a minimum, so the large diffs are mostly code motion. Auxiliary functions for printing, flags, fixes, and so on has been kept to a minimum so that we can make progress on this change. They will be dealt with in follow-up changes. Detailed list of changes to ease review: analysistest: - Run: we report Load errors to t.Log up front (unless RunDespiteErrors); this was previously in loadPackages. - Run: the Validate call is now done by checker.Analyze. - internal/checker.TestAnalyzer has melted away - the logic to construct the "legacy" Result slice is new. - Result: this type used to be an alias for internal/checker.checker.TestAnalyzerResult (regrettably exposing more than intended); now it is a plain public struct. - check: the logic to check fact expectations is new. - The buildtag and directive analyzers both used a similar hack w.r.t. IgnoredFiles; this hack is now in analysistest. Better ideas welcome. checker: - checker.go is mostly moved from internal/checker/checker.go. - print.go is mostly split from old printDiagnostics in internal/checker/checker.go. - Action.execOnce: the pass.Result/Err logic was simplified using an immediately-applied lambda. - inheritFacts: the comments on package-fact handling are new. - Graph.Visit is "born deprecated". Clients using go1.23 should use Graph.All (iter.Seq). - Example: this is new code. internal/checker: - applyFixes: now accepts only the actions to apply, without calling visitAll over the action graph. The previous code was wrong, a latent bug. Some files outside go/analysis were updated, not out of necessity (it's not a breaking change) but to modernize them (e.g. avoiding analysistest.Result.Pass). Updates golang/go#61324 (new API proposal) Fixes golang/go#53215 (feature proposal) Fixes golang/go#31897 Fixes golang/go#50265 Fixes golang/go#53336 Fixes golang/go#66745 Updates golang/go#30231 Updates golang/go#30219 Updates golang/go#31007 Updates golang/go#66745 Change-Id: I745d319a587dca506564a4624b52a7f1eb5f4751 Reviewed-on: https://go-review.googlesource.com/c/tools/+/411907 Reviewed-by: Robert Findley <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]>
What version of Go are you using (
go version
)?Does this issue reproduce with the latest release?
Yes
What operating system and processor architecture are you using (
go env
)?go env
OutputWhat did you do?
I am looking to write a generator that statically analyses some code, and based on the code paths being used generates a manifest for AWS permissions, similar to how kubebuilder controller-gen walks the directory tree and analyses code comments, then produces a result.
What did you see instead?
x/tools/go/analysis package provides a Analyzer package that is great for writing single use code analyzers, but the only documented way of using the analyzers is via the singlechecker or multichecker packages which expose a Main() method and exit with a status code. This makes it hard to write any code that uses the analysis result directly.
The internal checker package does sort of what I would expect, having a "Run" method that returns an exit code, but is both internal (so cannot be imported) and does not return the analysis results.
What did you expect to see?
An exported method that was capable of running analyzers against a set of packages/files, and returning the result interface{} to the program execution flow.
My current workaround is to copy parts of golang.org/x/tools/go/analysis/checker/internal/checker into my own codebase, such as load() to load packages, and the action code to create and run an analysis pass, to be able to get the result directly, which is a fair amount of extra duplicated code to maintain.
The text was updated successfully, but these errors were encountered: