diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/MemRegion.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/MemRegion.h index 59805d01be5db..c0d3fbd0eb961 100644 --- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/MemRegion.h +++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/MemRegion.h @@ -1503,7 +1503,7 @@ class MemRegionManager { /// associated element type, index, and super region. const ElementRegion *getElementRegion(QualType elementType, NonLoc Idx, const SubRegion *superRegion, - ASTContext &Ctx); + const ASTContext &Ctx); const ElementRegion *getElementRegionWithSuper(const ElementRegion *ER, const SubRegion *superRegion) { diff --git a/clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp index d4e020f7a72a0..74ee607849a5b 100644 --- a/clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp @@ -717,18 +717,56 @@ const ExplodedNode *StreamChecker::getAcquisitionSite(const ExplodedNode *N, return nullptr; } +static std::optional getKnownValue(ProgramStateRef State, SVal V) { + SValBuilder &SVB = State->getStateManager().getSValBuilder(); + if (const llvm::APSInt *Int = SVB.getKnownValue(State, V)) + return Int->tryExtValue(); + return std::nullopt; +} + +/// Invalidate only the requested elements instead of the whole buffer. +/// This is basically a refinement of the more generic 'escapeArgs' or +/// the plain old 'invalidateRegions'. +static ProgramStateRef +escapeByStartIndexAndCount(ProgramStateRef State, const CallEvent &Call, + unsigned BlockCount, const SubRegion *Buffer, + QualType ElemType, int64_t StartIndex, + int64_t ElementCount) { + constexpr auto DoNotInvalidateSuperRegion = + RegionAndSymbolInvalidationTraits::InvalidationKinds:: + TK_DoNotInvalidateSuperRegion; + + const LocationContext *LCtx = Call.getLocationContext(); + const ASTContext &Ctx = State->getStateManager().getContext(); + SValBuilder &SVB = State->getStateManager().getSValBuilder(); + auto &RegionManager = Buffer->getMemRegionManager(); + + SmallVector EscapingVals; + EscapingVals.reserve(ElementCount); + + RegionAndSymbolInvalidationTraits ITraits; + for (auto Idx : llvm::seq(StartIndex, StartIndex + ElementCount)) { + NonLoc Index = SVB.makeArrayIndex(Idx); + const auto *Element = + RegionManager.getElementRegion(ElemType, Index, Buffer, Ctx); + EscapingVals.push_back(loc::MemRegionVal(Element)); + ITraits.setTrait(Element, DoNotInvalidateSuperRegion); + } + return State->invalidateRegions( + EscapingVals, Call.getOriginExpr(), BlockCount, LCtx, + /*CausesPointerEscape=*/false, + /*InvalidatedSymbols=*/nullptr, &Call, &ITraits); +} + static ProgramStateRef escapeArgs(ProgramStateRef State, CheckerContext &C, const CallEvent &Call, ArrayRef EscapingArgs) { - const auto *CE = Call.getOriginExpr(); - - SmallVector EscapingVals; - EscapingVals.reserve(EscapingArgs.size()); - for (auto EscArgIdx : EscapingArgs) - EscapingVals.push_back(Call.getArgSVal(EscArgIdx)); - State = State->invalidateRegions(EscapingVals, CE, C.blockCount(), - C.getLocationContext(), - /*CausesPointerEscape=*/false); + auto GetArgSVal = [&Call](int Idx) { return Call.getArgSVal(Idx); }; + auto EscapingVals = to_vector(map_range(EscapingArgs, GetArgSVal)); + State = State->invalidateRegions(EscapingVals, Call.getOriginExpr(), + C.blockCount(), C.getLocationContext(), + /*CausesPointerEscape=*/false, + /*InvalidatedSymbols=*/nullptr); return State; } @@ -907,6 +945,76 @@ void StreamChecker::preWrite(const FnDescription *Desc, const CallEvent &Call, C.addTransition(State); } +static std::optional getPointeeType(const MemRegion *R) { + if (!R) + return std::nullopt; + if (const auto *ER = dyn_cast(R)) + return ER->getElementType(); + if (const auto *TR = dyn_cast(R)) + return TR->getValueType(); + if (const auto *SR = dyn_cast(R)) + return SR->getPointeeStaticType(); + return std::nullopt; +} + +static std::optional getStartIndex(SValBuilder &SVB, + const MemRegion *R) { + if (!R) + return std::nullopt; + + auto Zero = [&SVB] { + BasicValueFactory &BVF = SVB.getBasicValueFactory(); + return nonloc::ConcreteInt(BVF.getIntValue(0, /*isUnsigned=*/false)); + }; + + if (const auto *ER = dyn_cast(R)) + return ER->getIndex(); + if (const auto *TR = dyn_cast(R)) + return Zero(); + if (const auto *SR = dyn_cast(R)) + return Zero(); + return std::nullopt; +} + +static ProgramStateRef +tryToInvalidateFReadBufferByElements(ProgramStateRef State, CheckerContext &C, + const CallEvent &Call, NonLoc SizeVal, + NonLoc NMembVal) { + // Try to invalidate the individual elements. + const auto *Buffer = + dyn_cast_or_null(Call.getArgSVal(0).getAsRegion()); + + std::optional ElemTy = getPointeeType(Buffer); + std::optional StartElementIndex = + getStartIndex(C.getSValBuilder(), Buffer); + + // Drop the outermost ElementRegion to get the buffer. + if (const auto *ER = dyn_cast_or_null(Buffer)) + Buffer = dyn_cast(ER->getSuperRegion()); + + std::optional CountVal = getKnownValue(State, NMembVal); + std::optional Size = getKnownValue(State, SizeVal); + std::optional StartIndexVal = + getKnownValue(State, StartElementIndex.value_or(UnknownVal())); + + if (ElemTy && CountVal && Size && StartIndexVal) { + int64_t NumBytesRead = Size.value() * CountVal.value(); + int64_t ElemSizeInChars = + C.getASTContext().getTypeSizeInChars(*ElemTy).getQuantity(); + bool IncompleteLastElement = (NumBytesRead % ElemSizeInChars) != 0; + int64_t NumCompleteOrIncompleteElementsRead = + NumBytesRead / ElemSizeInChars + IncompleteLastElement; + + constexpr int MaxInvalidatedElementsLimit = 64; + if (NumCompleteOrIncompleteElementsRead <= MaxInvalidatedElementsLimit) { + return escapeByStartIndexAndCount(State, Call, C.blockCount(), Buffer, + *ElemTy, *StartIndexVal, + NumCompleteOrIncompleteElementsRead); + } + } + return nullptr; +} + void StreamChecker::evalFreadFwrite(const FnDescription *Desc, const CallEvent &Call, CheckerContext &C, bool IsFread) const { @@ -937,8 +1045,14 @@ void StreamChecker::evalFreadFwrite(const FnDescription *Desc, // At read, invalidate the buffer in any case of error or success, // except if EOF was already present. - if (IsFread && !E.isStreamEof()) - State = escapeArgs(State, C, Call, {0}); + if (IsFread && !E.isStreamEof()) { + // Try to invalidate the individual elements. + // Otherwise just fall back to invalidating the whole buffer. + ProgramStateRef InvalidatedState = tryToInvalidateFReadBufferByElements( + State, C, Call, *SizeVal, *NMembVal); + State = + InvalidatedState ? InvalidatedState : escapeArgs(State, C, Call, {0}); + } // Generate a transition for the success state. // If we know the state to be FEOF at fread, do not add a success state. diff --git a/clang/lib/StaticAnalyzer/Core/MemRegion.cpp b/clang/lib/StaticAnalyzer/Core/MemRegion.cpp index d6e4f23cc353f..6fe929b1cb94a 100644 --- a/clang/lib/StaticAnalyzer/Core/MemRegion.cpp +++ b/clang/lib/StaticAnalyzer/Core/MemRegion.cpp @@ -1155,10 +1155,10 @@ MemRegionManager::getCompoundLiteralRegion(const CompoundLiteralExpr *CL, return getSubRegion(CL, sReg); } -const ElementRegion* +const ElementRegion * MemRegionManager::getElementRegion(QualType elementType, NonLoc Idx, - const SubRegion* superRegion, - ASTContext &Ctx){ + const SubRegion *superRegion, + const ASTContext &Ctx) { QualType T = Ctx.getCanonicalType(elementType).getUnqualifiedType(); llvm::FoldingSetNodeID ID; diff --git a/clang/test/Analysis/Inputs/system-header-simulator-for-simple-stream.h b/clang/test/Analysis/Inputs/system-header-simulator-for-simple-stream.h index c26d358214912..47adf8e23a117 100644 --- a/clang/test/Analysis/Inputs/system-header-simulator-for-simple-stream.h +++ b/clang/test/Analysis/Inputs/system-header-simulator-for-simple-stream.h @@ -5,12 +5,15 @@ // suppressed. #pragma clang system_header +typedef __typeof(sizeof(int)) size_t; typedef struct _FILE { unsigned char *_p; } FILE; FILE *fopen(const char *restrict, const char *restrict) __asm("_" "fopen" ); int fputc(int, FILE *); int fputs(const char *restrict, FILE *restrict) __asm("_" "fputs" ); +size_t fread(void *buffer, size_t size, size_t count, FILE *stream); +int fgetc(FILE *stream); int fclose(FILE *); void exit(int); diff --git a/clang/test/Analysis/fread.c b/clang/test/Analysis/fread.c new file mode 100644 index 0000000000000..3f286421fd7a1 --- /dev/null +++ b/clang/test/Analysis/fread.c @@ -0,0 +1,445 @@ +// RUN: %clang_analyze_cc1 -verify %s \ +// RUN: -triple x86_64-linux-gnu \ +// RUN: -analyzer-checker=core,unix.Stream,alpha.security.taint \ +// RUN: -analyzer-checker=debug.ExprInspection + +#include "Inputs/system-header-simulator-for-simple-stream.h" + +#define EOF (-1) + +void clang_analyzer_dump(int); +void clang_analyzer_dump_char(char); +void clang_analyzer_isTainted(int); +void clang_analyzer_warnIfReached(void); + +// A stream is only tracked by StreamChecker if it results from a call to "fopen". +// Otherwise, there is no specific modelling of "fread". +void untracked_stream(FILE *fp) { + char c; + if (1 == fread(&c, 1, 1, fp)) { + char p = c; // Unknown value but not garbage and not modeled by checker. + } else { + char p = c; // Possibly indeterminate value but not modeled by checker. + } +} + +void fgetc_props_taint(void) { + FILE *fp = fopen("/home/test", "rb+"); + if (fp) { + int c = fgetc(fp); // c is tainted. + if (c != EOF) { + clang_analyzer_isTainted(c); // expected-warning{{YES}} + } + fclose(fp); + } +} + +void fread_props_taint(void) { + FILE *fp = fopen("/home/test", "rb+"); + if (fp) { + char buffer[10]; + int c = fread(buffer, 1, 10, fp); // c is tainted. + if (c != 10) { + // If the read failed, then the number of bytes successfully read should be tainted. + clang_analyzer_isTainted(c); // expected-warning{{YES}} + } + fclose(fp); + } +} + +void read_one_byte1(void) { + FILE *fp = fopen("/home/test", "rb+"); + if (fp) { + char c; + if (1 == fread(&c, 1, 1, fp)) { + char p = c; // Unknown value but not garbage. + clang_analyzer_isTainted(p); // expected-warning{{YES}} + } else { + char p = c; // Possibly indeterminate value but not modeled by checker. + clang_analyzer_isTainted(p); // expected-warning{{YES}} + } + fclose(fp); + } +} + +void read_one_byte2(char *buffer) { + FILE *fp = fopen("/home/test", "rb+"); + if (fp) { + if (1 == fread(buffer, 1, 1, fp)) { + char p = buffer[0]; // Unknown value but not garbage. + clang_analyzer_isTainted(p); // expected-warning{{YES}} + } else { + char p = buffer[0]; // Possibly indeterminate value but not modeled by checker. + clang_analyzer_isTainted(p); // expected-warning{{YES}} + } + fclose(fp); + } +} + +void read_one_byte3(char *buffer) { + buffer[1] = 10; + FILE *fp = fopen("/home/test", "rb+"); + if (fp) { + // buffer[1] is not mutated by fread and remains not tainted. + fread(buffer, 1, 1, fp); + char p = buffer[1]; + clang_analyzer_isTainted(p); // expected-warning{{NO}} + clang_analyzer_dump(buffer[1]); // expected-warning{{10 S32b}} + fclose(fp); + } +} + +void read_many_bytes(char *buffer) { + FILE *fp = fopen("/home/test", "rb+"); + if (fp) { + if (42 == fread(buffer, 1, 42, fp)) { + char p = buffer[0]; // Unknown value but not garbage. + clang_analyzer_isTainted(p); // expected-warning{{YES}} + } else { + char p = buffer[0]; // Possibly indeterminate value but not modeled. + clang_analyzer_isTainted(p); // expected-warning{{YES}} + } + fclose(fp); + } +} + +void random_access_read1(int index) { + FILE *fp = fopen("/home/test", "rb+"); + if (fp) { + long c[4]; + int success = 2 == fread(c + 1, sizeof(long), 2, fp); + + switch (index) { + case 0: + // c[0] is not mutated by fread. + if (success) { + char p = c[0]; // expected-warning {{Assigned value is garbage or undefined}} We kept the first byte intact. + } else { + char p = c[0]; // expected-warning {{Assigned value is garbage or undefined}} We kept the first byte intact. + } + break; + + case 1: + if (success) { + // Unknown value but not garbage. + clang_analyzer_isTainted(c[1]); // expected-warning {{YES}} + clang_analyzer_dump(c[1]); // expected-warning {{conj_}} + } else { + // Possibly indeterminate value but not modeled. + clang_analyzer_isTainted(c[1]); // expected-warning {{YES}} + clang_analyzer_dump(c[1]); // expected-warning {{conj_}} + } + break; + + case 2: + if (success) { + long p = c[2]; // Unknown value but not garbage. + // FIXME: Taint analysis only marks the first byte of a memory region. See getPointeeOf in GenericTaintChecker.cpp. + clang_analyzer_isTainted(c[2]); // expected-warning {{NO}} + clang_analyzer_dump(c[2]); // expected-warning {{conj_}} + } else { + // Possibly indeterminate value but not modeled. + clang_analyzer_isTainted(c[2]); // expected-warning {{NO}} // FIXME: See above. + clang_analyzer_dump(c[2]); // expected-warning {{conj_}} + } + break; + + case 3: + // c[3] is not mutated by fread. + if (success) { + long p = c[3]; // expected-warning {{Assigned value is garbage or undefined}} + } else { + long p = c[3]; // expected-warning {{Assigned value is garbage or undefined}} + } + break; + } + + fclose(fp); + } +} + +void random_access_read2(int b) { + FILE *fp = fopen("/home/test", "rb+"); + if (fp) { + int buffer[10]; + int *ptr = buffer + 2; + if (5 == fread(ptr - 1, sizeof(int), 5, fp)) { + if (b) { + int p = buffer[1]; // Unknown value but not garbage. + clang_analyzer_isTainted(p); // expected-warning {{YES}} + clang_analyzer_dump(p); // expected-warning {{conj_}} + } else { + int p = buffer[0]; // expected-warning {{Assigned value is garbage or undefined}} + } + } else { + int p = buffer[0]; // expected-warning {{Assigned value is garbage or undefined}} + } + fclose(fp); + } +} + +void random_access_read_symbolic_count(size_t count) { + // Cover a case that used to crash (symbolic count). + if (count > 2) + return; + + FILE *fp = fopen("/home/test", "rb+"); + if (fp) { + long c[4]; + fread(c + 1, sizeof(long), count, fp); + + // c[0] and c[3] are never mutated by fread, but because "count" is a symbolic value, the checker doesn't know that. + long p = c[0]; + clang_analyzer_isTainted(p); // expected-warning {{NO}} + clang_analyzer_dump(p); // expected-warning {{derived_}} + + p = c[3]; + clang_analyzer_isTainted(p); // expected-warning {{NO}} + clang_analyzer_dump(p); // expected-warning {{derived_}} + + p = c[1]; + clang_analyzer_isTainted(p); // expected-warning {{YES}} + clang_analyzer_dump(p); // expected-warning {{derived_}} + + fclose(fp); + } +} + +void dynamic_random_access_read(int startIndex) { + FILE *fp = fopen("/home/test", "rb+"); + if (fp) { + long buffer[10]; + // Cannot reason about index. + size_t res = fread(buffer + startIndex, sizeof(long), 5, fp); + long *p = &buffer[startIndex]; + long v = 0; + + // If all 5 elements were successfully read, then all 5 elements should be tainted and considered initialized. + if (5 == res) { + // FIXME: These should be tainted. + clang_analyzer_isTainted((v = p[0])); // expected-warning {{NO}} + clang_analyzer_isTainted((v = p[1])); // expected-warning {{NO}} + clang_analyzer_isTainted((v = p[2])); // expected-warning {{NO}} + clang_analyzer_isTainted((v = p[3])); // expected-warning {{NO}} + clang_analyzer_isTainted((v = p[4])); // expected-warning {{NO}} + clang_analyzer_dump((v = p[0])); // expected-warning {{conj_}} ok + clang_analyzer_dump((v = p[1])); // expected-warning {{conj_}} ok + clang_analyzer_dump((v = p[2])); // expected-warning {{conj_}} ok + clang_analyzer_dump((v = p[3])); // expected-warning {{conj_}} ok + clang_analyzer_dump((v = p[4])); // expected-warning {{conj_}} ok + clang_analyzer_dump((v = p[5])); // expected-warning {{conj_}} FIXME: This should raise an uninit read. + } else if (res == 4) { + // If only the first 4 elements were successfully read, + // then only the first 4 elements should be tainted and considered initialized. + // FIXME: These should be tainted. + clang_analyzer_isTainted((v = p[0])); // expected-warning {{NO}} + clang_analyzer_isTainted((v = p[1])); // expected-warning {{NO}} + clang_analyzer_isTainted((v = p[2])); // expected-warning {{NO}} + clang_analyzer_isTainted((v = p[3])); // expected-warning {{NO}} + clang_analyzer_dump((v = p[0])); // expected-warning {{conj_}} ok + clang_analyzer_dump((v = p[1])); // expected-warning {{conj_}} ok + clang_analyzer_dump((v = p[2])); // expected-warning {{conj_}} ok + clang_analyzer_dump((v = p[3])); // expected-warning {{conj_}} ok + clang_analyzer_dump((v = p[4])); // expected-warning {{conj_}} FIXME: This should raise an uninit read. + } else { + // Neither 5, or 4 elements were successfully read, so we must have read from 0 up to 3 elements. + // FIXME: These should be tainted. + clang_analyzer_isTainted((v = p[0])); // expected-warning {{NO}} + clang_analyzer_isTainted((v = p[1])); // expected-warning {{NO}} + clang_analyzer_isTainted((v = p[2])); // expected-warning {{NO}} + clang_analyzer_dump((v = p[0])); // expected-warning {{conj_}} ok + clang_analyzer_dump((v = p[1])); // expected-warning {{conj_}} ok + clang_analyzer_dump((v = p[2])); // expected-warning {{conj_}} ok + clang_analyzer_dump((v = p[3])); // expected-warning {{conj_}} FIXME: This should raise an uninit read. + } + fclose(fp); + } +} + +struct S { + int a; + long b; +}; + +void compound_read1(void) { + FILE *fp = fopen("/home/test", "rb+"); + if (fp) { + struct S s; // s.a is not touched by fread. + if (1 == fread(&s.b, sizeof(s.b), 1, fp)) { + long p = s.b; + clang_analyzer_isTainted(p); // expected-warning {{YES}} + clang_analyzer_dump(p); // expected-warning {{conj_}} + } else { + long p = s.b; + clang_analyzer_isTainted(p); // expected-warning {{YES}} + clang_analyzer_dump(p); // expected-warning {{conj_}} + } + fclose(fp); + } +} + +void compound_read2(void) { + FILE *fp = fopen("/home/test", "rb+"); + if (fp) { + struct S s; // s.a is not touched by fread. + if (1 == fread(&s.b, sizeof(s.b), 1, fp)) { + long p = s.a; // expected-warning {{Assigned value is garbage or undefined}} + } else { + long p = s.a; // expected-warning {{Assigned value is garbage or undefined}} + } + fclose(fp); + } +} + +void var_read(void) { + FILE *fp = fopen("/home/test", "rb+"); + if (fp) { + int a, b; // 'a' is not touched by fread. + if (1 == fread(&b, sizeof(b), 1, fp)) { + long p = a; // expected-warning{{Assigned value is garbage or undefined}} + } else { + long p = a; // expected-warning{{Assigned value is garbage or undefined}} + } + fclose(fp); + } +} + +// When reading a lot of data, invalidating all elements is too time-consuming. +// Instead, the knowledge of the whole array is lost. +#define MaxInvalidatedElementRegion 64 // See StreamChecker::evalFreadFwrite in StreamChecker.cpp. +#define PastMaxComplexity MaxInvalidatedElementRegion + 1 +void test_large_read(void) { + int buffer[PastMaxComplexity + 1]; + buffer[PastMaxComplexity] = 42; + FILE *fp = fopen("/home/test", "rb+"); + if (fp) { + if (buffer[PastMaxComplexity] != 42) { + clang_analyzer_warnIfReached(); // Unreachable. + } + if (1 == fread(buffer, sizeof(int), PastMaxComplexity, fp)) { + if (buffer[PastMaxComplexity] != 42) { + clang_analyzer_warnIfReached(); // expected-warning{{REACHABLE}} + } + } + fclose(fp); + } +} + +void test_small_read(void) { + int buffer[10]; + buffer[5] = 42; + FILE *fp = fopen("/home/test", "rb+"); + if (fp) { + clang_analyzer_dump(buffer[5]); // expected-warning{{42 S32b}} + if (1 == fread(buffer, sizeof(int), 5, fp)) { + clang_analyzer_dump(buffer[5]); // expected-warning{{42 S32b}} + } + fclose(fp); + } +} + +void test_partial_elements_read(void) { + clang_analyzer_dump(sizeof(int)); // expected-warning {{4 S32b}} + + int buffer[100]; + buffer[0] = 1; + buffer[1] = 2; + buffer[2] = 3; + buffer[3] = 4; + buffer[4] = 5; + buffer[5] = 6; + FILE *fp = fopen("/home/test", "rb+"); + if (fp) { + // 3*5: 15 bytes read; which is not exactly 4 integers, but we still invalidate the first 4 ints. + if (5 == fread(buffer + 1, 3, 5, fp)) { + clang_analyzer_dump(buffer[0]); // expected-warning{{1 S32b}} + clang_analyzer_dump(buffer[1]); // expected-warning{{conj_}} + clang_analyzer_dump(buffer[2]); // expected-warning{{conj_}} + clang_analyzer_dump(buffer[3]); // expected-warning{{conj_}} + clang_analyzer_dump(buffer[4]); // expected-warning{{conj_}} + clang_analyzer_dump(buffer[5]); // expected-warning{{6 S32b}} + + char *c = (char*)buffer; + clang_analyzer_dump(c[4+12]); // expected-warning{{conj_}} 16th byte of buffer, which is the beginning of the 4th 'int' in the buffer. + + // FIXME: The store should have returned a partial binding for the 17th byte of the buffer, which is the 2nd byte of the previous int. + // This byte should have been initialized by the 'fread' earlier. However, the Store lies to us and says it's uninitialized. + clang_analyzer_dump(c[4+13]); // expected-warning{{1st function call argument is an uninitialized value}} should be initialized. + clang_analyzer_dump(c[4+16]); // This should be the first byte that 'fread' leaves uninitialized. This should raise the uninit read diag. + } else { + clang_analyzer_dump(buffer[0]); // expected-warning{{1 S32b}} ok + clang_analyzer_dump(buffer[1]); // expected-warning{{conj_}} ok + clang_analyzer_dump(buffer[2]); // expected-warning{{conj_}} ok + clang_analyzer_dump(buffer[3]); // expected-warning{{conj_}} ok + clang_analyzer_dump(buffer[4]); // expected-warning{{conj_}} ok, but an uninit warning would be also fine. + clang_analyzer_dump(buffer[5]); // expected-warning{{6 S32b}} ok + clang_analyzer_dump(buffer[6]); // expected-warning{{1st function call argument is an uninitialized value}} ok + } + fclose(fp); + } +} + +void test_whole_elements_read(void) { + clang_analyzer_dump(sizeof(int)); // expected-warning {{4 S32b}} + + int buffer[100]; + buffer[0] = 1; + buffer[15] = 2; + buffer[16] = 3; + FILE *fp = fopen("/home/test", "rb+"); + if (fp) { + // 3*20: 60 bytes read; which is basically 15 integers. + if (20 == fread(buffer + 1, 3, 20, fp)) { + clang_analyzer_dump(buffer[0]); // expected-warning{{1 S32b}} + clang_analyzer_dump(buffer[15]); // expected-warning{{conj_}} + clang_analyzer_dump(buffer[16]); // expected-warning{{3 S32b}} + clang_analyzer_dump(buffer[17]); // expected-warning{{1st function call argument is an uninitialized value}} + } else { + clang_analyzer_dump(buffer[0]); // expected-warning{{1 S32b}} + clang_analyzer_dump(buffer[15]); // expected-warning{{conj_}} + clang_analyzer_dump(buffer[16]); // expected-warning{{3 S32b}} + clang_analyzer_dump(buffer[17]); // expected-warning{{1st function call argument is an uninitialized value}} + } + fclose(fp); + } +} + +void test_unaligned_start_read(void) { + clang_analyzer_dump(sizeof(int)); // expected-warning {{4 S32b}} + + int buffer[100]; + buffer[0] = 3; + buffer[1] = 4; + buffer[2] = 5; + char *asChar = (char*)buffer; + + FILE *fp = fopen("/home/test", "rb+"); + if (fp) { + // We have an 'int' binding at offset 0 of value 3. + // We read 4 bytes at byte offset: 1,2,3,4. + if (4 == fread(asChar + 1, 1, 4, fp)) { + clang_analyzer_dump(buffer[0]); // expected-warning{{3 S32b}} FIXME: The int binding should have been partially overwritten by the read call. This definitely should not be 3. + clang_analyzer_dump(buffer[1]); // expected-warning{{conj_}} + clang_analyzer_dump(buffer[2]); // expected-warning{{5 S32b}} + + clang_analyzer_dump_char(asChar[0]); // expected-warning{{3 S8b}} This is technically true assuming x86 (little-endian) architecture. + clang_analyzer_dump_char(asChar[1]); // expected-warning{{conj_}} 1 + clang_analyzer_dump_char(asChar[2]); // expected-warning{{conj_}} 2 + clang_analyzer_dump_char(asChar[3]); // expected-warning{{conj_}} 3 + clang_analyzer_dump_char(asChar[4]); // expected-warning{{conj_}} 4 + clang_analyzer_dump_char(asChar[5]); // expected-warning{{1st function call argument is an uninitialized value}} + } else { + clang_analyzer_dump(buffer[0]); // expected-warning{{3 S32b}} FIXME: The int binding should have been partially overwritten by the read call. This definitely should not be 3. + clang_analyzer_dump(buffer[1]); // expected-warning{{conj_}} + clang_analyzer_dump(buffer[2]); // expected-warning{{5 S32b}} + + clang_analyzer_dump_char(asChar[0]); // expected-warning{{3 S8b}} This is technically true assuming x86 (little-endian) architecture. + clang_analyzer_dump_char(asChar[1]); // expected-warning{{conj_}} 1 + clang_analyzer_dump_char(asChar[2]); // expected-warning{{conj_}} 2 + clang_analyzer_dump_char(asChar[3]); // expected-warning{{conj_}} 3 + clang_analyzer_dump_char(asChar[4]); // expected-warning{{conj_}} 4 + clang_analyzer_dump_char(asChar[5]); // expected-warning{{1st function call argument is an uninitialized value}} + } + fclose(fp); + } +}