blob: 99c31401adb81f4f6ed7a42dd7f47770fb9be115 [file] [log] [blame]
// 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
#include "lifetime_analysis/pointer_compatibility.h"
#include <functional>
#include "gtest/gtest.h"
#include "lifetime_annotations/lifetime_annotations.h"
#include "lifetime_annotations/test/run_on_code.h"
#include "clang/AST/ASTContext.h"
#include "clang/AST/Type.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/ASTMatchers/ASTMatchers.h"
#include "llvm/ADT/StringRef.h"
namespace clang {
namespace tidy {
namespace lifetimes {
namespace {
using clang::ast_matchers::cxxRecordDecl;
using clang::ast_matchers::enumDecl;
using clang::ast_matchers::hasName;
using clang::ast_matchers::match;
using clang::ast_matchers::selectFirst;
clang::QualType getClassType(llvm::StringRef name,
const clang::ASTContext& ast_context) {
return ast_context.getRecordType(selectFirst<clang::CXXRecordDecl>(
"class", match(cxxRecordDecl(hasName(name)).bind("class"),
const_cast<clang::ASTContext&>(ast_context))));
}
clang::QualType getEnumType(llvm::StringRef name,
const clang::ASTContext& ast_context) {
return ast_context.getEnumType(selectFirst<clang::EnumDecl>(
"enum", match(enumDecl(hasName(name)).bind("enum"),
const_cast<clang::ASTContext&>(ast_context))));
}
bool MayPointTo(clang::QualType pointer_type, clang::QualType object_type,
const clang::ASTContext& ast_context) {
return clang::tidy::lifetimes::MayPointTo(
pointer_type, object_type, const_cast<clang::ASTContext&>(ast_context));
}
TEST(PointerCompatibilityTest, MayPointTo) {
runOnCodeWithLifetimeHandlers(
"class Base {};"
"class Derived : public Base {};"
"class Unrelated {};"
"enum SignedEnum {};"
"enum UnsignedEnum : unsigned {};"
"enum LongEnum : long {};",
[](const clang::ASTContext& ast_context,
const LifetimeAnnotationContext&) {
auto pointer_to = [&ast_context](clang::QualType type) {
return ast_context.getPointerType(type);
};
clang::QualType void_type = ast_context.VoidTy;
clang::QualType char_type = ast_context.CharTy;
clang::QualType signed_char_type = ast_context.SignedCharTy;
clang::QualType unsigned_char_type = ast_context.UnsignedCharTy;
clang::QualType int_type = ast_context.IntTy;
clang::QualType unsigned_int_type = ast_context.UnsignedIntTy;
clang::QualType long_type = ast_context.LongTy;
clang::QualType bool_type = ast_context.BoolTy;
clang::QualType base_type = getClassType("Base", ast_context);
clang::QualType derived_type = getClassType("Derived", ast_context);
clang::QualType unrelated_type = getClassType("Unrelated", ast_context);
clang::QualType signed_enum_type =
getEnumType("SignedEnum", ast_context);
clang::QualType unsigned_enum_type =
getEnumType("UnsignedEnum", ast_context);
clang::QualType long_enum_type = getEnumType("LongEnum", ast_context);
// Trivial case: A pointer can point to its exact pointee type.
EXPECT_TRUE(MayPointTo(pointer_to(base_type), base_type, ast_context));
// void pointers and character pointers may point to anything.
EXPECT_TRUE(MayPointTo(pointer_to(void_type), base_type, ast_context));
EXPECT_TRUE(MayPointTo(pointer_to(char_type), base_type, ast_context));
EXPECT_TRUE(
MayPointTo(pointer_to(signed_char_type), base_type, ast_context));
EXPECT_TRUE(
MayPointTo(pointer_to(unsigned_char_type), base_type, ast_context));
// But an int pointer may not point at an unrelated type.
EXPECT_FALSE(MayPointTo(pointer_to(int_type), base_type, ast_context));
// We also allow a void pointer to be converted back to any other
// pointer type, but we don't allow the same for character pointers.
EXPECT_TRUE(MayPointTo(pointer_to(base_type), void_type, ast_context));
EXPECT_FALSE(MayPointTo(pointer_to(base_type), char_type, ast_context));
EXPECT_FALSE(
MayPointTo(pointer_to(base_type), signed_char_type, ast_context));
EXPECT_FALSE(
MayPointTo(pointer_to(base_type), unsigned_char_type, ast_context));
// A signed integer pointer may point to the unsigned variant of the
// integer type and vice versa.
EXPECT_TRUE(
MayPointTo(pointer_to(int_type), unsigned_int_type, ast_context));
EXPECT_TRUE(
MayPointTo(pointer_to(unsigned_int_type), int_type, ast_context));
// An enum pointer may point to any enum that has the same underlying
// type or to its underlying type (ignoring signedness in both cases).
// Signed enum:
EXPECT_TRUE(MayPointTo(pointer_to(signed_enum_type), signed_enum_type,
ast_context));
EXPECT_TRUE(MayPointTo(pointer_to(signed_enum_type), unsigned_enum_type,
ast_context));
EXPECT_TRUE(
MayPointTo(pointer_to(signed_enum_type), int_type, ast_context));
EXPECT_TRUE(MayPointTo(pointer_to(signed_enum_type), unsigned_int_type,
ast_context));
EXPECT_TRUE(
MayPointTo(pointer_to(int_type), signed_enum_type, ast_context));
EXPECT_TRUE(MayPointTo(pointer_to(unsigned_int_type), signed_enum_type,
ast_context));
// Unsigned enum:
EXPECT_TRUE(MayPointTo(pointer_to(unsigned_enum_type), signed_enum_type,
ast_context));
EXPECT_TRUE(MayPointTo(pointer_to(unsigned_enum_type),
unsigned_enum_type, ast_context));
EXPECT_TRUE(
MayPointTo(pointer_to(unsigned_enum_type), int_type, ast_context));
EXPECT_TRUE(MayPointTo(pointer_to(unsigned_enum_type),
unsigned_int_type, ast_context));
EXPECT_TRUE(
MayPointTo(pointer_to(int_type), unsigned_enum_type, ast_context));
EXPECT_TRUE(MayPointTo(pointer_to(unsigned_int_type),
unsigned_enum_type, ast_context));
// Underlying types of different width are not compatible:
EXPECT_FALSE(MayPointTo(pointer_to(long_enum_type), signed_enum_type,
ast_context));
EXPECT_FALSE(
MayPointTo(pointer_to(long_type), signed_enum_type, ast_context));
// A bool pointer may point to bool. This is a regression test for an
// assertion failure that we were getting because
// Type::isUnsignedIntegerType() considers `bool` to be an unsigned
// integer type.
EXPECT_TRUE(MayPointTo(pointer_to(bool_type), bool_type, ast_context));
// But an integer pointer may not point at an integer of a different
// size.
EXPECT_FALSE(MayPointTo(pointer_to(long_type), int_type, ast_context));
EXPECT_FALSE(MayPointTo(pointer_to(int_type), long_type, ast_context));
// A pointer to a base class may point to an object of the derived
// class, and vice versa. However, a pointer to a class type may not
// point to an object of a class unrelated by inheritance.
EXPECT_TRUE(
MayPointTo(pointer_to(base_type), derived_type, ast_context));
EXPECT_TRUE(
MayPointTo(pointer_to(derived_type), base_type, ast_context));
EXPECT_FALSE(
MayPointTo(pointer_to(base_type), unrelated_type, ast_context));
// A pointer to const may point at a non-const object (unsurprisingly),
// but we also allow the opposite. IOW, in propagating pointees through
// a function, we assume the function may cast away const.
// As a side note, this is consistent with the "strict aliasing" rules,
// which the pointer's pointee type and the dynamic type of the object
// to be similar by C++'s definition of similar.
EXPECT_TRUE(MayPointTo(pointer_to(base_type.withConst()), base_type,
ast_context));
EXPECT_TRUE(MayPointTo(pointer_to(base_type), base_type.withConst(),
ast_context));
// Likewise, a pointer to volatile may point at a non-volatile object
// and vice versa.
EXPECT_TRUE(MayPointTo(pointer_to(base_type.withVolatile()), base_type,
ast_context));
EXPECT_TRUE(MayPointTo(pointer_to(base_type), base_type.withVolatile(),
ast_context));
// We also allow points-to relationships that would be disallowed by
// invariance. The example below is equivalent to the following:
// int **pp;
// const int ***ppp = &pp;
// The code above doesn't compile, but the strict aliasing rules permit
// this type of aliasing.
EXPECT_TRUE(
MayPointTo(pointer_to(pointer_to(pointer_to(int_type.withConst()))),
pointer_to(pointer_to(int_type)), ast_context));
},
{});
}
} // namespace
} // namespace lifetimes
} // namespace tidy
} // namespace clang