Skip to content

[clang][analyzer] add proper function signature validation #1

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
3 changes: 3 additions & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1060,6 +1060,9 @@ impact the linker behaviour like the other `-static-*` flags.
Crash and bug fixes
^^^^^^^^^^^^^^^^^^^

- Fixed a crash in ``UnixAPIMisuseChecker`` and ``MallocChecker`` when analyzing
code with non-standard ``getline`` or ``getdelim`` function signatures.

Improvements
^^^^^^^^^^^^

Expand Down
1 change: 1 addition & 0 deletions clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ add_clang_library(clangStaticAnalyzerCheckers
ExprInspectionChecker.cpp
FixedAddressChecker.cpp
FuchsiaHandleChecker.cpp
FunctionSignature.cpp
GCDAntipatternChecker.cpp
GenericTaintChecker.cpp
GTestChecker.cpp
Expand Down
188 changes: 188 additions & 0 deletions clang/lib/StaticAnalyzer/Checkers/FunctionSignature.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
//=== FunctionSignature.cpp - Validation of functions signatures. --*- C++ -*-//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// Provides utilities for validating function signatures.
//
//===----------------------------------------------------------------------===//

#include "FunctionSignature.h"
#include "clang/AST/ASTContext.h"
#include "clang/AST/Decl.h"
#include "clang/AST/DeclCXX.h"

namespace clang {
namespace ento {

Signature::Signature(ArgTypes ArgTys, RetType RetTy) {
for (const std::optional<QualType> &Arg : ArgTys) {
if (!Arg) {
Invalid = true;
return;
}
assertArgTypeSuitableForSignature(*Arg);
this->ArgTys.push_back(*Arg);
}
if (!RetTy) {
Invalid = true;
return;
}
assertRetTypeSuitableForSignature(*RetTy);
this->RetTy = *RetTy;
}

bool Signature::isInvalid() const { return Invalid; }

bool Signature::matches(const FunctionDecl *FD) const {
assert(!isInvalid());
// Check the number of arguments.
if (FD->param_size() != ArgTys.size())
return false;

// The "restrict" keyword is illegal in C++, however, many libc
// implementations use the "__restrict" compiler intrinsic in functions
// prototypes. The "__restrict" keyword qualifies a type as a restricted type
// even in C++.
// In case of any non-C99 languages, we don't want to match based on the
// restrict qualifier because we cannot know if the given libc implementation
// qualifies the paramater type or not.
const auto RemoveRestrict = [&FD](QualType T) {
if (!FD->getASTContext().getLangOpts().C99)
T.removeLocalRestrict();
return T;
};

// Check the return type.
if (!isIrrelevant(RetTy)) {
const QualType FDRetTy =
RemoveRestrict(FD->getReturnType().getCanonicalType());
if (RetTy != FDRetTy)
return false;
}

// Check the argument types.
for (auto [Idx, ArgTy] : llvm::enumerate(ArgTys)) {
if (isIrrelevant(ArgTy))
continue;
const QualType FDArgTy =
RemoveRestrict(FD->getParamDecl(Idx)->getType().getCanonicalType());
if (ArgTy != FDArgTy)
return false;
}

return true;
}

bool Signature::isIrrelevant(QualType T) { return T.isNull(); }

void Signature::assertArgTypeSuitableForSignature(QualType T) {
assert((T.isNull() || !T->isVoidType()) &&
"We should have no void types in the spec");
assert((T.isNull() || T.isCanonical()) &&
"We should only have canonical types in the spec");
}

void Signature::assertRetTypeSuitableForSignature(QualType T) {
assert((T.isNull() || T.isCanonical()) &&
"We should only have canonical types in the spec");
}

TypeFactory::TypeFactory(const ASTContext &ACtx) : ACtx(ACtx) {}

QualType TypeFactory::getVoidTy() const { return ACtx.VoidTy; }
QualType TypeFactory::getCharTy() const { return ACtx.CharTy; }
QualType TypeFactory::getWCharTy() const { return ACtx.WCharTy; }
QualType TypeFactory::getIntTy() const { return ACtx.IntTy; }
QualType TypeFactory::getUnsignedIntTy() const { return ACtx.UnsignedIntTy; }
QualType TypeFactory::getLongTy() const { return ACtx.LongTy; }
QualType TypeFactory::getSizeTy() const { return ACtx.getSizeType(); }

QualType TypeFactory::getPointerTy(QualType Ty) const {
return ACtx.getPointerType(Ty);
}

std::optional<QualType>
TypeFactory::getPointerTy(std::optional<QualType> Ty) const {
return Ty ? std::optional<QualType>(getPointerTy(*Ty)) : std::nullopt;
}

QualType TypeFactory::getConstTy(QualType Ty) const { return Ty.withConst(); }

std::optional<QualType>
TypeFactory::getConstTy(std::optional<QualType> Ty) const {
return Ty ? std::optional<QualType>(getConstTy(*Ty)) : std::nullopt;
}

QualType TypeFactory::getRestrictTy(QualType Ty) const {
return ACtx.getLangOpts().C99 ? ACtx.getRestrictType(Ty) : Ty;
}

std::optional<QualType>
TypeFactory::getRestrictTy(std::optional<QualType> Ty) const {
return Ty ? std::optional<QualType>(getRestrictTy(*Ty)) : std::nullopt;
}

std::optional<QualType> TypeFactory::lookupTy(StringRef Name) const {
IdentifierInfo &II = ACtx.Idents.get(Name);
auto LookupRes = ACtx.getTranslationUnitDecl()->lookup(&II);
if (LookupRes.empty())
return std::nullopt;

// Prioritize typedef declarations.
// This is needed in case of C struct typedefs. E.g.:
// typedef struct FILE FILE;
// In this case, we have a RecordDecl 'struct FILE' with the name 'FILE'
// and we have a TypedefDecl with the name 'FILE'.
for (Decl *D : LookupRes)
if (auto *TD = dyn_cast<TypedefNameDecl>(D))
return ACtx.getTypeDeclType(TD).getCanonicalType();

// Find the first TypeDecl.
// There maybe cases when a function has the same name as a struct.
// E.g. in POSIX: `struct stat` and the function `stat()`:
// int stat(const char *restrict path, struct stat *restrict buf);
for (Decl *D : LookupRes)
if (auto *TD = dyn_cast<TypeDecl>(D))
return ACtx.getTypeDeclType(TD).getCanonicalType();

return std::nullopt;
}

QualType TypeFactory::getVoidPtrTy() const { return getPointerTy(getVoidTy()); }

QualType TypeFactory::getCharPtrTy() const { return getPointerTy(getCharTy()); }

QualType TypeFactory::getConstCharPtrTy() const {
return getPointerTy(getConstTy(getCharTy()));
}

QualType TypeFactory::getConstVoidPtrTy() const {
return getPointerTy(getConstTy(getVoidTy()));
}

void SignatureMatcher::addSignature(StringRef Name, const Signature &Sign) {
Signatures.try_emplace(Name, Sign);
}

bool SignatureMatcher::matches(const FunctionDecl *FD,
StringRef ExpectedName) const {
if (FD->getName() != ExpectedName)
return false;

const auto It = Signatures.find(ExpectedName);
if (It == Signatures.end())
return false;

const Signature &Sign = It->second;
if (Sign.isInvalid())
return false;

return Sign.matches(FD);
}

} // namespace ento
} // namespace clang
117 changes: 117 additions & 0 deletions clang/lib/StaticAnalyzer/Checkers/FunctionSignature.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
//=== FunctionSignature.h - Validation of functions signatures. ----*- C++ -*-//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// Provides utilities for validating function signatures.
//
//===----------------------------------------------------------------------===//

#ifndef LLVM_CLANG_LIB_STATICANALYZER_CHECKERS_FUNCTIONSIGNATURE_H
#define LLVM_CLANG_LIB_STATICANALYZER_CHECKERS_FUNCTIONSIGNATURE_H

#include <optional>

#include "clang/AST/Type.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/StringMap.h"
#include "llvm/ADT/StringRef.h"

namespace clang {

class FunctionDecl;
class ASTContext;
class IdentifierInfo;

namespace ento {

// The signature of a function we want to check. This is a concessive
// signature, meaning there may be irrelevant types in the signature
// which we do not check against a function with concrete types.
// All types in the spec need to be canonical.
class Signature {
using ArgQualTypes = SmallVector<QualType>;
ArgQualTypes ArgTys;
QualType RetTy;
// True if any component type is not found by lookup.
bool Invalid = false;

public:
using ArgTypes = ArrayRef<std::optional<QualType>>;
using RetType = std::optional<QualType>;

// Construct a signature from optional types. If any of the optional types
// are not set then the signature will be invalid.
Signature(ArgTypes ArgTys, RetType RetTy);
Signature(const Signature &) = default;
Signature(Signature &&) = default;
Signature &operator=(const Signature &) = default;
Signature &operator=(Signature &&) = default;

bool isInvalid() const;
bool matches(const FunctionDecl *FD) const;

private:
static bool isIrrelevant(QualType T);
static void assertArgTypeSuitableForSignature(QualType T);
static void assertRetTypeSuitableForSignature(QualType T);
};

/// Provides type creation utilities for Signature class.
/// It encapsulates the logic for creating pointer, const, restrict types
/// and looking up types by name from the AST.
class TypeFactory {
const ASTContext &ACtx;

public:
explicit TypeFactory(const ASTContext &ACtx);

// Basic types from AST
QualType getVoidTy() const;
QualType getCharTy() const;
QualType getWCharTy() const;
QualType getIntTy() const;
QualType getUnsignedIntTy() const;
QualType getLongTy() const;
QualType getSizeTy() const;

// Common type mutations
QualType getPointerTy(QualType Ty) const;
std::optional<QualType> getPointerTy(std::optional<QualType> Ty) const;
QualType getConstTy(QualType Ty) const;
std::optional<QualType> getConstTy(std::optional<QualType> Ty) const;
QualType getRestrictTy(QualType Ty) const;
std::optional<QualType> getRestrictTy(std::optional<QualType> Ty) const;

// Type lookup by name in AST
std::optional<QualType> lookupTy(StringRef Name) const;

// Common composite types
QualType getVoidPtrTy() const;
QualType getCharPtrTy() const;
QualType getConstCharPtrTy() const;
QualType getConstVoidPtrTy() const;
};

/// Helper class for matching function signatures.
/// This class stores function signatures and provides an API to check
/// if a given FunctionDecl matches the expected signature.
class SignatureMatcher {
using SignatureMap = llvm::StringMap<Signature>;
SignatureMap Signatures;

public:
// Add a function signature to the matcher.
void addSignature(StringRef Name, const Signature &Sign);

// Check if a function declaration matches the expected signature.
bool matches(const FunctionDecl *FD, StringRef ExpectedName) const;
};

} // namespace ento
} // namespace clang

#endif // LLVM_CLANG_LIB_STATICANALYZER_CHECKERS_FUNCTIONSIGNATURE_H
Loading