Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .github/workflows/ci-macos-local.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ jobs:
strategy:
matrix:
build_type: [ Release ]
os: [macos-12]
os: [macos-latest]
compiler:
- cc: cc
cxx: c++
Expand All @@ -32,6 +32,10 @@ jobs:
with:
fetch-depth: 0

- uses: actions/setup-python@v5
with:
python-version: '3.13'

- name: Install llvm@17
run: |
brew install llvm@17
Expand Down
6 changes: 5 additions & 1 deletion .github/workflows/ci-macos.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
strategy:
matrix:
build_type: [ Release ]
os: [macos-12]
os: [macos-13, macos-14, macos-15]
compiler:
- cc: cc
cxx: c++
Expand All @@ -34,6 +34,10 @@ jobs:
steps:
- uses: actions/checkout@v3

- uses: actions/setup-python@v5
with:
python-version: '3.13'

- name: Install conan
run: pip3 install conan

Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ Note that cppsafe will detect system includes via `c++`, you can override it via
CXX=/opt/homebrew/opt/llvm/bin/clang cppsafe example.cpp -- -std=c++20
```

> Note: cppship should be used with std17 or above, since cpp17 has changed the rule for temporaries.

### With compile\_commands.json
Generally, you should use cppsafe with compile\_commands.json.

Expand Down
2 changes: 2 additions & 0 deletions include/cppsafe/lifetime/LifetimePsetBuilder.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ class PSBuilder {

DISALLOW_COPY_AND_MOVE(PSBuilder);

virtual const Expr* ignoreTransparentExprs(const Expr* E, bool IgnoreLValueToRValue = false) const = 0;

virtual void setPSet(const Expr* E, const PSet& PS) = 0;
virtual void setPSet(const PSet& LHS, PSet RHS, SourceRange Range) = 0;

Expand Down
21 changes: 21 additions & 0 deletions integration_test/case/elements_view_cxx17.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
#include <utility> // std::declval/get
#include <string>

template <class T>
void __lifetime_type_category() {}

template <class T>
void __lifetime_type_category_arg(T&&) {}

Expand Down Expand Up @@ -110,6 +113,24 @@ using namespace std;

int main()
{
using T1 = elements_view<std::map<int, std::string>, 0>;
using T1_iter = T1::iterator;
using T2 = elements_view<std::map<int, std::string>, 1>;
using T2_iter = T2::iterator;
using T3 = elements_view<std::map<int, std::string>&, 0>;
using T3_iter = T1::iterator;
using T4 = elements_view<std::map<int, std::string>&, 1>;
using T4_iter = T2::iterator;

__lifetime_type_category<T1>(); // expected-warning {{lifetime type category is Pointer with pointee const int}}
__lifetime_type_category<T1_iter>(); // expected-warning {{lifetime type category is Pointer with pointee const int}}
__lifetime_type_category<T2>(); // expected-warning {{lifetime type category is Pointer with pointee}}
__lifetime_type_category<T2_iter>(); // expected-warning {{lifetime type category is Pointer with pointee}}
__lifetime_type_category<T3>(); // expected-warning {{lifetime type category is Pointer with pointee const int}}
__lifetime_type_category<T3_iter>(); // expected-warning {{lifetime type category is Pointer with pointee const int}}
__lifetime_type_category<T4>(); // expected-warning {{lifetime type category is Pointer with pointee}}
__lifetime_type_category<T4_iter>(); // expected-warning {{lifetime type category is Pointer with pointee}}

map<int, string> mp{{1, "one"}, {2, "two"}, {3, "three"}, {4, "four"}};

Use(elements<0>(mp));
Expand Down
2 changes: 1 addition & 1 deletion integration_test/feature/structureal_binding.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ int main()

const auto& [x3, y3] = get1();
__lifetime_pset(x3); // expected-warning {{(unknown)}}
__lifetime_pset(y3); // expected-warning {{(unknown)}}
__lifetime_pset(y3); // expected-warning {{(global)}}

auto& [x4, y4] = get2(d);
__lifetime_pset(x4); // expected-warning {{(unknown)}}
Expand Down
13 changes: 13 additions & 0 deletions integration_test/safety/contract_return.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,17 @@ void test()
Derive d;
auto* p2 = d.foo(&x);
__lifetime_pset(p2); // expected-warning {{pset(p2) = (x)}}
}

int* get()
{
static int x = 0;
return &x;
}

int* get2()
{
static int x;
static int* y = &x;
return y;
}
73 changes: 73 additions & 0 deletions integration_test/safety/contract_return_aggr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,12 @@ struct A {
Inner i;
};

struct B {
int* x;
};

A foo(int* x);
B get();

void test_contract_return()
{
Expand All @@ -27,4 +32,72 @@ void test_contract_return()
__lifetime_pset(m.y); // expected-warning {{pset(m.y) = ((global))}}
__lifetime_pset(m.i); // expected-warning {{pset(m.i) = (m.i)}}
__lifetime_pset(m.i.m); // expected-warning {{pset(m.i.m) = (x)}}

A n = foo(get().x);
__lifetime_pset(n); // expected-warning {{pset(n) = (n)}}
__lifetime_pset(n.x); // expected-warning {{pset(n.x) = ((global))}}
__lifetime_pset(n.y); // expected-warning {{pset(n.y) = ((global))}}
__lifetime_pset(n.i); // expected-warning {{pset(n.i) = (n.i)}}
__lifetime_pset(n.i.m); // expected-warning {{pset(n.i.m) = ((global))}}

int y = 0;
m = foo(&y);
__lifetime_pset(m); // expected-warning {{pset(m) = (m)}}
__lifetime_pset(m.x); // expected-warning {{pset(m.x) = (y)}}
__lifetime_pset(m.y); // expected-warning {{pset(m.y) = ((global))}}
__lifetime_pset(m.i); // expected-warning {{pset(m.i) = (m.i)}}
__lifetime_pset(m.i.m); // expected-warning {{pset(m.i.m) = (y)}}

n = foo(B{&y}.x);
__lifetime_pset(n); // expected-warning {{pset(n) = (n)}}
__lifetime_pset(n.x); // expected-warning {{pset(n.x) = (y)}}
__lifetime_pset(n.y); // expected-warning {{pset(n.y) = ((global))}}
__lifetime_pset(n.i); // expected-warning {{pset(n.i) = (n.i)}}
__lifetime_pset(n.i.m); // expected-warning {{pset(n.i.m) = (y)}}

n = foo(B{}.x);
__lifetime_pset(n); // expected-warning {{pset(n) = (n)}}
__lifetime_pset(n.x); // expected-warning {{pset(n.x) = ((global))}}
__lifetime_pset(n.y); // expected-warning {{pset(n.y) = ((global))}}
__lifetime_pset(n.i); // expected-warning {{pset(n.i) = (n.i)}}
__lifetime_pset(n.i.m); // expected-warning {{pset(n.i.m) = ((global))}}

int* z = B{}.x;
__lifetime_pset(z); // expected-warning {{pset(z) = ((null))}}
}

B createB()
{
const B b { .x = nullptr };
return b;
}

B createStaticB()
{
static int t = 0;
static const B b { .x = &t };
__lifetime_pmap();
return b;
}

struct [[gsl::Owner(char)]] String {};
struct [[gsl::Pointer(char)]] StringView {
StringView(const String&);

void Foo();
};

struct Wrapper {
StringView s1;
StringView s2;
};

Wrapper get(StringView);

void test_string_view()
{
auto s = get(String{}); // expected-note {{temporary was destroyed at the end of the full expression}}
__lifetime_pset(s.s1); // expected-warning {{pset(s.s1) = ((invalid))}}
__lifetime_pset(s.s2); // expected-warning {{pset(s.s2) = ((invalid))}}
s.s1.Foo(); // expected-warning {{passing a dangling pointer as argument}}
}
8 changes: 8 additions & 0 deletions integration_test/safety/type_aggr_init.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,14 @@ void test_default_init()
__lifetime_pset(b.c); // expected-warning {{pset(b.c) = (*b.c)}}
}

void test_list_init()
{
int t = 0;
Aggr a = {.y = &t };
__lifetime_pset(a); // expected-warning {{pset(a) = (a)}}
__lifetime_pset(a.y); // expected-warning {{pset(a.y) = (t)}}
}

void test_copy_init()
{
int m = 0;
Expand Down
11 changes: 8 additions & 3 deletions lib/lifetime/LifetimePsetBuilder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ class PSetsBuilder final : public ConstStmtVisitor<PSetsBuilder, void>, public P
/// Does not ignore MaterializeTemporaryExpr as Expr::IgnoreParenImpCasts
/// would.
// NOLINTBEGIN(readability-else-after-return)
static const Expr* ignoreTransparentExprs(const Expr* E, bool IgnoreLValueToRValue = false)
const Expr* ignoreTransparentExprs(const Expr* E, bool IgnoreLValueToRValue = false) const override
{
while (true) {
E = E->IgnoreParens();
Expand Down Expand Up @@ -150,7 +150,7 @@ class PSetsBuilder final : public ConstStmtVisitor<PSetsBuilder, void>, public P
// NOLINTEND(readability-else-after-return)
}

static bool isIgnoredStmt(const Stmt* S)
bool isIgnoredStmt(const Stmt* S)
{
const Expr* E = dyn_cast<Expr>(S);
return E && ignoreTransparentExprs(E) != E;
Expand Down Expand Up @@ -1068,14 +1068,19 @@ class PSetsBuilder final : public ConstStmtVisitor<PSetsBuilder, void>, public P
// NOLINTNEXTLINE(readability-identifier-naming): required by parent
void VisitVarDecl(const VarDecl* VD)
{
if (const auto* DD = dyn_cast<DecompositionDecl>(VD)) {
if (const auto* DD = dyn_cast_if_present<DecompositionDecl>(VD)) {
visitDecompositionDecl(DD);
return;
}

const Expr* Initializer = VD->getInit();
const SourceRange Range = VD->getSourceRange();

// NB. clang don't generage ExprWithCleanups node in CFG
if (const auto* E = dyn_cast_if_present<ExprWithCleanups>(Initializer)) {
Initializer = E->getSubExpr();
}

switch (classifyTypeCategory(VD->getType())) {
case TypeCategory::Pointer: {
PSet PS;
Expand Down
13 changes: 7 additions & 6 deletions lib/lifetime/type/Aggregate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -164,20 +164,21 @@ void handleAggregateCopy(const Expr* LHS, const Expr* RHS, PSBuilder& Builder)
const auto OtherPS = Builder.getPSet(RHS);
CPPSAFE_ASSERT(OtherPS.vars().size() <= 1);

const auto* Other = OtherPS.vars().empty() ? nullptr : &*OtherPS.vars().begin();
// if PSet(RHS) = {}, use it as a placeholder to derive members
const Variable Candidate(Builder.ignoreTransparentExprs(RHS));
const auto* Other = OtherPS.vars().empty() ? &Candidate : &*OtherPS.vars().begin();
const auto* RD = LHS->getType()->getAsCXXRecordDecl();
const Variable Base(LHS);
expandAggregate(Base, RD,
[&Base, &Builder, Other, &OtherPS](const Variable& LhsSubVar, const SubVarPath& Path, TypeClassification TC) {
if (!Other) {
Builder.setVarPSet(LhsSubVar, OtherPS);
return;
}

const Variable RhsSubVar(Other->chainFields(Path));
if (!TC.isPointer()) {
return;
}
if (OtherPS.containsGlobal()) {
Builder.setVarPSet(LhsSubVar, PSet::globalVar());
return;
}

auto PS = Builder.getVarPSet(RhsSubVar);
CPPSAFE_ASSERT(PS);
Expand Down