Skip to content

Commit b2b6919

Browse files
committed
Diagnose potential size confusion with VLA params
With Clang starting to support bounds safety checking annotations, we've begun to dabble in late parsing of function parameters so that an earlier parameter can refer to a later parameter by name. However, one situation where this can cause a problem is if the earlier parameter is actually referencing a variable from an outer scope. To help identify those situations, this introduces a new diagnostic -Wvla-potential-size-confusion (grouped under -Wvla). This diagnoses code like: int n; void func(int array[n], int n); to let the user know that the 'n' in 'array[n]' references the file scope variable and not the parameter.
1 parent cd4c10a commit b2b6919

File tree

5 files changed

+152
-1
lines changed

5 files changed

+152
-1
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,11 @@ Improvements to Clang's diagnostics
222222

223223
- Improve the diagnostics for shadows template parameter to report correct location (#GH129060).
224224

225+
- Added the ``-Wvla-potential-size-confusion`` diagnostic, which is grouped
226+
under ``-Wvla`` to diagnose when a variably-modified type in a function
227+
parameter list is using a variable from an outer scope as opposed to a
228+
variable declared later in the parameter list.
229+
225230
Improvements to Clang's time-trace
226231
----------------------------------
227232

clang/include/clang/Basic/DiagnosticGroups.td

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -936,7 +936,8 @@ def VexingParse : DiagGroup<"vexing-parse">;
936936
def VLAUseStaticAssert : DiagGroup<"vla-extension-static-assert">;
937937
def VLACxxExtension : DiagGroup<"vla-cxx-extension", [VLAUseStaticAssert]>;
938938
def VLAExtension : DiagGroup<"vla-extension", [VLACxxExtension]>;
939-
def VLA : DiagGroup<"vla", [VLAExtension]>;
939+
def VLASizeConfusion : DiagGroup<"vla-potential-size-confusion">;
940+
def VLA : DiagGroup<"vla", [VLAExtension, VLASizeConfusion]>;
940941
def VolatileRegisterVar : DiagGroup<"volatile-register-var">;
941942
def Visibility : DiagGroup<"visibility">;
942943
def ZeroLengthArray : DiagGroup<"zero-length-array">;

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,13 @@ def err_vla_in_coroutine_unsupported : Error<
170170
"variable length arrays in a coroutine are not supported">;
171171
def note_vla_unsupported : Note<
172172
"variable length arrays are not supported for the current target">;
173+
def warn_vla_size_expr_shadow : Warning<
174+
"variable length array size expression refers to declaration from an outer "
175+
"scope">, InGroup<VLASizeConfusion>;
176+
def note_vla_size_expr_shadow_param : Note<
177+
"does not refer to this declaration">;
178+
def note_vla_size_expr_shadow_actual : Note<
179+
"refers to this declaration instead">;
173180

174181
// C99 variably modified types
175182
def err_variably_modified_template_arg : Error<

clang/lib/Sema/SemaDecl.cpp

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
#include "clang/AST/MangleNumberingContext.h"
2727
#include "clang/AST/NonTrivialTypeVisitor.h"
2828
#include "clang/AST/Randstruct.h"
29+
#include "clang/AST/RecursiveASTVisitor.h"
2930
#include "clang/AST/StmtCXX.h"
3031
#include "clang/AST/Type.h"
3132
#include "clang/Basic/Builtins.h"
@@ -10333,6 +10334,75 @@ Sema::ActOnFunctionDeclarator(Scope *S, Declarator &D, DeclContext *DC,
1033310334
}
1033410335
}
1033510336

