https://github.com/balazske updated https://github.com/llvm/llvm-project/pull/147766
From f8dc303029c68762cdbd19b217730192d26f6fca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20K=C3=A9ri?= <balazs.k...@ericsson.com> Date: Wed, 9 Jul 2025 16:55:07 +0200 Subject: [PATCH 1/3] [clang][analyzer] Add C standard streams to the internal memory space If variables `stdin`, `stdout`, `stderr` are added to the system memory space, they are invalidated at any call to system (or C library) functions. These variables are not expected to be changed by system functions, therefore should not belong to the system memory space. This change moves these to the "internal memory space" which is not invalidated at system calls (but is at non-system calls). This eliminates some false positives coming from StreamChecker. --- clang/lib/StaticAnalyzer/Core/MemRegion.cpp | 20 +++++++-- clang/test/Analysis/stream.c | 45 +++++++++++++++++++-- 2 files changed, 59 insertions(+), 6 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Core/MemRegion.cpp b/clang/lib/StaticAnalyzer/Core/MemRegion.cpp index 63ad567ab7151..4e946f39c1cef 100644 --- a/clang/lib/StaticAnalyzer/Core/MemRegion.cpp +++ b/clang/lib/StaticAnalyzer/Core/MemRegion.cpp @@ -1054,10 +1054,24 @@ const VarRegion *MemRegionManager::getVarRegion(const VarDecl *D, assert(!Ty.isNull()); if (Ty.isConstQualified()) { sReg = getGlobalsRegion(MemRegion::GlobalImmutableSpaceRegionKind); - } else if (Ctx.getSourceManager().isInSystemHeader(D->getLocation())) { - sReg = getGlobalsRegion(MemRegion::GlobalSystemSpaceRegionKind); } else { - sReg = getGlobalsRegion(MemRegion::GlobalInternalSpaceRegionKind); + StringRef N = D->getNameAsString(); + QualType FILETy = D->getASTContext().getFILEType(); + if (!FILETy.isNull()) + FILETy = FILETy.getCanonicalType(); + Ty = Ty.getCanonicalType(); + bool IsStdStreamVar = Ty->isPointerType() && + Ty->getPointeeType() == FILETy && + (N == "stdin" || N == "stdout" || N == "stderr"); + // Pointer value of C standard streams is usually not modified by system + // calls. This means they should not get invalidated at system calls and + // can not belong to the system memory space. + if (Ctx.getSourceManager().isInSystemHeader(D->getLocation()) && + !IsStdStreamVar) { + sReg = getGlobalsRegion(MemRegion::GlobalSystemSpaceRegionKind); + } else { + sReg = getGlobalsRegion(MemRegion::GlobalInternalSpaceRegionKind); + } } // Finally handle static locals. diff --git a/clang/test/Analysis/stream.c b/clang/test/Analysis/stream.c index 758b40cca4931..781c023ea248a 100644 --- a/clang/test/Analysis/stream.c +++ b/clang/test/Analysis/stream.c @@ -519,14 +519,53 @@ void reopen_std_stream(void) { if (!fp) return; stdout = fp; // Let's make them alias. - clang_analyzer_eval(fp == oldStdout); // expected-warning {{UNKNOWN}} - clang_analyzer_eval(fp == stdout); // expected-warning {{TRUE}} no-FALSE - clang_analyzer_eval(oldStdout == stdout); // expected-warning {{UNKNOWN}} + clang_analyzer_eval(fp == oldStdout); // expected-warning {{FALSE}} + clang_analyzer_eval(fp == stdout); // expected-warning {{TRUE}} + clang_analyzer_eval(oldStdout == stdout); // expected-warning {{FALSE}} } void only_success_path_does_not_alias_with_stdout(void) { if (stdout) return; FILE *f = fopen("/tmp/foof", "r"); // no-crash + clang_analyzer_eval(f == 0);// expected-warning {{TRUE}} expected-warning {{FALSE}} if (!f) return; fclose(f); } + +extern void do_something(); + +void test_no_invalidate_at_system_call(int use_std) { + FILE *fd; + char *buf; + + if (use_std) { + fd = stdin; + } else { + if ((fd = fopen("x/y/z", "r")) == NULL) + return; + + clang_analyzer_eval(fd == stdin); // expected-warning{{FALSE}} + buf = (char *)malloc(100); + clang_analyzer_eval(fd == stdin); // expected-warning{{FALSE}} + } + + if (fd != stdin) + fclose(fd); +} + +void test_invalidate_at_non_system_call(int use_std) { + FILE *fd; + + if (use_std) { + fd = stdin; + } else { + if ((fd = fopen("x/y/z", "r")) == NULL) + return; + } + + do_something(); + clang_analyzer_eval(fd == stdin); // expected-warning{{UNKNOWN}} + + if (fd != stdin) + fclose(fd); +} // expected-warning{{Opened stream never closed. Potential resource leak}} From 3117a648fe537b11a51fcb1e6b4ba19f49f1c4bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20K=C3=A9ri?= <balazs.k...@ericsson.com> Date: Thu, 10 Jul 2025 10:04:29 +0200 Subject: [PATCH 2/3] updated code comment --- clang/lib/StaticAnalyzer/Core/MemRegion.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Core/MemRegion.cpp b/clang/lib/StaticAnalyzer/Core/MemRegion.cpp index 4e946f39c1cef..53f421640c555 100644 --- a/clang/lib/StaticAnalyzer/Core/MemRegion.cpp +++ b/clang/lib/StaticAnalyzer/Core/MemRegion.cpp @@ -1063,9 +1063,11 @@ const VarRegion *MemRegionManager::getVarRegion(const VarDecl *D, bool IsStdStreamVar = Ty->isPointerType() && Ty->getPointeeType() == FILETy && (N == "stdin" || N == "stdout" || N == "stderr"); - // Pointer value of C standard streams is usually not modified by system - // calls. This means they should not get invalidated at system calls and - // can not belong to the system memory space. + // Pointer value of C standard streams is usually not modified by calls + // to functions declared in system headers. This means that they should + // not get invalidated by calls to functions declared in system headers, + // so they are placed in the global internal space, which is not + // invalidated by calls to functions declared in system headers. if (Ctx.getSourceManager().isInSystemHeader(D->getLocation()) && !IsStdStreamVar) { sReg = getGlobalsRegion(MemRegion::GlobalSystemSpaceRegionKind); From 0845fa2168eeea11e00d5ebee1eda733f8a843de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20K=C3=A9ri?= <balazs.k...@ericsson.com> Date: Fri, 11 Jul 2025 16:52:39 +0200 Subject: [PATCH 3/3] moved test for std stream into function, simplified test --- clang/lib/StaticAnalyzer/Core/MemRegion.cpp | 27 ++++++++---- clang/test/Analysis/stream.c | 47 +++++++-------------- 2 files changed, 33 insertions(+), 41 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Core/MemRegion.cpp b/clang/lib/StaticAnalyzer/Core/MemRegion.cpp index 53f421640c555..9c7b11b7947b1 100644 --- a/clang/lib/StaticAnalyzer/Core/MemRegion.cpp +++ b/clang/lib/StaticAnalyzer/Core/MemRegion.cpp @@ -1022,6 +1022,23 @@ getStackOrCaptureRegionForDeclContext(const LocationContext *LC, return (const StackFrameContext *)nullptr; } +static bool isStdStreamVar(const VarDecl *D) { + const auto *ND = dyn_cast<NamedDecl>(D); + if (!ND) + return false; + DeclarationName DeclN = ND->getDeclName(); + if (!DeclN.isIdentifier()) + return false; + StringRef N = DeclN.getAsIdentifierInfo()->getName(); + QualType FILETy = D->getASTContext().getFILEType(); + if (FILETy.isNull()) + return false; + FILETy = FILETy.getCanonicalType(); + QualType Ty = D->getType().getCanonicalType(); + return Ty->isPointerType() && Ty->getPointeeType() == FILETy && + (N == "stdin" || N == "stdout" || N == "stderr"); +} + const VarRegion *MemRegionManager::getVarRegion(const VarDecl *D, const LocationContext *LC) { const auto *PVD = dyn_cast<ParmVarDecl>(D); @@ -1055,21 +1072,13 @@ const VarRegion *MemRegionManager::getVarRegion(const VarDecl *D, if (Ty.isConstQualified()) { sReg = getGlobalsRegion(MemRegion::GlobalImmutableSpaceRegionKind); } else { - StringRef N = D->getNameAsString(); - QualType FILETy = D->getASTContext().getFILEType(); - if (!FILETy.isNull()) - FILETy = FILETy.getCanonicalType(); - Ty = Ty.getCanonicalType(); - bool IsStdStreamVar = Ty->isPointerType() && - Ty->getPointeeType() == FILETy && - (N == "stdin" || N == "stdout" || N == "stderr"); // Pointer value of C standard streams is usually not modified by calls // to functions declared in system headers. This means that they should // not get invalidated by calls to functions declared in system headers, // so they are placed in the global internal space, which is not // invalidated by calls to functions declared in system headers. if (Ctx.getSourceManager().isInSystemHeader(D->getLocation()) && - !IsStdStreamVar) { + !isStdStreamVar(D)) { sReg = getGlobalsRegion(MemRegion::GlobalSystemSpaceRegionKind); } else { sReg = getGlobalsRegion(MemRegion::GlobalInternalSpaceRegionKind); diff --git a/clang/test/Analysis/stream.c b/clang/test/Analysis/stream.c index 781c023ea248a..10e6e5381ee6d 100644 --- a/clang/test/Analysis/stream.c +++ b/clang/test/Analysis/stream.c @@ -534,38 +534,21 @@ void only_success_path_does_not_alias_with_stdout(void) { extern void do_something(); -void test_no_invalidate_at_system_call(int use_std) { - FILE *fd; - char *buf; - - if (use_std) { - fd = stdin; - } else { - if ((fd = fopen("x/y/z", "r")) == NULL) - return; - - clang_analyzer_eval(fd == stdin); // expected-warning{{FALSE}} - buf = (char *)malloc(100); - clang_analyzer_eval(fd == stdin); // expected-warning{{FALSE}} - } - - if (fd != stdin) - fclose(fd); +void test_no_invalidate_at_system_call() { + FILE *old_stdin = stdin; + if ((stdin = fopen("x/y/z", "r")) == NULL) + return; + clang_analyzer_eval(old_stdin == stdin); // expected-warning{{FALSE}} + free(malloc(100)); + clang_analyzer_eval(old_stdin == stdin); // expected-warning{{FALSE}} + fclose(stdin); } -void test_invalidate_at_non_system_call(int use_std) { - FILE *fd; - - if (use_std) { - fd = stdin; - } else { - if ((fd = fopen("x/y/z", "r")) == NULL) - return; - } - +void test_invalidate_at_non_system_call() { + FILE *old_stdin = stdin; + if ((stdin = fopen("x/y/z", "r")) == NULL) + return; + clang_analyzer_eval(old_stdin == stdin); // expected-warning{{FALSE}} do_something(); - clang_analyzer_eval(fd == stdin); // expected-warning{{UNKNOWN}} - - if (fd != stdin) - fclose(fd); -} // expected-warning{{Opened stream never closed. Potential resource leak}} + clang_analyzer_eval(old_stdin == stdin); // expected-warning{{UNKNOWN}} +} _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits