Skip to content

Commit c2067c1

Browse files
authored
[clang][analyzer] Add "pedantic" mode to StreamChecker. (#87322)
The checker may create failure branches for all stream write operations only if the new option "pedantic" is set to true. Result of the write operations is often not checked in typical code. If failure branches are created the checker will warn for unchecked write operations and generate a lot of "false positives" (these are valid warnings but the programmer does not care about this problem).
1 parent 8461d90 commit c2067c1

File tree

12 files changed

+166
-14
lines changed

12 files changed

+166
-14
lines changed

clang/docs/analyzer/checkers.rst

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3138,10 +3138,16 @@ are detected:
31383138
allowed in this state.
31393139
* Invalid 3rd ("``whence``") argument to ``fseek``.
31403140
3141-
The checker does not track the correspondence between integer file descriptors
3142-
and ``FILE *`` pointers. Operations on standard streams like ``stdin`` are not
3143-
treated specially and are therefore often not recognized (because these streams
3144-
are usually not opened explicitly by the program, and are global variables).
3141+
The stream operations are by this checker usually split into two cases, a success
3142+
and a failure case. However, in the case of write operations (like ``fwrite``,
3143+
``fprintf`` and even ``fsetpos``) this behavior could produce a large amount of
3144+
unwanted reports on projects that don't have error checks around the write
3145+
operations, so by default the checker assumes that write operations always succeed.
3146+
This behavior can be controlled by the ``Pedantic`` flag: With
3147+
``-analyzer-config alpha.unix.Stream:Pedantic=true`` the checker will model the
3148+
cases where a write operation fails and report situations where this leads to
3149+
erroneous behavior. (The default is ``Pedantic=false``, where write operations
3150+
are assumed to succeed.)
31453151
31463152
.. code-block:: c
31473153
@@ -3196,6 +3202,13 @@ are usually not opened explicitly by the program, and are global variables).
31963202
fclose(p);
31973203
}
31983204
3205+
**Limitations**
3206+
3207+
The checker does not track the correspondence between integer file descriptors
3208+
and ``FILE *`` pointers. Operations on standard streams like ``stdin`` are not
3209+
treated specially and are therefore often not recognized (because these streams
3210+
are usually not opened explicitly by the program, and are global variables).
3211+
31993212
.. _alpha-unix-cstring-BufferOverlap:
32003213
32013214
alpha.unix.cstring.BufferOverlap (C)

clang/include/clang/StaticAnalyzer/Checkers/Checkers.td

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -604,6 +604,15 @@ def PthreadLockChecker : Checker<"PthreadLock">,
604604
def StreamChecker : Checker<"Stream">,
605605
HelpText<"Check stream handling functions">,
606606
WeakDependencies<[NonNullParamChecker]>,
607+
CheckerOptions<[
608+
CmdLineOption<Boolean,
609+
"Pedantic",
610+
"If false, assume that stream operations which are often not "
611+
"checked for error do not fail."
612+
"fail.",
613+
"false",
614+
InAlpha>
615+
]>,
607616
Documentation<HasDocumentation>;
608617

609618
def SimpleStreamChecker : Checker<"SimpleStream">,

clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,9 @@ class StreamChecker : public Checker<check::PreCall, eval::Call,
297297
/// If true, evaluate special testing stream functions.
298298
bool TestMode = false;
299299