10337+
// Loop over the parameters to see if any of the size expressions contains
10338+
// a DeclRefExpr which refers to a variable from an outer scope that is
10339+
// also named later in the parameter list.
10340+
// e.g., int n; void func(int array[n], int n);
10341+
SmallVector<const DeclRefExpr *, 2> DRESizeExprs;
10342+
llvm::for_each(Params, [&](const ParmVarDecl *Param) {
10343+
// If we have any size expressions we need to check against, check them
10344+
// now.
10345+
for (const auto *DRE : DRESizeExprs) {
10346+
// Check to see if this parameter has the same name as one of the
10347+
// DeclRefExprs we wanted to test against. If so, then we found a
10348+
// situation where an earlier parameter refers to the name of a later
10349+
// parameter, which is (currently) only valid if there's a variable
10350+
// from an outer scope with the same name.
10351+
if (const auto *SizeExprND = dyn_cast<NamedDecl>(DRE->getDecl())) {
10352+
if (SizeExprND->getIdentifier() == Param->getIdentifier()) {
10353+
// Diagnose the DeclRefExpr from the parameter with the size
10354+
// expression.
10355+
Diag(DRE->getLocation(), diag::warn_vla_size_expr_shadow);
10356+
// Note the parameter that a user could be confused into thinking
10357+
// they're referring to.
10358+
Diag(Param->getLocation(), diag::note_vla_size_expr_shadow_param);
10359+
// Note the DeclRefExpr that's actually being used.
10360+
Diag(DRE->getDecl()->getLocation(),
10361+
diag::note_vla_size_expr_shadow_actual);
10362+
10363+
}
10364+
}
10365+
}
10366+
10367+
// To check whether its size expression is a simple DeclRefExpr, we first
10368+
// have to walk through pointers or references, but array types always
10369+
// decay to a pointer, so skip if this is a DecayedType.
10370+
QualType QT = Param->getType();
10371+
while (!isa<DecayedType>(QT.getTypePtr()) &&
10372+
(QT->isPointerType() || QT->isReferenceType()))
10373+
QT = QT->getPointeeType();
10374+
10375+
// An array type is always decayed to a pointer, so we need to get the
10376+
// original type in that case.
10377+
if (const auto *DT = QT->getAs<DecayedType>())
10378+
QT = DT->getOriginalType();
10379+
10380+
// Now we can see if it's a VLA type with a size expression.
10381+
// FIXME: it would be nice to handle constant-sized arrays as well,
10382+
// e.g., constexpr int n = 12; void foo(int array[n], int n);
10383+
// however, the constant expression is replaced by its value at the time
10384+
// we form the type, so we've lost that information here.
10385+
if (!QT->hasSizedVLAType())
10386+
return;
10387+
10388+
const VariableArrayType *VAT = getASTContext().getAsVariableArrayType(QT);
10389+
if (!VAT)
10390+
return;
10391+
10392+
class DeclRefFinder : public RecursiveASTVisitor<DeclRefFinder> {
10393+
SmallVectorImpl<const DeclRefExpr *> &Found;
10394+
10395+
public:
10396+
DeclRefFinder(SmallVectorImpl<const DeclRefExpr *> &Found)
10397+
: Found(Found) {}
10398+
bool VisitDeclRefExpr(const DeclRefExpr *DRE) {
10399+
Found.push_back(DRE);
10400+
return true;
10401+
}
10402+
} Finder(DRESizeExprs);
10403+
Finder.TraverseStmt(VAT->getSizeExpr());
10404+
});
10405+
1033610406
if (!getLangOpts().CPlusPlus) {
1033710407
// In C, find all the tag declarations from the prototype and move them
1033810408
// into the function DeclContext. Remove them from the surrounding tag
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
// RUN: %clang_cc1 %s -std=c23 -verify=expected,c -fsyntax-only
2+
// RUN: %clang_cc1 %s -std=c23 -verify=good -fsyntax-only -Wno-vla
3+
// RUN: %clang_cc1 -x c++ %s -verify -fsyntax-only
4+
// RUN: %clang_cc1 -DCARET -fsyntax-only -std=c23 -fno-diagnostics-show-line-numbers -fcaret-diagnostics-max-lines=1 %s 2>&1 | FileCheck %s -strict-whitespace
5+
6+
// good-no-diagnostics
7+
8+
int n, m; // #decl
9+
int size(int);
10+
11+
void foo(int vla[n], int n); // expected-warning {{variable length array size expression refers to declaration from an outer scope}} \
12+
expected-note {{does not refer to this declaration}} \
13+
expected-note@#decl {{refers to this declaration instead}}
14+
15+
void bar(int (*vla)[n], int n); // expected-warning {{variable length array size expression refers to declaration from an outer scope}} \
16+
expected-note {{does not refer to this declaration}} \
17+
expected-note@#decl {{refers to this declaration instead}}
18+
19+
void baz(int n, int vla[n]); // no diagnostic expected
20+
21+
void quux(int vla[n + 12], int n); // expected-warning {{variable length array size expression refers to declaration from an outer scope}} \
22+
expected-note {{does not refer to this declaration}} \
23+
expected-note@#decl {{refers to this declaration instead}}
24+
25+
void quibble(int vla[size(n)], int n); // expected-warning {{variable length array size expression refers to declaration from an outer scope}} \
26+
expected-note {{does not refer to this declaration}} \
27+
expected-note@#decl {{refers to this declaration instead}}
28+
29+
void quobble(int vla[n + m], int n, int m); // expected-warning 2 {{variable length array size expression refers to declaration from an outer scope}} \
30+
expected-note 2 {{does not refer to this declaration}} \
31+
expected-note@#decl 2 {{refers to this declaration instead}}
32+
33+
// For const int, we still treat the function as having a variably-modified
34+
// type, but only in C.
35+
const int x = 12; // #other-decl
36+
void quorble(int vla[x], int x); // c-warning {{variable length array size expression refers to declaration from an outer scope}} \
37+
c-note {{does not refer to this declaration}} \
38+
c-note@#other-decl {{refers to this declaration instead}}
39+
40+
// For constexpr int, the function has a constant array type. It would be nice
41+
// to diagnose this case as well, but the type system replaces the expression
42+
// with the constant value, and so the information about the name of the
43+
// variable used in the size expression is lost.
44+
constexpr int y = 12;
45+
void quuble(int vla[y], int y); // no diagnostic expected
46+
47+
#ifdef __cplusplus
48+
struct S {
49+
static int v; // #mem-var
50+
51+
void member_function(int vla[v], int v); // expected-warning {{variable length array size expression refers to declaration from an outer scope}} \
52+
expected-note {{does not refer to this declaration}} \
53+
expected-note@#mem-var {{refers to this declaration instead}}
54+
};
55+
#endif
56+
57+
#ifdef CARET
58+
// Test that caret locations make sense.
59+
int w;
60+
void quable(int vla[w], int w);
61+
62+
// CHECK: void quable(int vla[w], int w);
63+
// CHECK: ^
64+
// CHECK: void quable(int vla[w], int w);
65+
// CHECK: ^
66+
// CHECK: int w;
67+
// CHECK: ^
68+
#endif

0 commit comments

Comments
 (0)