Skip to content

Commit 8c5e9cf

Browse files
committed
[clang][Interp] Implement nullability argument checking
Implement constexpr checking for null pointers being passed to arguments annotated as nonnull.
1 parent f75c6ed commit 8c5e9cf

File tree

11 files changed

+231
-12
lines changed

11 files changed

+231
-12
lines changed

clang/lib/AST/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ add_clang_library(clangAST
8888
Interp/Record.cpp
8989
Interp/Source.cpp
9090
Interp/State.cpp
91+
Interp/InterpShared.cpp
9192
ItaniumCXXABI.cpp
9293
ItaniumMangle.cpp
9394
JSONNodeDumper.cpp

clang/lib/AST/Interp/ByteCodeExprGen.cpp

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,10 @@
1313
#include "Context.h"
1414
#include "Floating.h"
1515
#include "Function.h"
16+
#include "InterpShared.h"
1617
#include "PrimType.h"
1718
#include "Program.h"
19+
#include "clang/AST/Attr.h"
1820

1921
using namespace clang;
2022
using namespace clang::interp;
@@ -2656,6 +2658,7 @@ bool ByteCodeExprGen<Emitter>::VisitCallExpr(const CallExpr *E) {
26562658
QualType ReturnType = E->getCallReturnType(Ctx.getASTContext());
26572659
std::optional<PrimType> T = classify(ReturnType);
26582660
bool HasRVO = !ReturnType->isVoidType() && !T;
2661+
const FunctionDecl *FuncDecl = E->getDirectCallee();
26592662

26602663
if (HasRVO) {
26612664
if (DiscardResult) {
@@ -2673,17 +2676,16 @@ bool ByteCodeExprGen<Emitter>::VisitCallExpr(const CallExpr *E) {
26732676
}
26742677
}
26752678

2676-
auto Args = E->arguments();
2679+
auto Args = llvm::ArrayRef(E->getArgs(), E->getNumArgs());
26772680
// Calling a static operator will still
26782681
// pass the instance, but we don't need it.
26792682
// Discard it here.
26802683
if (isa<CXXOperatorCallExpr>(E)) {
2681-
if (const auto *MD =
2682-
dyn_cast_if_present<CXXMethodDecl>(E->getDirectCallee());
2684+
if (const auto *MD = dyn_cast_if_present<CXXMethodDecl>(FuncDecl);
26832685
MD && MD->isStatic()) {
26842686
if (!this->discard(E->getArg(0)))
26852687
return false;
2686-
Args = drop_begin(Args, 1);
2688+
Args = Args.drop_front();
26872689
}
26882690
}
26892691

@@ -2693,13 +2695,25 @@ bool ByteCodeExprGen<Emitter>::VisitCallExpr(const CallExpr *E) {
26932695
return false;
26942696
}
26952697

2698+
llvm::BitVector NonNullArgs = collectNonNullArgs(FuncDecl, Args);
26962699
// Put arguments on the stack.
2700+
unsigned ArgIndex = 0;
26972701
for (const auto *Arg : Args) {
26982702
if (!this->visit(Arg))
26992703
return false;
2704+
2705+
// If we know the callee already, check the known parametrs for nullability.
2706+
if (FuncDecl && NonNullArgs[ArgIndex]) {
2707+
PrimType ArgT = classify(Arg).value_or(PT_Ptr);
2708+
if (ArgT == PT_Ptr || ArgT == PT_FnPtr) {
2709+
if (!this->emitCheckNonNullArg(ArgT, Arg))
2710+
return false;
2711+
}
2712+
}
2713+
++ArgIndex;
27002714
}
27012715

2702-
if (const FunctionDecl *FuncDecl = E->getDirectCallee()) {
2716+
if (FuncDecl) {
27032717
const Function *Func = getFunction(FuncDecl);
27042718
if (!Func)
27052719
return false;
@@ -2748,7 +2762,7 @@ bool ByteCodeExprGen<Emitter>::VisitCallExpr(const CallExpr *E) {
27482762
if (!this->visit(E->getCallee()))
27492763
return false;
27502764

2751-
if (!this->emitCallPtr(ArgSize, E))
2765+
if (!this->emitCallPtr(ArgSize, E, E))
27522766
return false;
27532767
}
27542768

clang/lib/AST/Interp/Function.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,10 @@
1515
#ifndef LLVM_CLANG_AST_INTERP_FUNCTION_H
1616
#define LLVM_CLANG_AST_INTERP_FUNCTION_H
1717

18-
#include "Source.h"
1918
#include "Descriptor.h"
19+
#include "Source.h"
2020
#include "clang/AST/ASTLambda.h"
21+
#include "clang/AST/Attr.h"
2122
#include "clang/AST/Decl.h"
2223
#include "llvm/Support/raw_ostream.h"
2324

@@ -108,6 +109,8 @@ class Function final {
108109
/// Checks if the first argument is a RVO pointer.
109110
bool hasRVO() const { return HasRVO; }
110111

112+
bool hasNonNullAttr() const { return getDecl()->hasAttr<NonNullAttr>(); }
113+
111114
/// Range over the scope blocks.
112115
llvm::iterator_range<llvm::SmallVector<Scope, 2>::const_iterator>
113116
scopes() const {

clang/lib/AST/Interp/Interp.cpp

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,9 @@
77
//===----------------------------------------------------------------------===//
88

99
#include "Interp.h"
10-
#include <limits>
11-
#include <vector>
1210
#include "Function.h"
1311
#include "InterpFrame.h"
12+
#include "InterpShared.h"
1413
#include "InterpStack.h"
1514
#include "Opcode.h"
1615
#include "PrimType.h"
@@ -22,6 +21,10 @@
2221
#include "clang/AST/Expr.h"
2322
#include "clang/AST/ExprCXX.h"
2423
#include "llvm/ADT/APSInt.h"
24+
#include <limits>
25+
#include <vector>
26+
27+
using namespace clang;
2528

2629
using namespace clang;
2730
using namespace clang::interp;
@@ -622,6 +625,28 @@ bool CheckDeclRef(InterpState &S, CodePtr OpPC, const DeclRefExpr *DR) {
622625
return false;
623626
}
624627

628+
bool CheckNonNullArgs(InterpState &S, CodePtr OpPC, const Function *F,
629+
const CallExpr *CE, unsigned ArgSize) {
630+
auto Args = llvm::ArrayRef(CE->getArgs(), CE->getNumArgs());
631+
auto NonNullArgs = collectNonNullArgs(F->getDecl(), Args);
632+
unsigned Offset = 0;
633+
unsigned Index = 0;
634+
for (const Expr *Arg : Args) {
635+
if (NonNullArgs[Index] && Arg->getType()->isPointerType()) {
636+
const Pointer &ArgPtr = S.Stk.peek<Pointer>(ArgSize - Offset);
637+
if (ArgPtr.isZero()) {
638+
const SourceLocation &Loc = S.Current->getLocation(OpPC);
639+
S.CCEDiag(Loc, diag::note_non_null_attribute_failed);
640+
return false;
641+
}
642+
}
643+
644+
Offset += align(primSize(S.Ctx.classify(Arg).value_or(PT_Ptr)));
645+
++Index;
646+
}
647+
return true;
648+
}
649+
625650
bool Interpret(InterpState &S, APValue &Result) {
626651
// The current stack frame when we started Interpret().
627652
// This is being used by the ops to determine wheter

clang/lib/AST/Interp/Interp.h

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,10 @@ bool CheckThis(InterpState &S, CodePtr OpPC, const Pointer &This);
113113
/// Checks if a method is pure virtual.
114114
bool CheckPure(InterpState &S, CodePtr OpPC, const CXXMethodDecl *MD);
115115

116+
/// Checks if all the arguments annotated as 'nonnull' are in fact not null.
117+
bool CheckNonNullArgs(InterpState &S, CodePtr OpPC, const Function *F,
118+
const CallExpr *CE, unsigned ArgSize);
119+
116120
/// Sets the given integral value to the pointer, which is of
117121
/// a std::{weak,partial,strong}_ordering type.
118122
bool SetThreeWayComparisonField(InterpState &S, CodePtr OpPC,
@@ -1980,6 +1984,7 @@ inline bool CallVar(InterpState &S, CodePtr OpPC, const Function *Func,
19801984

19811985
return false;
19821986
}
1987+
19831988
inline bool Call(InterpState &S, CodePtr OpPC, const Function *Func,
19841989
uint32_t VarArgSize) {
19851990
if (Func->hasThisPointer()) {
@@ -2083,7 +2088,8 @@ inline bool CallBI(InterpState &S, CodePtr &PC, const Function *Func,
20832088
return false;
20842089
}
20852090

2086-
inline bool CallPtr(InterpState &S, CodePtr OpPC, uint32_t ArgSize) {
2091+
inline bool CallPtr(InterpState &S, CodePtr OpPC, uint32_t ArgSize,
2092+
const CallExpr *CE) {
20872093
const FunctionPointer &FuncPtr = S.Stk.pop<FunctionPointer>();
20882094

20892095
const Function *F = FuncPtr.getFunction();
@@ -2095,6 +2101,12 @@ inline bool CallPtr(InterpState &S, CodePtr OpPC, uint32_t ArgSize) {
20952101
}
20962102
assert(F);
20972103

2104+
// Check argument nullability state.
2105+
if (F->hasNonNullAttr()) {
2106+
if (!CheckNonNullArgs(S, OpPC, F, CE, ArgSize))
2107+
return false;
2108+
}
2109+
20982110
assert(ArgSize >= F->getWrittenArgSize());
20992111
uint32_t VarArgSize = ArgSize - F->getWrittenArgSize();
21002112

@@ -2151,6 +2163,18 @@ inline bool OffsetOf(InterpState &S, CodePtr OpPC, const OffsetOfExpr *E) {
21512163
return true;
21522164
}
21532165

2166+
template <PrimType Name, class T = typename PrimConv<Name>::T>
2167+
inline bool CheckNonNullArg(InterpState &S, CodePtr OpPC) {
2168+
const T &Arg = S.Stk.peek<T>();
2169+
if (!Arg.isZero())
2170+
return true;
2171+
2172+
const SourceLocation &Loc = S.Current->getLocation(OpPC);
2173+
S.CCEDiag(Loc, diag::note_non_null_attribute_failed);
2174+
2175+
return false;
2176+
}
2177+
21542178
//===----------------------------------------------------------------------===//
21552179
// Read opcode arguments
21562180
//===----------------------------------------------------------------------===//

clang/lib/AST/Interp/InterpShared.cpp

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
//===--- InterpShared.cpp ---------------------------------------*- C++ -*-===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#include "InterpShared.h"
10+
#include "clang/AST/Attr.h"
11+
#include "llvm/ADT/BitVector.h"
12+
13+
namespace clang {
14+
namespace interp {
15+
16+
llvm::BitVector collectNonNullArgs(const FunctionDecl *F,
17+
const llvm::ArrayRef<const Expr *> &Args) {
18+
llvm::BitVector NonNullArgs;
19+
if (!F)
20+
return NonNullArgs;
21+
22+
assert(F);
23+
NonNullArgs.resize(Args.size());
24+
25+
for (const auto *Attr : F->specific_attrs<NonNullAttr>()) {
26+
if (!Attr->args_size()) {
27+
NonNullArgs.set();
28+
break;
29+
} else
30+
for (auto Idx : Attr->args()) {
31+
unsigned ASTIdx = Idx.getASTIndex();
32+
if (ASTIdx >= Args.size())
33+
continue;
34+
NonNullArgs[ASTIdx] = true;
35+
}
36+
}
37+
38+
return NonNullArgs;
39+
}
40+
41+
} // namespace interp
42+
} // namespace clang

clang/lib/AST/Interp/InterpShared.h

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
//===--- InterpShared.h -----------------------------------------*- C++ -*-===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#ifndef LLVM_CLANG_LIB_AST_INTERP_SHARED_H
10+
#define LLVM_CLANG_LIB_AST_INTERP_SHARED_H
11+
12+
#include "llvm/ADT/BitVector.h"
13+
14+
namespace clang {
15+
class FunctionDecl;
16+
class Expr;
17+
18+
namespace interp {
19+
20+
llvm::BitVector collectNonNullArgs(const FunctionDecl *F,
21+
const llvm::ArrayRef<const Expr *> &Args);
22+
23+
} // namespace interp
24+
} // namespace clang
25+
26+
#endif

clang/lib/AST/Interp/Opcodes.td

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ def CallBI : Opcode {
206206
}
207207

208208
def CallPtr : Opcode {
209-
let Args = [ArgUint32];
209+
let Args = [ArgUint32, ArgCallExpr];
210210
let Types = [];
211211
}
212212

@@ -706,3 +706,8 @@ def InvalidDeclRef : Opcode {
706706
}
707707

708708
def ArrayDecay : Opcode;
709+
710+
def CheckNonNullArg : Opcode {
711+
let Types = [PtrTypeClass];
712+
let HasGroup = 1;
713+
}

clang/test/AST/Interp/nullable.cpp

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
// RUN: %clang_cc1 -fexperimental-new-constant-interpreter -verify=expected,both %s
2+
// RUN: %clang_cc1 -verify=ref,both %s
3+
4+
5+
constexpr int dummy = 1;
6+
constexpr const int *null = nullptr;
7+
8+
namespace simple {
9+
__attribute__((nonnull))
10+
constexpr int simple1(const int*) {
11+
return 1;
12+
}
13+
static_assert(simple1(&dummy) == 1, "");
14+
static_assert(simple1(nullptr) == 1, ""); // both-error {{not an integral constant expression}} \
15+
// both-note {{null passed to a callee}}
16+
static_assert(simple1(null) == 1, ""); // both-error {{not an integral constant expression}} \
17+
// both-note {{null passed to a callee}}
18+
19+
__attribute__((nonnull)) // both-warning {{applied to function with no pointer arguments}}
20+
constexpr int simple2(const int &a) {
21+
return 12;
22+
}
23+
static_assert(simple2(1) == 12, "");
24+
}
25+
26+
namespace methods {
27+
struct S {
28+
__attribute__((nonnull(2))) // both-warning {{only applies to pointer arguments}}
29+
__attribute__((nonnull(3)))
30+
constexpr int foo(int a, const void *p) const {
31+
return 12;
32+
}
33+
34+
__attribute__((nonnull(3)))
35+
constexpr int foo2(...) const {
36+
return 12;
37+
}
38+
39+
__attribute__((nonnull))
40+
constexpr int foo3(...) const {
41+
return 12;
42+
}
43+
};
44+
45+
constexpr S s{};
46+
static_assert(s.foo(8, &dummy) == 12, "");
47+
48+
static_assert(s.foo2(nullptr) == 12, "");
49+
static_assert(s.foo2(1, nullptr) == 12, ""); // both-error {{not an integral constant expression}} \
50+
// both-note {{null passed to a callee}}
51+
52+
constexpr S *s2 = nullptr;
53+
static_assert(s2->foo3() == 12, ""); // both-error {{not an integral constant expression}} \
54+
// both-note {{member call on dereferenced null pointer}}
55+
}
56+
57+
namespace fnptrs {
58+
__attribute__((nonnull))
59+
constexpr int add(int a, const void *p) {
60+
return a + 1;
61+
}
62+
__attribute__((nonnull(3)))
63+
constexpr int applyBinOp(int a, int b, int (*op)(int, const void *)) {
64+
return op(a, nullptr); // both-note {{null passed to a callee}}
65+
}
66+
static_assert(applyBinOp(10, 20, add) == 11, ""); // both-error {{not an integral constant expression}} \
67+
// both-note {{in call to}}
68+
69+
static_assert(applyBinOp(10, 20, nullptr) == 11, ""); // both-error {{not an integral constant expression}} \
70+
// both-note {{null passed to a callee}}
71+
}
72+
73+
namespace lambdas {
74+
auto lstatic = [](const void *P) __attribute__((nonnull)) { return 3; };
75+
static_assert(lstatic(nullptr) == 3, ""); // both-error {{not an integral constant expression}} \
76+
// both-note {{null passed to a callee}}
77+
}

clang/test/Sema/attr-nonnull.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
// RUN: %clang_cc1 %s -verify -fsyntax-only
2+
// RUN: %clang_cc1 %s -verify -fsyntax-only -fexperimental-new-constant-interpreter
23

34
void f1(int *a1, int *a2, int *a3, int *a4, int *a5, int *a6, int *a7,
45
int *a8, int *a9, int *a10, int *a11, int *a12, int *a13, int *a14,

0 commit comments

Comments
 (0)