[DRAFT] [clang] translate block-scope enum, function and local label declarations - #2153
Draft
VladimirMakaev wants to merge 1 commit into
Draft
VladimirMakaev wants to merge 1 commit into
VladimirMakaev wants to merge 1 commit into
Conversation
…ions
The clang frontend translates a `DeclStmt` according to its first
declaration. Variables, bindings and records were translated, typedefs,
using declarations and namespace aliases were no-ops, and any other kind
raised `Unimplemented`. That aborted the translation of the whole
enclosing function, which was then replaced by a bodyless declaration:
nothing was reported in it, not even for the statements before the
declaration, and its callers saw an unknown callee. The kinds that reach
this in ordinary code are enums (`enum { kLen = 4 };`), function
declarations (`extern int helper(int);`, or `X x();` in C++),
`_Static_assert` (also `static_assert` in C once `<assert.h>` is
included), GNU local labels (`__label__ l;`) and C++20 `using enum`.
For instance, no overrun was reported in
void f(void) {
enum { kLen = 4 };
char buf[kLen];
buf[kLen] = 0;
}
Enum and function declarations now go to `collect_all_decl`, which skips
declarations that are not variables but still initializes the variables
that can follow them in the same statement (`enum E { A } e = A;`,
`int g(void), x = 3;`). Static assertions, local label declarations and
using-enum declarations are no-ops. Other unknown kinds still raise
`Unimplemented`.
Local labels need more than a no-op. They exist so that a macro can
define a label in each of its expansions, which gives distinct labels
with the same name in one function:
#define FREE_UNLESS(cond, p) \
do { __label__ skip; if (cond) goto skip; free(p); skip:; } while (0)
FREE_UNLESS(c, p);
FREE_UNLESS(c, q);
The frontend identified labels by name, so both `skip` labels would
share one CFG node, whose successor would be the code after only one of
them: here a false double free of `q`, and no path to the exit of the
function. A goto statement already exports the declaration of its
label, so the AST exporter now exports it for label statements too, and
the frontend identifies labels by that declaration.
Routing block-scope enums is not enough on its own either. The values of
enum constants are looked up in a map that only contains the enums
added to it so far, and an unknown constant evaluates to 0. Enums are
added when they are declared at the top level, when the record that
contains them is translated, or when their type is translated. In C,
enum constants have type `int`, so using one does not add its enum, and
`buf[kLen]` above would become `buf[0]`. The same already happened,
without any abort, for an enum nested in a struct that is not
translated before the constant is used: a block-scope struct
(statements are translated last to first, so a use of the constant is
translated before any variable of that type is declared), or a struct
declared in a header other than the one of the current source file.
`ClangPointers` now records the enum that declares each enum constant
while it visits the AST, and `get_enum_constant_expr` adds the enum of
an unknown constant to the map before looking the constant up again.
Some cases remain. The AST exporter does not export enums declared
inside a type name (`sizeof(enum { N = 1 })`) or a parameter list, so
their constants still evaluate to 0. And as for top-level enums, the
value of a constant is computed by translating its initializer, so
initializers other than literal arithmetic (`?:`, calls to `constexpr`
functions, static members) still give an unknown value. Exporting the
value clang computes for each constant would fix both.
## Test plan
New tests, whose `_bad`/`_Bad` cases are not reported on main (the
function is dropped or the constant evaluates to 0), while the `_ok` and
`_Good` controls stay silent:
- c/pulse/enum.c: values of block-scope enum constants, an enum and a
variable declared in the same statement, a typedef of a block-scope
enum, an enum in a block-scope struct, an enum in a struct declared in
another header, statements before an enum declaration
- c/pulse/frontend.c: a block-scope function declaration (resolved to
the definition that follows), a function and a variable declared in
the same statement, `_Static_assert`, `__label__`, and two expansions
of a macro with a local label (no false double free, and a real double
free after them is found)
- c/bufferoverrun/trivial.c: array indexed with a block-scope enum
constant
- c/frontend/enumeration/block_scope_enum.c: the CFG shows the values of
block-scope enum constants
- c/frontend/gotostmt/local_labels.c: the CFG has one node per local
label, also when local labels share a name with each other or with a
label of the function
- cpp/pulse/frontend.cpp (also issues.exp-11): block-scope enum class,
function declaration from the "most vexing parse"
- cpp/pulse-20/using_enum.cpp: `using enum`
Ran the C, C++, Java, Kotlin and SIL codetoanalyze tests: the only
expected output changes are the new tests. The ObjC tests were not run;
none of them has a block-scope declaration of the newly handled kinds,
a local label or an enum nested in a record.
The only AST exporter test with a label statement is ObjCTest.m, which
needs the macOS SDK. I ran the old and the new plugin on it on Linux:
the outputs differ only in the label statement, which now has the same
record as the goto statement before it. I applied that change to the
Json and Yojson expected outputs, and to the Biniou one by hand
(`bdump` was not available), copying the encoding of the goto
statement's record. The Json and Yojson outputs of the other exporter
tests are unchanged.
This branch has not been deployed
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
A block-scope
DeclStmtof an unhandled kind (an enum, a function declaration,_Static_assert,__label__, C++20using enum) raisedUnimplemented, so the whole enclosing function wasdropped and nothing was reported in it. For example (from
c/bufferoverrun/trivial.c):The fix:
collect_all_decl, which still initializes variablesdeclared in the same statement; the other kinds are no-ops.
ClangPointersrecords the enum of each enum constant, so an unknown constant no longerevaluates to 0 (also fixes enums nested in structs not yet translated).
identifies labels by it, so local labels with the same name get distinct CFG nodes.
Test plan
New tests in
c/pulse/enum.c,c/pulse/frontend.c,c/bufferoverrun/trivial.c,cpp/pulse/frontend.cppandcpp/pulse-20/using_enum.cpp: the bad cases are now reported andthe ok controls stay silent. New frontend tests
block_scope_enum.candlocal_labels.ccheckenum values and one CFG node per local label. The C, C++, Java, Kotlin and SIL codetoanalyze tests
pass; the AST exporter
ObjCTest.mexpected outputs are updated for the label change.