Skip to content

[analyzer] Fix crash when modelling 'getline' function in checkers #145229

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

Merged
merged 6 commits into from
Jun 24, 2025
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
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. (#GH144884)

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

Expand Down
40 changes: 24 additions & 16 deletions clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -430,8 +430,8 @@ class MallocChecker
CHECK_FN(checkGMemdup)
CHECK_FN(checkGMallocN)
CHECK_FN(checkGMallocN0)
CHECK_FN(preGetdelim)
CHECK_FN(checkGetdelim)
CHECK_FN(preGetDelimOrGetLine)
CHECK_FN(checkGetDelimOrGetLine)
CHECK_FN(checkReallocN)
CHECK_FN(checkOwnershipAttr)

Expand All @@ -445,15 +445,16 @@ class MallocChecker
const CallDescriptionMap<CheckFn> PreFnMap{
// NOTE: the following CallDescription also matches the C++ standard
// library function std::getline(); the callback will filter it out.
{{CDM::CLibrary, {"getline"}, 3}, &MallocChecker::preGetdelim},
{{CDM::CLibrary, {"getdelim"}, 4}, &MallocChecker::preGetdelim},
{{CDM::CLibrary, {"getline"}, 3}, &MallocChecker::preGetDelimOrGetLine},
{{CDM::CLibrary, {"getdelim"}, 4}, &MallocChecker::preGetDelimOrGetLine},
};

const CallDescriptionMap<CheckFn> PostFnMap{
// NOTE: the following CallDescription also matches the C++ standard
// library function std::getline(); the callback will filter it out.
{{CDM::CLibrary, {"getline"}, 3}, &MallocChecker::checkGetdelim},
{{CDM::CLibrary, {"getdelim"}, 4}, &MallocChecker::checkGetdelim},
{{CDM::CLibrary, {"getline"}, 3}, &MallocChecker::checkGetDelimOrGetLine},
{{CDM::CLibrary, {"getdelim"}, 4},
&MallocChecker::checkGetDelimOrGetLine},
};

const CallDescriptionMap<CheckFn> FreeingMemFnMap{
Expand Down Expand Up @@ -1482,8 +1483,9 @@ static bool isFromStdNamespace(const CallEvent &Call) {
return FD->isInStdNamespace();
}

void MallocChecker::preGetdelim(ProgramStateRef State, const CallEvent &Call,
CheckerContext &C) const {
void MallocChecker::preGetDelimOrGetLine(ProgramStateRef State,
const CallEvent &Call,
CheckerContext &C) const {
// Discard calls to the C++ standard library function std::getline(), which
// is completely unrelated to the POSIX getline() that we're checking.
if (isFromStdNamespace(Call))
Expand All @@ -1505,8 +1507,9 @@ void MallocChecker::preGetdelim(ProgramStateRef State, const CallEvent &Call,
C.addTransition(State);
}

void MallocChecker::checkGetdelim(ProgramStateRef State, const CallEvent &Call,
CheckerContext &C) const {
void MallocChecker::checkGetDelimOrGetLine(ProgramStateRef State,
const CallEvent &Call,
CheckerContext &C) const {
// Discard calls to the C++ standard library function std::getline(), which
// is completely unrelated to the POSIX getline() that we're checking.
if (isFromStdNamespace(Call))
Expand All @@ -1518,14 +1521,19 @@ void MallocChecker::checkGetdelim(ProgramStateRef State, const CallEvent &Call,
if (!CE)
return;

const auto LinePtr =
getPointeeVal(Call.getArgSVal(0), State)->getAs<DefinedSVal>();
const auto Size =
getPointeeVal(Call.getArgSVal(1), State)->getAs<DefinedSVal>();
if (!LinePtr || !Size || !LinePtr->getAsRegion())
const auto LinePtrOpt = getPointeeVal(Call.getArgSVal(0), State);
const auto SizeOpt = getPointeeVal(Call.getArgSVal(1), State);
if (!LinePtrOpt || !SizeOpt || LinePtrOpt->isUnknownOrUndef() ||
SizeOpt->isUnknownOrUndef())
return;

const auto LinePtr = LinePtrOpt->getAs<DefinedSVal>();
const auto Size = SizeOpt->getAs<DefinedSVal>();
const MemRegion *LinePtrReg = LinePtr->getAsRegion();
if (!LinePtrReg)
return;

State = setDynamicExtent(State, LinePtr->getAsRegion(), *Size);
State = setDynamicExtent(State, LinePtrReg, *Size);
C.addTransition(MallocUpdateRefState(C, CE, State,
AllocationFamily(AF_Malloc), *LinePtr));
}
Expand Down
32 changes: 23 additions & 9 deletions clang/lib/StaticAnalyzer/Checkers/UnixAPIChecker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ class UnixAPIMisuseChecker : public Checker<check::PreCall> {

void CheckOpen(CheckerContext &C, const CallEvent &Call) const;
void CheckOpenAt(CheckerContext &C, const CallEvent &Call) const;
void CheckGetDelim(CheckerContext &C, const CallEvent &Call) const;
void CheckGetDelimOrGetline(CheckerContext &C, const CallEvent &Call) const;
void CheckPthreadOnce(CheckerContext &C, const CallEvent &Call) const;

void CheckOpenVariant(CheckerContext &C, const CallEvent &Call,
Expand Down Expand Up @@ -128,7 +128,7 @@ ProgramStateRef UnixAPIMisuseChecker::EnsurePtrNotNull(
const StringRef PtrDescr,
std::optional<std::reference_wrapper<const BugType>> BT) const {
const auto Ptr = PtrVal.getAs<DefinedSVal>();
if (!Ptr)
if (!Ptr || !PtrExpr->getType()->isPointerType())
return State;

const auto [PtrNotNull, PtrNull] = State->assume(*Ptr);
Expand Down Expand Up @@ -177,7 +177,7 @@ void UnixAPIMisuseChecker::checkPreCall(const CallEvent &Call,
CheckPthreadOnce(C, Call);

else if (is_contained({"getdelim", "getline"}, FName))
CheckGetDelim(C, Call);
CheckGetDelimOrGetline(C, Call);
}
void UnixAPIMisuseChecker::ReportOpenBug(CheckerContext &C,
ProgramStateRef State,
Expand Down Expand Up @@ -332,8 +332,12 @@ ProgramStateRef UnixAPIMisuseChecker::EnsureGetdelimBufferAndSizeCorrect(

// We have a pointer to a pointer to the buffer, and a pointer to the size.
// We want what they point at.
auto LinePtrSVal = getPointeeVal(LinePtrPtrSVal, State)->getAs<DefinedSVal>();
auto NSVal = getPointeeVal(SizePtrSVal, State);
const auto LinePtrValOpt = getPointeeVal(LinePtrPtrSVal, State);
if (!LinePtrValOpt)
return nullptr;

const auto LinePtrSVal = LinePtrValOpt->getAs<DefinedSVal>();
const auto NSVal = getPointeeVal(SizePtrSVal, State);
if (!LinePtrSVal || !NSVal || NSVal->isUnknown())
return nullptr;

Expand All @@ -350,9 +354,16 @@ ProgramStateRef UnixAPIMisuseChecker::EnsureGetdelimBufferAndSizeCorrect(
// If it is defined, and known, its size must be less than or equal to
// the buffer size.
auto NDefSVal = NSVal->getAs<DefinedSVal>();
if (!NDefSVal)
return LinePtrNotNull;

auto &SVB = C.getSValBuilder();
auto LineBufSize =
getDynamicExtent(LinePtrNotNull, LinePtrSVal->getAsRegion(), SVB);

const MemRegion *LinePtrRegion = LinePtrSVal->getAsRegion();
if (!LinePtrRegion)
return LinePtrNotNull;

auto LineBufSize = getDynamicExtent(LinePtrNotNull, LinePtrRegion, SVB);
auto LineBufSizeGtN = SVB.evalBinOp(LinePtrNotNull, BO_GE, LineBufSize,
*NDefSVal, SVB.getConditionType())
.getAs<DefinedOrUnknownSVal>();
Expand All @@ -367,8 +378,11 @@ ProgramStateRef UnixAPIMisuseChecker::EnsureGetdelimBufferAndSizeCorrect(
return State;
}

void UnixAPIMisuseChecker::CheckGetDelim(CheckerContext &C,
const CallEvent &Call) const {
void UnixAPIMisuseChecker::CheckGetDelimOrGetline(CheckerContext &C,
const CallEvent &Call) const {
if (Call.getNumArgs() < 2)
return;

ProgramStateRef State = C.getState();

// The parameter `n` must not be NULL.
Expand Down
127 changes: 127 additions & 0 deletions clang/test/Analysis/getline-unixapi-invalid-signatures.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
// RUN: %clang_analyze_cc1 -analyzer-checker=core,unix -verify %s -DTEST_CORRECT
// RUN: %clang_analyze_cc1 -analyzer-checker=core,unix -verify %s -DTEST_GETLINE_1
// RUN: %clang_analyze_cc1 -analyzer-checker=core,unix -verify %s -DTEST_GETLINE_2
// RUN: %clang_analyze_cc1 -analyzer-checker=core,unix -verify %s -DTEST_GETLINE_3
// RUN: %clang_analyze_cc1 -analyzer-checker=core,unix -verify %s -DTEST_GETLINE_4
// RUN: %clang_analyze_cc1 -analyzer-checker=core,unix -verify %s -DTEST_GETLINE_5
// RUN: %clang_analyze_cc1 -analyzer-checker=core,unix -verify %s -DTEST_GETLINE_GH144884

// emulator of "system-header-simulator.h" because of redefinition of 'getline' function
typedef struct _FILE FILE;
typedef __typeof(sizeof(int)) size_t;
typedef long ssize_t;
#define NULL 0

int fclose(FILE *fp);
FILE *tmpfile(void);

#ifdef TEST_CORRECT
ssize_t getline(char **lineptr, size_t *n, FILE *stream);
ssize_t getdelim(char **lineptr, size_t *n, int delimiter, FILE *stream);

void test_correct() {
FILE *F1 = tmpfile();
if (!F1)
return;
char *buffer = NULL;
getline(&buffer, NULL, F1); // expected-warning {{Size pointer might be NULL}}
fclose(F1);
}

void test_delim_correct() {
FILE *F1 = tmpfile();
if (!F1)
return;
char *buffer = NULL;
getdelim(&buffer, NULL, ',', F1); // expected-warning {{Size pointer might be NULL}}
fclose(F1);
}
#endif

#ifdef TEST_GETLINE_1
// expected-no-diagnostics
ssize_t getline(int lineptr);

void test() {
FILE *F1 = tmpfile();
if (!F1)
return;
int buffer = 0;
getline(buffer);
fclose(F1);
}
#endif

#ifdef TEST_GETLINE_2
ssize_t getline(char **lineptr, size_t *n);

void test() {
FILE *F1 = tmpfile();
if (!F1)
return;
char *buffer = NULL;
getline(&buffer, NULL); // expected-warning {{Size pointer might be NULL}}
fclose(F1);
}
#endif

#ifdef TEST_GETLINE_3
// expected-no-diagnostics
ssize_t getline(char **lineptr, size_t n, FILE *stream);

void test() {
FILE *F1 = tmpfile();
if (!F1)
return;
char *buffer = NULL;
getline(&buffer, 0, F1);
fclose(F1);
}
#endif

#ifdef TEST_GETLINE_4
ssize_t getline(char **lineptr, size_t *n, int stream);
ssize_t getdelim(char **lineptr, size_t *n, int delimiter, int stream);

void test() {
FILE *F1 = tmpfile();
if (!F1)
return;
char *buffer = NULL;
getline(&buffer, NULL, 1); // expected-warning {{Size pointer might be NULL}}
fclose(F1);
}

void test_delim() {
FILE *F1 = tmpfile();
if (!F1)
return;
char *buffer = NULL;
getdelim(&buffer, NULL, ',', 1); // expected-warning {{Size pointer might be NULL}}
fclose(F1);
}
#endif

#ifdef TEST_GETLINE_5
ssize_t getdelim(char **lineptr, size_t *n, const char* delimiter, FILE *stream);

void test_delim() {
FILE *F1 = tmpfile();
if (!F1)
return;
char *buffer = NULL;
getdelim(&buffer, NULL, ",", F1); // expected-warning {{Size pointer might be NULL}}
fclose(F1);
}
#endif

#ifdef TEST_GETLINE_GH144884
// expected-no-diagnostics
struct AW_string {};
void getline(int *, struct AW_string);
void top() {
struct AW_string line;
int getline_file_info;
getline(&getline_file_info, line);
}
#endif
Loading