blob: 0b4f3540185f9b7744021317227d03f374a80d43 [file] [log] [blame] [edit]
// Part of the Crubit project, under the Apache License v2.0 with LLVM
// Exceptions. See /LICENSE for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
// Tests that control flow is taken into account correctly.
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "lifetime_analysis/test/lifetime_analysis_test.h"
namespace clang {
namespace tidy {
namespace lifetimes {
namespace {
TEST_F(LifetimeAnalysisTest, ReturnArgumentWithControlFlow) {
EXPECT_THAT(GetLifetimes(R"(
int* get_lesser_of(int* a, int* b) {
if (*a < *b) {
return a;
}
return b;
}
)"),
LifetimesAre({{"get_lesser_of", "a, a -> a"}}));
}
TEST_F(LifetimeAnalysisTest, ReturnPtrArgumentWithConditionalOperator) {
EXPECT_THAT(GetLifetimes(R"(
int* get_lesser_of(int* a, int* b) {
return *a < *b? a : b;
}
)"),
LifetimesAre({{"get_lesser_of", "a, a -> a"}}));
}
TEST_F(LifetimeAnalysisTest, ReturnRefArgumentWithConditionalOperator) {
EXPECT_THAT(GetLifetimes(R"(
int& get_lesser_of(int& a, int& b) {
return a < b? a : b;
}
)"),
LifetimesAre({{"get_lesser_of", "a, a -> a"}}));
}
TEST_F(LifetimeAnalysisTest, ControlFlowExceptionsWorkSometimes) {
// This test documents that we do understand the control flow resulting from
// exceptions in some limited circumstances. However, this is not true in the
// general case -- see the test ControlFlowExceptionsNotSupportedInGeneral --
// and it's a non-goal to add support for this.
EXPECT_THAT(GetLifetimes(R"(
int* target(int* a, int* b) {
try {
throw 42;
return a;
} catch(...) {
return b;
}
}
)"),
LifetimesAre({{"target", "a, b -> b"}}));
}
TEST_F(LifetimeAnalysisTest, ControlFlowExceptionsNotSupportedInGeneral) {
// This test documents that we do not in general treat the control flow
// resulting from exceptions correctly; changing this is a non-goal.
EXPECT_THAT(GetLifetimes(R"(
void may_throw() {
throw 42;
}
int* target(int* a, int* b) {
try {
may_throw();
return a;
} catch(...) {
return b;
}
}
)"),
LifetimesContain({{"target", "a, b -> a"}}));
}
TEST_F(LifetimeAnalysisTest, DoublePointerWithConditionalAssignment) {
// This is a regression test for a bug where we were not taking all
// substitutions into account in the return value lifetimes.
EXPECT_THAT(GetLifetimes(R"(
int** target(int** pp1, int** pp2) {
if (**pp1 > **pp2) {
*pp1 = *pp2;
}
return pp2;
}
)"),
LifetimesAre({{"target", "(a, b), (a, c) -> (a, c)"}}));
}
TEST_F(LifetimeAnalysisTest, ReturnArgumentWithControlFlowAndJoin) {
EXPECT_THAT(GetLifetimes(R"(
int* get_lesser_of(int* a, int* b) {
int* p = a;
if (*a < *b) {
p = b;
}
return p;
}
)"),
LifetimesAre({{"get_lesser_of", "a, a -> a"}}));
}
TEST_F(LifetimeAnalysisTest, ReturnArgumentWithUnnecessaryAssignment) {
EXPECT_THAT(GetLifetimes(R"(
int* target(int* p, int* a, int* b) {
for (int i=0; i<*a; i++) {
p = a;
p = b;
}
return p;
}
)"),
LifetimesAre({{"target", "a, b, a -> a"}}));
}
TEST_F(LifetimeAnalysisTest, TakeAReferenceInControlFlow) {
EXPECT_THAT(GetLifetimes(R"(
int* target(int* p, int* a) {
int local = 42;
int** pp = &a;
if (*a < *p) {
pp = &p;
}
p = &local;
return *pp;
}
)"),
LifetimesAre({{"target",
"ERROR: function returns reference to a local"}}));
}
TEST_F(LifetimeAnalysisTest, TakeAReferenceEndOfBlock) {
// Make sure that the analysis handles statement ordering correctly.
EXPECT_THAT(GetLifetimes(R"(
int* target(int* a) {
int local = 42;
int** pp = &a;
int* b = a;
int* p = a;
if (*p < *a) {
p = b;
pp = &p;
b = &local;
}
return *pp;
}
)"),
LifetimesAre({{"target", "a -> a"}}));
}
TEST_F(LifetimeAnalysisTest, TakeAReferenceSneaky) {
EXPECT_THAT(GetLifetimes(R"(
int* target(int* p, int* a) {
int local = 42;
int** pp = &a;
int* b = a;
for (int i=0; i<*a; i++) {
p = b;
pp = &p;
b = &local;
}
return *pp;
}
)"),
LifetimesAre({{"target",
"ERROR: function returns reference to a local"}}));
}
TEST_F(LifetimeAnalysisTest, TakeAReferenceSneakyParam) {
EXPECT_THAT(GetLifetimes(R"(
int* target(int* p, int* a, int* c) {
int** pp = &a;
int* b = a;
for (int i=0; i<*a; i++) {
p = b;
pp = &p;
b = c;
}
return *pp;
}
)"),
LifetimesAre({{"target", "a, a, a -> a"}}));
}
TEST_F(LifetimeAnalysisTest, TakeAReferenceAndOverwrite) {
EXPECT_THAT(GetLifetimes(R"(
int* target(int* p, int* a, int* b) {
int** pp = &a;
if (*a < *p) {
pp = &p;
}
p = b;
return *pp;
}
)"),
LifetimesAre({{"target", "a, b, b -> b"}}));
}
TEST_F(LifetimeAnalysisTest, TakeAReferenceTooStrict) {
EXPECT_THAT(GetLifetimes(R"(
int* target(int* p, int* a) {
int** pp = &a;
if (*p < *a) {
p = a;
pp = &p;
}
return *pp;
}
)"),
LifetimesAre({{"target", "a, a -> a"}}));
// TODO(mboehme): This result is too strict. This is because at the
// return statement, the analysis concludes that
// - pp may be pointing at either p or a, and
// - p may either still have its original value or it may be pointing at a
// The analysis doesn't "know" that the combination "p has original value and
// pp points at p" can never occur. It may be possible to solve this with path
// conditions -- IIUC, this is exactly what they are for.
}
} // namespace
} // namespace lifetimes
} // namespace tidy
} // namespace clang