-
Notifications
You must be signed in to change notification settings - Fork 17.5k
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
proposal: cmd/vet: add declarative support for identifying functions for printf check #58340
Comments
I just want to note that I am aware that for Go v2 other proposals are available, like #50554 . The focus of this proposal is Go v1. |
CC @golang/tools-team |
This is closely related to #52445. (A duplicate in spirit if not the same collection of suggestions.) |
Perhaps this issue and the linked one could both be solved by letting go vet recognize pragmas of the form // govet: printf To turn on the matching vet check for the function defined just after this pragma? |
There are already simple ways of marking a function as a printf wrapper when the analysis cannot infer this:
and to do the reverse, to hide from vet the fact that a function should not be treated like a printf wrapper even though it does delegate to printf:
Since this proposal is about the specific case of printf, I think we should reject it. There are other analyzers for which the idea of a configuration file or annotation mechanism is more compelling, but this proposal is not about them. |
I agree that this printf check issue alone is not strong enough to justify introduction of new configuration mechanism. And It looks to me this workaround depends a lot on the current implementation details of the printf check. If this is the recommended official way, we need to make it future-proof (e.g. add testing in the printf check and ensure we don't accidentally break it while optimizing or tuning the check). |
The first of my two code examples above is documented:
There's also a web version of the same documentation here: We could document the other trick here if that would help, but I think it's pretty rarely wanted. You're right that these tricks do depend on the strength of the printf analysis, but the documentation is effectively a promise that this trick is supported. Also, making vet analyses flow-sensitive enough to ignore "if false" would cause them to skip conditionally compiled code such as |
I don't think it is currently clear what the goal of the proposal is (or what the problem is). I am guessing that all four suggestions are trying to do the equivalent of adding a new entry to |
The printf case is a weak justification for analysis annotations because it is already well served by simple and unobjectionable code changes that are (at least partly) documented by the analyzer. Other analyzers may provide more a compelling motive for a richer annotation or configuration mechanism. I suggest we either broaden the proposal to examine typical patterns across a range of analyzers, or reject the proposal. |
Change https://go.dev/cl/476635 mentions this issue: |
Re-reading this thread I realized my first comment was addressing only a narrow part of the original proposal, which is more broadly about how to indicate in the type of a function that it is a printf wrapper, so that dynamic calls to func values and interface methods are checked. Apologies for not reading more carefully. Let's reconsider the four ideas in the first note:
In reverse order, my reactions are:
For completeness, let's not forget the implicit zero approach, which is some comment-based syntax as @beoran (and many others in the past) have suggested. We may eventually need a more powerful and general notation to express analysis facts in the source code, but I'd rather not design it until we have seen a handful of analyzers that need it. I think approach 1 is worth prototyping in the printf checker. I put together a quick sketch in https://go.dev/cl/479175. |
Change https://go.dev/cl/479175 mentions this issue: |
IIUC the annotations that are being requested are 2 slightly different cases:
@adonovan We should be able to solve both with approach (1) in #58340 (comment). We can annotate functions using a special argument name like this:
This is reusing the parameter name from the func type to pick up the annotation. I am not sure if the sketch https://go.dev/cl/479175 supports it. This annotation only needs to be done once per package. We can also forward added A limitation I can see from a user's perspective is that this does not allow for the following: There is a minor implementation issue that an added isPrint annotation from another package should be a package Fact instead of an object Fact. |
On the question of "injecting" annotations into a lower package, the answer has to be a clear no: the framework allows information to flow only upwards, from p's imports to p itself. I'm not sure it's practical even to support the restricted version of injection in which, given A->B->C, package B expresses a retroactive annotation on a declaration in C so that A benefits, because, as you say, object facts can only be exported by the declaring package. If one of your dependencies forgot to add an annotation, your best choice remains to send them a PR. On the question of the notation of annotations, one approach we should consider is for each analyzer to provide a subpackage of declarations with no run-time effect--just types, constants, and empty function bodies--which the client code imports and uses to express annotations. For example:
Using Go syntax for annotations has a number of benefits: unlike comments, they stay up to date as the code evolves, they can be navigated like ordinary references, and documentation is instantly available. |
You can do this with package facts where
I've been thinking about this kind of annotations for a while. I definitely like these as a high level idea. It feels like we just need more use cases before pursuing this though (guarded by checking would be a good candidate). But this is kinda getting off topic. For the printf checker though, maybe it is good enough to using the name of the first function argument? That was my understanding of (1) #58340 (comment). The name might be a magic constant, but we do accept magic constants for things like |
I agree. At the risk of stating the obvious, package facts are for facts about packages, and objects facts are for objects. (The superset property is not essential, BTW. "Package P has package doc comment C" would be a totally valid package fact.)
Glad we're on the same page wrt Go syntax for annotations; and, yes, there are many details still to work out.
I'd be happy with that approach (specifically: bullet point 1, sketched in CL 479175, not the |
Change https://go.dev/cl/489835 mentions this issue: |
Goals
Add support for
printf
(anderrorf
) for function pointers and interfaces:Why change is needed
Currently the implementation does work by (a) having either functions mapping a specified list (which can be provided to
vet
via commandline argument) or by (b) calling thefmt.Printf
orfmt.Print
function.a) Providing a specific function mapping does not work with modules that are not imported within the source file. Also there is no config file which could be use provide this list project-wide or inherit it from imported modules. For the full mapping list, see here.
b) Calling the
fmt.Printf
orfmt.Print
function directly does not work for arguments, return values, and interfaces.c) Also IDEs like VS Code and Goland add support for
vet
-based rules. Adding this feature tovet
will likely also trigger changes in the IDE support.For more details on the implementation, see here.
Solutions
There are different ways to solve this issue. I would like to brainstorm different ways, so please feel free to add proposals in the comments.
I) Named Arguments and Return Values
Use named arguments and return values by either using
printfFormat
orerrorfFormat
as argument name:Pros: Easy to use, unlikely to cause side effects with existing code, clean upgrade path for existing modules, does not increase required Go version
Cons: Bloated code, argument names where non should be required
II) Add new
fmt.FormatString
anderrors.FormatString
Add new
fmt.FormatString
anderrors.FormatString
types (which point tostring
) to declare the functions/methods as formatted:Pros: Safe from side effects with existing code
Cons: Impact on reflection, requires build tag on upgrading code, requires addition to core packages, requires upgrade to higer Go version
III) Add declaration list to
go.mod
Allow additional printf-like functions to be declared within the
go.mod
file and have those settings inherited from imported modules. Package names are resolved relative to the module root.Examples:
Pros: no side effects on existing code
Cons: requires upgrade to higher Go version
IV) Add new
go.vet
config fileAllow additional printf-like functions to be declared within a new
go.vet
file and have those settings inherited from imported modules. Package names are resolved relative to the module root. (Similar to III but with less issues.)The file should always be backward compatible and unknown and illegal entries should be ignored with a warning.
Pros: no side effects on existing code, clean upgrade path for existing modules, does not increase required Go version, could be used for additional
go tool vet
settingsCons: new file added to Go universe
Please let me know what you think.
Best Regards, Felix
The text was updated successfully, but these errors were encountered: