-
Notifications
You must be signed in to change notification settings - Fork 14.3k
[clang] Implement __attribute__((format_matches)) #116708
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
Merged
Merged
Changes from all commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
ad4c127
[clang] Implement __attribute__((format_matches))
apple-fcloutier 3562b90
Address RFC, review feedback
apple-fcloutier f19a97a
Fix post-rebase format issues
apple-fcloutier 28c8762
Improve diagnostics when compared format strings have different numbe…
apple-fcloutier 49e23a6
Validate that format strings match when both caller and callee have f…
apple-fcloutier e81899e
Improve diagnostics, tests
apple-fcloutier bcffd76
Add tests for all warn_format_cmp_role_mismatch combinations
apple-fcloutier e26f5bd
Cross-verify duplicate specifications of attribute(format_matches) on…
apple-fcloutier 33ef239
Validate that positional arguments in a format string are self-consis…
apple-fcloutier 94a5aa6
Add test for warn_format_cmp_modifierfor_mismatch
apple-fcloutier 70e06e6
Simplify loops around getPosition() groups
apple-fcloutier 8909cd9
Add test for argument sensitivity
apple-fcloutier File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3886,6 +3886,132 @@ behavior of the program is undefined. | |
}]; | ||
} | ||
|
||
def FormatMatchesDocs : Documentation { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm hopeful someone comes along and does a better 'english/documentation-quality' review of this. I don't have the mental acuity at the moment. |
||
let Category = DocCatFunction; | ||
let Content = [{ | ||
|
||
The ``format`` attribute is the basis for the enforcement of diagnostics in the | ||
``-Wformat`` family, but it only handles the case where the format string is | ||
passed along with the arguments it is going to format. It cannot handle the case | ||
where the format string and the format arguments are passed separately from each | ||
other. For instance: | ||
|
||
.. code-block:: c | ||
|
||
static const char *first_name; | ||
static double todays_temperature; | ||
static int wind_speed; | ||
|
||
void say_hi(const char *fmt) { | ||
printf(fmt, first_name, todays_temperature); | ||
// ^ warning: format string is not a string literal | ||
printf(fmt, first_name, wind_speed); | ||
// ^ warning: format string is not a string literal | ||
} | ||
|
||
int main() { | ||
say_hi("hello %s, it is %g degrees outside"); | ||
say_hi("hello %s, it is %d degrees outside!"); | ||
// ^ no diagnostic, but %d cannot format doubles | ||
} | ||
|
||
In this example, ``fmt`` is expected to format a ``const char *`` and a | ||
``double``, but these values are not passed to ``say_hi``. Without the | ||
``format`` attribute (which cannot apply in this case), the -Wformat-nonliteral | ||
diagnostic unnecessarily triggers in the body of ``say_hi``, and incorrect | ||
``say_hi`` call sites do not trigger a diagnostic. | ||
|
||
To complement the ``format`` attribute, Clang also defines the | ||
``format_matches`` attribute. Its syntax is similar to the ``format`` | ||
attribute's, but instead of taking the index of the first formatted value | ||
argument, it takes a C string literal with the expected specifiers: | ||
|
||
.. code-block:: c | ||
|
||
static const char *first_name; | ||
static double todays_temperature; | ||
static int wind_speed; | ||
|
||
__attribute__((__format_matches__(printf, 1, "%s %g"))) | ||
void say_hi(const char *fmt) { | ||
printf(fmt, first_name, todays_temperature); // no dignostic | ||
printf(fmt, first_name, wind_speed); // warning: format specifies type 'int' but the argument has type 'double' | ||
} | ||
|
||
int main() { | ||
say_hi("hello %s, it is %g degrees outside"); | ||
say_hi("it is %g degrees outside, have a good day %s!"); | ||
// warning: format specifies 'double' where 'const char *' is required | ||
// warning: format specifies 'const char *' where 'double' is required | ||
} | ||
|
||
The third argument to ``format_matches`` is expected to evaluate to a **C string | ||
literal** even when the format string would normally be a different type for the | ||
given flavor, like a ``CFStringRef`` or a ``NSString *``. | ||
|
||
The only requirement on the format string literal is that it has specifiers | ||
that are compatible with the arguments that will be used. It can contain | ||
arbitrary non-format characters. For instance, for the purposes of compile-time | ||
validation, ``"%s scored %g%% on her test"`` and ``"%s%g"`` are interchangeable | ||
as the format string argument. As a means of self-documentation, users may | ||
prefer the former when it provides a useful example of an expected format | ||
string. | ||
|
||
In the implementation of a function with the ``format_matches`` attribute, | ||
format verification works as if the format string was identical to the one | ||
specified in the attribute. | ||
|
||
.. code-block:: c | ||
|
||
__attribute__((__format_matches__(printf, 1, "%s %g"))) | ||
void say_hi(const char *fmt) { | ||
printf(fmt, "person", 546); | ||
// ^ warning: format specifies type 'double' but the | ||
// argument has type 'int' | ||
// note: format string is defined here: | ||
// __attribute__((__format_matches__(printf, 1, "%s %g"))) | ||
// ^~ | ||
} | ||
|
||
|
||
At the call sites of functions with the ``format_matches`` attribute, format | ||
verification instead compares the two format strings to evaluate their | ||
equivalence. Each format flavor defines equivalence between format specifiers. | ||
Generally speaking, two specifiers are equivalent if they format the same type. | ||
For instance, in the ``printf`` flavor, ``%2i`` and ``%-0.5d`` are compatible. | ||
When ``-Wformat-signedness`` is disabled, ``%d`` and ``%u`` are compatible. For | ||
a negative example, ``%ld`` is incompatible with ``%d``. | ||
|
||
Do note the following un-obvious cases: | ||
|
||
* Passing ``NULL`` as the format string does not trigger format diagnostics. | ||
* When the format string is not NULL, it cannot _miss_ specifiers, even in | ||
trailing positions. For instance, ``%d`` is not accepted when the required | ||
format is ``%d %d %d``. | ||
* While checks for the ``format`` attribute tolerate sone size mismatches | ||
that standard argument promotion renders immaterial (such as formatting an | ||
``int`` with ``%hhd``, which specifies a ``char``-sized integer), checks for | ||
``format_matches`` require specified argument sizes to match exactly. | ||
* Format strings expecting a variable modifier (such as ``%*s``) are | ||
incompatible with format strings that would itemize the variable modifiers | ||
(such as ``%i %s``), even if the two specify ABI-compatible argument lists. | ||
* All pointer specifiers, modifiers aside, are mutually incompatible. For | ||
instance, ``%s`` is not compatible with ``%p``, and ``%p`` is not compatible | ||
with ``%n``, and ``%hhn`` is incompatible with ``%s``, even if the pointers | ||
are ABI-compatible or identical on the selected platform. However, ``%0.5s`` | ||
is compatible with ``%s``, since the difference only exists in modifier flags. | ||
This is not overridable with ``-Wformat-pedantic`` or its inverse, which | ||
control similar behavior in ``-Wformat``. | ||
|
||
At this time, clang implements ``format_matches`` only for format types in the | ||
``printf`` family. This includes variants such as Apple's NSString format and | ||
the FreeBSD ``kprintf``, but excludes ``scanf``. Using a known but unsupported | ||
format silently fails in order to be compatible with other implementations that | ||
would support these formats. | ||
|
||
}]; | ||
} | ||
|
||
def FlagEnumDocs : Documentation { | ||
let Category = DocCatDecl; | ||
let Content = [{ | ||
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.