Index: clang/include/clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h =================================================================== --- clang/include/clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h +++ clang/include/clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h @@ -407,6 +407,11 @@ const ParamVarRegion *getParameterLocation(unsigned Index, unsigned BlockCount) const; + /// Returns the argument value if it is a symbol or a region (see + /// getArgSVal()). Otherwise it returns the parameter location (see + /// getParameterLocation()). + SVal getArgObject(unsigned Index, unsigned BlockCount) const; + /// Returns true if on the current path, the argument was constructed by /// calling a C++ constructor over it. This is an internal detail of the /// analysis which doesn't necessarily represent the program semantics: @@ -444,6 +449,12 @@ /// can be retrieved from its construction context. Optional getReturnValueUnderConstruction() const; + + /// If the call returns a C++ record type then this function retrieves and + /// returns its return value from its construction context. Otherwise it + /// simply returns its return value. + SVal getReturnObject() const; + // Iterator access to formal parameters and their types. private: struct GetTypeFn { Index: clang/lib/StaticAnalyzer/Core/CallEvent.cpp =================================================================== --- clang/lib/StaticAnalyzer/Core/CallEvent.cpp +++ clang/lib/StaticAnalyzer/Core/CallEvent.cpp @@ -221,6 +221,20 @@ return PVR; } +SVal CallEvent::getArgObject(unsigned Index, unsigned BlockCount) const { + SVal Arg = getArgSVal(Index); + const Expr *ArgExpr = getArgExpr(Index); + + if (ArgExpr->getValueKind() != VK_RValue) + return Arg; + + if (!ArgExpr->getType().getDesugaredType( + getState()->getStateManager().getContext())->isRecordType()) + return Arg; + + return loc::MemRegionVal(getParameterLocation(Index, BlockCount)); +} + /// Returns true if a type is a pointer-to-const or reference-to-const /// with no further indirection. static bool isPointerToConst(QualType Ty) { @@ -531,6 +545,14 @@ return RetVal; } +SVal CallEvent::getReturnObject() const { + Optional RetValUnderConstr = getReturnValueUnderConstruction(); + if (RetValUnderConstr.hasValue()) + return *RetValUnderConstr; + + return getReturnValue(); +} + ArrayRef AnyFunctionCall::parameters() const { const FunctionDecl *D = getDecl(); if (!D) Index: clang/unittests/StaticAnalyzer/CMakeLists.txt =================================================================== --- clang/unittests/StaticAnalyzer/CMakeLists.txt +++ clang/unittests/StaticAnalyzer/CMakeLists.txt @@ -12,6 +12,7 @@ RegisterCustomCheckersTest.cpp StoreTest.cpp SymbolReaperTest.cpp + TestParameterLocation.cpp TestReturnValueUnderConstruction.cpp ) Index: clang/unittests/StaticAnalyzer/TestParameterLocation.cpp =================================================================== --- /dev/null +++ clang/unittests/StaticAnalyzer/TestParameterLocation.cpp @@ -0,0 +1,143 @@ +//===- unittests/StaticAnalyzer/TestParameterLocation.cpp -----------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#include "CheckerRegistration.h" +#include "clang/StaticAnalyzer/Core/Checker.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" +#include "clang/StaticAnalyzer/Frontend/AnalysisConsumer.h" +#include "clang/StaticAnalyzer/Frontend/CheckerRegistry.h" +#include "clang/Tooling/Tooling.h" +#include "gtest/gtest.h" + +namespace clang { +namespace ento { +namespace { + +class TestParameterLocationChecker : public Checker { +public: + void checkPostCall(const CallEvent &Call, CheckerContext &C) const { + const IdentifierInfo *ID = Call.getCalleeIdentifier(); + if (!ID) + return; + + SVal Arg0 = Call.getArgSVal(0); + const ParamVarRegion *Param0Loc = + Call.getParameterLocation(0, C.blockCount()); + SVal Arg0Obj = Call.getArgObject(0, C.blockCount()); + + if (ID->getName() == "passC") { + // Since `PassC` takes an object by value, the object is copy-constructed + // into the location of the parameter. + ASSERT_TRUE(Param0Loc); + + ASSERT_FALSE(Arg0.isConstant() || Arg0.getAsSymbol() || + Arg0.getAsRegion()); + + ASSERT_TRUE(Arg0Obj.getAsRegion()); + ASSERT_TRUE(Arg0Obj.getAsRegion() == Param0Loc); + return; + } + + if (ID->getName() == "passCPtr" || ID->getName() == "passCRef" || + ID->getName() == "passCConstPtr" || ID->getName() == "passCConstRef" || + ID->getName() == "passVoidPtr") { + // Since these functions take their arguments by pointer or reference, + // no copy-construction happens, but the region of the object is passed. + ASSERT_TRUE(Param0Loc); + + ASSERT_TRUE(Arg0.getAsRegion()); + + ASSERT_TRUE(Arg0Obj.getAsRegion()); + ASSERT_TRUE(Arg0Obj == Arg0); + return; + } + + if (ID->getName() == "passInt") { + // Since `PassInt` takes an integer by value, it is copied into the + // parameter but not copy-constructed since it is a basic value. + ASSERT_TRUE(Param0Loc); + + ASSERT_TRUE(Arg0.isConstant()); + + ASSERT_TRUE(Arg0Obj.isConstant()); + ASSERT_TRUE(Arg0Obj == Arg0); + return; + } + + llvm_unreachable("Unknown callee."); + } +}; + +void addTestParameterLocationChecker( + AnalysisASTConsumer &AnalysisConsumer, AnalyzerOptions &AnOpts) { + AnOpts.CheckersAndPackages = + {{"test.TestParameterLocation", true}}; + AnalysisConsumer.AddCheckerRegistrationFn([](CheckerRegistry &Registry) { + Registry.addChecker( + "test.TestParameterLocation", "", ""); + }); +} + +TEST(TestParameterLocationChecker, + ParameterLocationChecker) { + EXPECT_TRUE(runCheckerOnCode( + R"(class C { + public: + C(int nn): n(nn) {} + operator int() const { return n; } + virtual ~C() {} + private: + int n; + }; + + int passC(C c) { + return c; + } + + int passCConstPtr(const C *ccp) { + return *ccp; + } + + int passCConstRef(const C &ccr) { + return ccr; + } + + int passCPtr(C *cp) { + return *cp; + } + + int passCRef(C &cr) { + return cr; + } + + int passVoidPtr(void *vp) { + return *((int*)vp); + } + + int passInt(int k) { + return k; + } + + void foo() { + int m = 1; + C cc(m); + void *vp = &m; + m = passC(cc); + m = passCPtr(&cc); + m = passCRef(cc); + m = passCConstPtr(&cc); + m = passCConstRef(cc); + m = passVoidPtr(vp); + m = passInt(m); + })")); +} + +} // namespace +} // namespace ento +} // namespace clang Index: clang/unittests/StaticAnalyzer/TestReturnValueUnderConstruction.cpp =================================================================== --- clang/unittests/StaticAnalyzer/TestReturnValueUnderConstruction.cpp +++ clang/unittests/StaticAnalyzer/TestReturnValueUnderConstruction.cpp @@ -23,18 +23,46 @@ : public Checker { public: void checkPostCall(const CallEvent &Call, CheckerContext &C) const { - // Only calls with origin expression are checked. These are `returnC()` - // and C::C(). - if (!Call.getOriginExpr()) + const IdentifierInfo *ID = Call.getCalleeIdentifier(); + if (!ID) return; - // Since `returnC` returns an object by value, the invocation results - // in an object of type `C` constructed into variable `c`. Thus the - // return value of `CallEvent::getReturnValueUnderConstruction()` must - // be non-empty and has to be a `MemRegion`. - Optional RetVal = Call.getReturnValueUnderConstruction(); - ASSERT_TRUE(RetVal); - ASSERT_TRUE(RetVal->getAsRegion()); + SVal RetVal = Call.getReturnValue(); + Optional RetValUC = Call.getReturnValueUnderConstruction(); + SVal RetObj = Call.getReturnObject(); + + if (ID->getName() == "returnC") { + // Since `returnC` returns an object by value, the invocation results + // in an object of type `C` constructed into variable `c`. Thus the + // return value of `CallEvent::getReturnValueUnderConstruction()` must + // be non-empty and has to be a `MemRegion`. + ASSERT_TRUE(RetValUC); + ASSERT_TRUE(RetValUC->getAsRegion()); + + // In this case `CallEvent::getReturnValue()` returns a `LazyCompoundVal`. + ASSERT_TRUE(RetVal.getAs().hasValue()); + + // The result of `CallEvent::getReturnObject()` is the same as the result + // of `CallEvent::getReturnValueUnderConstruction()`. + ASSERT_TRUE(RetObj == *RetValUC); + return; + } + + if (ID->getName() == "returnVoidPtr") { + // Since `returnVoidPtr` returns a basic value (pointer), it has no + // consturcion context. + ASSERT_FALSE(RetValUC); + + // In this case `CallEvent::getReturnValue()` returns a `MemRegion`. + ASSERT_TRUE(RetVal.getAsRegion()); + + // The result of `CallEvent::getReturnObject()` is the same as the result + // of `CallEvent::getReturnValueUnderConstruction()`. + ASSERT_TRUE(RetObj == RetVal); + return; + } + + llvm_unreachable("Unknown callee."); } }; @@ -64,8 +92,13 @@ return c; } + void *returnVoidPtr(int m) { + return &m; + } + void foo() { C c = returnC(1); + void *vp = returnVoidPtr(1); })")); }