300+
/// If true, generate failure branches for cases that are often not checked.
301+
bool PedanticMode = false;
302+
300303
private:
301304
CallDescriptionMap<FnDescription> FnDescriptions = {
302305
{{{"fopen"}, 2}, {nullptr, &StreamChecker::evalFopen, ArgNone}},
@@ -945,6 +948,10 @@ void StreamChecker::evalFreadFwrite(const FnDescription *Desc,
945948
}
946949

947950
// Add transition for the failed state.
951+
// At write, add failure case only if "pedantic mode" is on.
952+
if (!IsFread && !PedanticMode)
953+
return;
954+
948955
NonLoc RetVal = makeRetVal(C, E.CE).castAs<NonLoc>();
949956
ProgramStateRef StateFailed =
950957
State->BindExpr(E.CE, C.getLocationContext(), RetVal);
@@ -1057,6 +1064,9 @@ void StreamChecker::evalFputx(const FnDescription *Desc, const CallEvent &Call,
10571064
C.addTransition(StateNotFailed);
10581065
}
10591066

1067+
if (!PedanticMode)
1068+
return;
1069+
10601070
// Add transition for the failed state. The resulting value of the file
10611071
// position indicator for the stream is indeterminate.
10621072
ProgramStateRef StateFailed = E.bindReturnValue(State, C, *EofVal);
@@ -1092,6 +1102,9 @@ void StreamChecker::evalFprintf(const FnDescription *Desc,
10921102
E.setStreamState(StateNotFailed, StreamState::getOpened(Desc));
10931103
C.addTransition(StateNotFailed);
10941104

1105+
if (!PedanticMode)
1106+
return;
1107+
10951108
// Add transition for the failed state. The resulting value of the file
10961109
// position indicator for the stream is indeterminate.
10971110
StateFailed = E.setStreamState(
@@ -1264,21 +1277,23 @@ void StreamChecker::evalFseek(const FnDescription *Desc, const CallEvent &Call,
12641277
if (!E.Init(Desc, Call, C, State))
12651278
return;
12661279

1267-
// Bifurcate the state into failed and non-failed.
1268-
// Return zero on success, -1 on error.
1280+
// Add success state.
12691281
ProgramStateRef StateNotFailed = E.bindReturnValue(State, C, 0);
1270-
ProgramStateRef StateFailed = E.bindReturnValue(State, C, -1);
1271-
12721282
// No failure: Reset the state to opened with no error.
12731283
StateNotFailed =
12741284
E.setStreamState(StateNotFailed, StreamState::getOpened(Desc));
12751285
C.addTransition(StateNotFailed);
12761286

1287+
if (!PedanticMode)
1288+
return;
1289+
1290+
// Add failure state.
12771291
// At error it is possible that fseek fails but sets none of the error flags.
12781292
// If fseek failed, assume that the file position becomes indeterminate in any
12791293
// case.
12801294
// It is allowed to set the position beyond the end of the file. EOF error
12811295
// should not occur.
1296+
ProgramStateRef StateFailed = E.bindReturnValue(State, C, -1);
12821297
StateFailed = E.setStreamState(
12831298
StateFailed, StreamState::getOpened(Desc, ErrorNone | ErrorFError, true));
12841299
C.addTransition(StateFailed, E.getFailureNoteTag(this, C));
@@ -1316,6 +1331,10 @@ void StreamChecker::evalFsetpos(const FnDescription *Desc,
13161331

13171332
StateNotFailed = E.setStreamState(
13181333
StateNotFailed, StreamState::getOpened(Desc, ErrorNone, false));
1334+
C.addTransition(StateNotFailed);
1335+
1336+
if (!PedanticMode)
1337+
return;
13191338

13201339
// At failure ferror could be set.
13211340
// The standards do not tell what happens with the file position at failure.
@@ -1324,7 +1343,6 @@ void StreamChecker::evalFsetpos(const FnDescription *Desc,
13241343
StateFailed = E.setStreamState(
13251344
StateFailed, StreamState::getOpened(Desc, ErrorNone | ErrorFError, true));
13261345

1327-
C.addTransition(StateNotFailed);
13281346
C.addTransition(StateFailed, E.getFailureNoteTag(this, C));
13291347
}
13301348

@@ -1794,7 +1812,9 @@ ProgramStateRef StreamChecker::checkPointerEscape(
17941812
//===----------------------------------------------------------------------===//
17951813

17961814
void ento::registerStreamChecker(CheckerManager &Mgr) {
1797-
Mgr.registerChecker<StreamChecker>();
1815+
auto *Checker = Mgr.registerChecker<StreamChecker>();
1816+
Checker->PedanticMode =
1817+
Mgr.getAnalyzerOptions().getCheckerBooleanOption(Checker, "Pedantic");
17981818
}
17991819

18001820
bool ento::shouldRegisterStreamChecker(const CheckerManager &Mgr) {

clang/test/Analysis/analyzer-config.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
// CHECK-NEXT: alpha.security.MmapWriteExec:MmapProtExec = 0x04
1313
// CHECK-NEXT: alpha.security.MmapWriteExec:MmapProtRead = 0x01
1414
// CHECK-NEXT: alpha.security.taint.TaintPropagation:Config = ""
15+
// CHECK-NEXT: alpha.unix.Stream:Pedantic = false
1516
// CHECK-NEXT: apply-fixits = false
1617
// CHECK-NEXT: assume-controlled-environment = false
1718
// CHECK-NEXT: avoid-suppressing-null-argument-paths = false

clang/test/Analysis/std-c-library-functions-vs-stream-checker.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Check the case when only the StreamChecker is enabled.
22
// RUN: %clang_analyze_cc1 %s \
33
// RUN: -analyzer-checker=core,alpha.unix.Stream \
4+
// RUN: -analyzer-config alpha.unix.Stream:Pedantic=true \
45
// RUN: -analyzer-checker=debug.ExprInspection \
56
// RUN: -analyzer-config eagerly-assume=false \
67
// RUN: -triple x86_64-unknown-linux \
@@ -19,6 +20,7 @@
1920
// StdLibraryFunctionsChecker are enabled.
2021
// RUN: %clang_analyze_cc1 %s \
2122
// RUN: -analyzer-checker=core,alpha.unix.Stream \
23+
// RUN: -analyzer-config alpha.unix.Stream:Pedantic=true \
2224
// RUN: -analyzer-checker=unix.StdCLibraryFunctions \
2325
// RUN: -analyzer-config unix.StdCLibraryFunctions:DisplayLoadedSummaries=true \
2426
// RUN: -analyzer-checker=debug.ExprInspection \

clang/test/Analysis/stream-errno-note.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// RUN: %clang_analyze_cc1 -analyzer-checker=core \
22
// RUN: -analyzer-checker=alpha.unix.Stream \
3+
// RUN: -analyzer-config alpha.unix.Stream:Pedantic=true \
34
// RUN: -analyzer-checker=unix.Errno \
45
// RUN: -analyzer-checker=unix.StdCLibraryFunctions \
56
// RUN: -analyzer-config unix.StdCLibraryFunctions:ModelPOSIX=true \

clang/test/Analysis/stream-errno.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
// RUN: %clang_analyze_cc1 -analyzer-checker=core,alpha.unix.Stream,unix.Errno,unix.StdCLibraryFunctions,debug.ExprInspection \
2+
// RUN: -analyzer-config alpha.unix.Stream:Pedantic=true \
23
// RUN: -analyzer-config unix.StdCLibraryFunctions:ModelPOSIX=true -verify %s
34

45
#include "Inputs/system-header-simulator.h"

clang/test/Analysis/stream-error.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// RUN: %clang_analyze_cc1 -verify %s \
22
// RUN: -analyzer-checker=core \
33
// RUN: -analyzer-checker=alpha.unix.Stream \
4+
// RUN: -analyzer-config alpha.unix.Stream:Pedantic=true \
45
// RUN: -analyzer-checker=debug.StreamTester \
56
// RUN: -analyzer-checker=debug.ExprInspection
67

clang/test/Analysis/stream-note.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// RUN: %clang_analyze_cc1 -analyzer-checker=core,alpha.unix.Stream -analyzer-output text \
2+
// RUN: -analyzer-config alpha.unix.Stream:Pedantic=true \
23
// RUN: -verify %s
34
// RUN: %clang_analyze_cc1 -analyzer-checker=core,alpha.unix.Stream,unix.StdCLibraryFunctions -analyzer-output text \
5+
// RUN: -analyzer-config alpha.unix.Stream:Pedantic=true \
46
// RUN: -analyzer-config unix.StdCLibraryFunctions:ModelPOSIX=true -verify=expected,stdargs %s
57

68
#include "Inputs/system-header-simulator.h"

clang/test/Analysis/stream-pedantic.c

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
// RUN: %clang_analyze_cc1 -triple=x86_64-pc-linux-gnu -analyzer-checker=core,alpha.unix.Stream,debug.ExprInspection \
2+
// RUN: -analyzer-config alpha.unix.Stream:Pedantic=false -verify=nopedantic %s
3+
4+
// RUN: %clang_analyze_cc1 -triple=x86_64-pc-linux-gnu -analyzer-checker=core,alpha.unix.Stream,debug.ExprInspection \
5+
// RUN: -analyzer-config alpha.unix.Stream:Pedantic=true -verify=pedantic %s
6+
7+
#include "Inputs/system-header-simulator.h"
8+
9+
void clang_analyzer_eval(int);
10+
11+
void check_fwrite(void) {
12+
char *Buf = "123456789";
13+
FILE *Fp = tmpfile();
14+
if (!Fp)
15+
return;
16+
size_t Ret = fwrite(Buf, 1, 10, Fp);
17+
clang_analyzer_eval(Ret == 0); // nopedantic-warning {{FALSE}} \
18+
// pedantic-warning {{FALSE}} \
19+
// pedantic-warning {{TRUE}}
20+
fputc('A', Fp); // pedantic-warning {{might be 'indeterminate'}}
21+
fclose(Fp);
22+
}
23+
24+
void check_fputc(void) {
25+
FILE *Fp = tmpfile();
26+
if (!Fp)
27+
return;
28+
int Ret = fputc('A', Fp);
29+
clang_analyzer_eval(Ret == EOF); // nopedantic-warning {{FALSE}} \
30+
// pedantic-warning {{FALSE}} \
31+
// pedantic-warning {{TRUE}}
32+
fputc('A', Fp); // pedantic-warning {{might be 'indeterminate'}}
33+
fclose(Fp);
34+
}
35+
36+
void check_fputs(void) {
37+
FILE *Fp = tmpfile();
38+
if (!Fp)
39+
return;
40+
int Ret = fputs("ABC", Fp);
41+
clang_analyzer_eval(Ret == EOF); // nopedantic-warning {{FALSE}} \
42+
// pedantic-warning {{FALSE}} \
43+
// pedantic-warning {{TRUE}}
44+
fputc('A', Fp); // pedantic-warning {{might be 'indeterminate'}}
45+
fclose(Fp);
46+
}
47+
48+
void check_fprintf(void) {
49+
FILE *Fp = tmpfile();
50+
if (!Fp)
51+
return;
52+
int Ret = fprintf(Fp, "ABC");
53+
clang_analyzer_eval(Ret < 0); // nopedantic-warning {{FALSE}} \
54+
// pedantic-warning {{FALSE}} \
55+
// pedantic-warning {{TRUE}}
56+
fputc('A', Fp); // pedantic-warning {{might be 'indeterminate'}}
57+
fclose(Fp);
58+
}
59+
60+
void check_fseek(void) {
61+
FILE *Fp = tmpfile();
62+
if (!Fp)
63+
return;
64+
int Ret = fseek(Fp, 0, 0);
65+
clang_analyzer_eval(Ret == -1); // nopedantic-warning {{FALSE}} \
66+
// pedantic-warning {{FALSE}} \
67+
// pedantic-warning {{TRUE}}
68+
fputc('A', Fp); // pedantic-warning {{might be 'indeterminate'}}
69+
fclose(Fp);
70+
}
71+
72+
void check_fseeko(void) {
73+
FILE *Fp = tmpfile();
74+
if (!Fp)
75+
return;
76+
int Ret = fseeko(Fp, 0, 0);
77+
clang_analyzer_eval(Ret == -1); // nopedantic-warning {{FALSE}} \
78+
// pedantic-warning {{FALSE}} \
79+
// pedantic-warning {{TRUE}}
80+
fputc('A', Fp); // pedantic-warning {{might be 'indeterminate'}}
81+
fclose(Fp);
82+
}
83+
84+
void check_fsetpos(void) {
85+
FILE *Fp = tmpfile();
86+
if (!Fp)
87+
return;
88+
fpos_t Pos;
89+
int Ret = fsetpos(Fp, &Pos);
90+
clang_analyzer_eval(Ret); // nopedantic-warning {{FALSE}} \
91+
// pedantic-warning {{FALSE}} \
92+
// pedantic-warning {{TRUE}}
93+
fputc('A', Fp); // pedantic-warning {{might be 'indeterminate'}}
94+
fclose(Fp);
95+
}

clang/test/Analysis/stream-stdlibraryfunctionargs.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
// RUN: %clang_analyze_cc1 -analyzer-checker=core,alpha.unix.Stream,unix.StdCLibraryFunctions,debug.ExprInspection \
2+
// RUN: -analyzer-config alpha.unix.Stream:Pedantic=true \
23
// RUN: -analyzer-config unix.StdCLibraryFunctions:ModelPOSIX=true -verify=stream,any %s
34

45
// RUN: %clang_analyze_cc1 -analyzer-checker=core,alpha.unix.Stream,debug.ExprInspection \
6+
// RUN: -analyzer-config alpha.unix.Stream:Pedantic=true \
57
// RUN: -analyzer-config unix.StdCLibraryFunctions:ModelPOSIX=true -verify=stream,any %s
68

79
// RUN: %clang_analyze_cc1 -analyzer-checker=core,unix.StdCLibraryFunctions,debug.ExprInspection \
10+
// RUN: -analyzer-config alpha.unix.Stream:Pedantic=true \
811
// RUN: -analyzer-config unix.StdCLibraryFunctions:ModelPOSIX=true -verify=stdfunc,any %s
912

1013
#include "Inputs/system-header-simulator.h"

clang/test/Analysis/stream.c

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
1-
// RUN: %clang_analyze_cc1 -triple=x86_64-pc-linux-gnu -analyzer-checker=core,alpha.unix.Stream,debug.ExprInspection -verify %s
2-
// RUN: %clang_analyze_cc1 -triple=armv8-none-linux-eabi -analyzer-checker=core,alpha.unix.Stream,debug.ExprInspection -verify %s
3-
// RUN: %clang_analyze_cc1 -triple=aarch64-linux-gnu -analyzer-checker=core,alpha.unix.Stream,debug.ExprInspection -verify %s
4-
// RUN: %clang_analyze_cc1 -triple=hexagon -analyzer-checker=core,alpha.unix.Stream,debug.ExprInspection -verify %s
1+
// RUN: %clang_analyze_cc1 -triple=x86_64-pc-linux-gnu -analyzer-checker=core,alpha.unix.Stream,debug.ExprInspection \
2+
// RUN: -analyzer-config alpha.unix.Stream:Pedantic=true -verify %s
3+
// RUN: %clang_analyze_cc1 -triple=armv8-none-linux-eabi -analyzer-checker=core,alpha.unix.Stream,debug.ExprInspection \
4+
// RUN: -analyzer-config alpha.unix.Stream:Pedantic=true -verify %s
5+
// RUN: %clang_analyze_cc1 -triple=aarch64-linux-gnu -analyzer-checker=core,alpha.unix.Stream,debug.ExprInspection \
6+
// RUN: -analyzer-config alpha.unix.Stream:Pedantic=true -verify %s
7+
// RUN: %clang_analyze_cc1 -triple=hexagon -analyzer-checker=core,alpha.unix.Stream,debug.ExprInspection \
8+
// RUN: -analyzer-config alpha.unix.Stream:Pedantic=true -verify %s
59

610
#include "Inputs/system-header-simulator.h"
711
#include "Inputs/system-header-simulator-for-malloc.h"

0 commit comments

Comments
 (0)