[clang] [clang][analyzer] Add function 'fscanf' to StreamChecker. (PR #78180)

2024-01-24 Thread Balazs Benics via cfe-commits
=?utf-8?q?Balázs_Kéri?= 
Message-ID:
In-Reply-To: 


steakhal wrote:

> This patch breaks a downstream test, like this:
> 
> ```c++
> void test_fscanf_2() {
>   FILE *F1 = tmpfile();
>   if (!F1)
> return;
> 
>   int a;
>   unsigned b;
>   fscanf(F1, "%d %u", , );
>   clang_analyzer_dump_int(a); // FP warning: 1st function call argument is an 
> uninitialized value
>   fclose(F1);
> }
> ```
> 
> The FP is present, even if I guard the dump with `if (ret == 2)`.

>I think this can be caused by missing the default evalCall for fscanf, but did 
>not find the exact reason.

Now I know what's going on - after cherry-picking like 15 StreamChecker patches 
:sweat_smile: (Yea, some fun for the last couple of days) Previously the call 
was default eval called, thus arguments escaped. This is no longer the case, 
thus the regions ``, `` won't escape, thus preserves their original values 
(which was `UndefinedVal()`)

This is a regression compared to default eval calling "fscanf".

https://github.com/llvm/llvm-project/pull/78180
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang][analyzer] Add function 'fscanf' to StreamChecker. (PR #78180)

2024-01-23 Thread Balázs Kéri via cfe-commits

balazske wrote:

> This patch breaks a downstream test, like this:

I think this can be caused by missing the default `evalCall` for `fscanf`, but 
did not find the exact reason.

https://github.com/llvm/llvm-project/pull/78180
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang][analyzer] Add function 'fscanf' to StreamChecker. (PR #78180)

2024-01-22 Thread Balazs Benics via cfe-commits
=?utf-8?q?Balázs_Kéri?= 
Message-ID:
In-Reply-To: 


steakhal wrote:

This patch breaks a downstream test, like this:
```c++
void test_fscanf_2() {
  FILE *F1 = tmpfile();
  if (!F1)
return;

  int a;
  unsigned b;
  fscanf(F1, "%d %u", , );
  clang_analyzer_dump_int(a); // FP warning: 1st function call argument is an 
uninitialized value
  fclose(F1);
}
```
The FP is present, even if I guard the dump with `if (ret == 2)`.

https://github.com/llvm/llvm-project/pull/78180
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang][analyzer] Add function 'fscanf' to StreamChecker. (PR #78180)

2024-01-22 Thread Balázs Kéri via cfe-commits

https://github.com/balazske closed 
https://github.com/llvm/llvm-project/pull/78180
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang][analyzer] Add function 'fscanf' to StreamChecker. (PR #78180)

2024-01-19 Thread via cfe-commits
=?utf-8?q?Bal=C3=A1zs_K=C3=A9ri?= 
Message-ID:
In-Reply-To: 


https://github.com/NagyDonat approved this pull request.


https://github.com/llvm/llvm-project/pull/78180
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang][analyzer] Add function 'fscanf' to StreamChecker. (PR #78180)

2024-01-16 Thread Ben Shi via cfe-commits
=?utf-8?q?Bal=C3=A1zs_K=C3=A9ri?= 
Message-ID:
In-Reply-To: 


https://github.com/benshi001 approved this pull request.


https://github.com/llvm/llvm-project/pull/78180
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang][analyzer] Add function 'fscanf' to StreamChecker. (PR #78180)

2024-01-16 Thread Balázs Kéri via cfe-commits

https://github.com/balazske updated 
https://github.com/llvm/llvm-project/pull/78180

From aacfc3f06ee51ede08464cb23ec32b210e703b6b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bal=C3=A1zs=20K=C3=A9ri?= 
Date: Mon, 15 Jan 2024 16:41:34 +0100
Subject: [PATCH 1/2] [clang][analyzer] Add function 'fscanf' to StreamChecker.

---
 .../StaticAnalyzer/Checkers/StreamChecker.cpp | 61 +++
 clang/test/Analysis/stream-error.c| 25 
 clang/test/Analysis/stream.c  |  6 ++
 3 files changed, 92 insertions(+)

diff --git a/clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp 
b/clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp
index 95c7503e49e0d3..ab23a428e9397f 100644
--- a/clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp
@@ -266,6 +266,9 @@ class StreamChecker : public Checker(Call.getOriginExpr());
+  if (!CE)
+return;
+
+  const StreamState *OldSS = State->get(StreamSym);
+  if (!OldSS)
+return;
+
+  assertStreamStateOpened(OldSS);
+
+  SValBuilder  = C.getSValBuilder();
+  ASTContext  = C.getASTContext();
+
+  if (OldSS->ErrorState != ErrorFEof) {
+NonLoc RetVal = makeRetVal(C, CE).castAs();
+ProgramStateRef StateNotFailed =
+State->BindExpr(CE, C.getLocationContext(), RetVal);
+auto RetGeZero =
+SVB.evalBinOp(StateNotFailed, BO_GE, RetVal,
+  SVB.makeZeroVal(ACtx.IntTy), SVB.getConditionType())
+.getAs();
+if (!RetGeZero)
+  return;
+StateNotFailed = StateNotFailed->assume(*RetGeZero, true);
+
+C.addTransition(StateNotFailed);
+  }
+
+  // Add transition for the failed state.
+  // Error occurs if nothing is matched yet and reading the input fails.
+  // Error can be EOF, or other error. At "other error" FERROR or 'errno' can
+  // be set but it is not further specified if all are required to be set.
+  // Documentation does not mention, but file position will be set to
+  // indeterminate similarly as at 'fread'.
+  ProgramStateRef StateFailed = bindInt(*EofVal, State, C, CE);
+  StreamErrorState NewES = (OldSS->ErrorState == ErrorFEof)
+   ? ErrorFEof
+   : ErrorNone | ErrorFEof | ErrorFError;
+  StreamState NewSS = StreamState::getOpened(Desc, NewES, !NewES.isFEof());
+  StateFailed = StateFailed->set(StreamSym, NewSS);
+  if (OldSS->ErrorState != ErrorFEof)
+C.addTransition(StateFailed, constructSetEofNoteTag(C, StreamSym));
+  else
+C.addTransition(StateFailed);
+}
+
 void StreamChecker::evalUngetc(const FnDescription *Desc, const CallEvent 
,
CheckerContext ) const {
   ProgramStateRef State = C.getState();
diff --git a/clang/test/Analysis/stream-error.c 
b/clang/test/Analysis/stream-error.c
index 0f7fdddc0dd4cd..2cf46e1d4ad51f 100644
--- a/clang/test/Analysis/stream-error.c
+++ b/clang/test/Analysis/stream-error.c
@@ -208,6 +208,31 @@ void error_fprintf(void) {
   fprintf(F, "ccc"); // expected-warning {{Stream might be already closed}}
 }
 
+void error_fscanf(int *A) {
+  FILE *F = tmpfile();
+  if (!F)
+return;
+  int Ret = fscanf(F, "a%ib", A);
+  if (Ret >= 0) {
+clang_analyzer_eval(feof(F) || ferror(F)); // expected-warning {{FALSE}}
+fscanf(F, "bbb");  // no-warning
+  } else {
+if (ferror(F)) {
+  clang_analyzer_warnIfReached(); // expected-warning {{REACHABLE}}
+  fscanf(F, "bbb");   // expected-warning {{might be 
'indeterminate'}}
+} else if (feof(F)) {
+  clang_analyzer_warnIfReached(); // expected-warning {{REACHABLE}}
+  fscanf(F, "bbb");   // expected-warning {{is in EOF state}}
+  clang_analyzer_eval(feof(F));   // expected-warning {{TRUE}}
+} else {
+  clang_analyzer_warnIfReached(); // expected-warning {{REACHABLE}}
+  fscanf(F, "bbb");   // expected-warning {{might be 
'indeterminate'}}
+}
+  }
+  fclose(F);
+  fscanf(F, "ccc"); // expected-warning {{Stream might be already closed}}
+}
+
 void error_ungetc() {
   FILE *F = tmpfile();
   if (!F)
diff --git a/clang/test/Analysis/stream.c b/clang/test/Analysis/stream.c
index e8f06922bdb2f3..36a9b4e26b07a2 100644
--- a/clang/test/Analysis/stream.c
+++ b/clang/test/Analysis/stream.c
@@ -45,6 +45,12 @@ void check_fprintf(void) {
   fclose(fp);
 }
 
+void check_fscanf(void) {
+  FILE *fp = tmpfile();
+  fscanf(fp, "ABC"); // expected-warning {{Stream pointer might be NULL}}
+  fclose(fp);
+}
+
 void check_ungetc(void) {
   FILE *fp = tmpfile();
   ungetc('A', fp); // expected-warning {{Stream pointer might be NULL}}

From 128b59ebe046994f38f2e488ce44e891c0b1b0d4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bal=C3=A1zs=20K=C3=A9ri?= 
Date: Tue, 16 Jan 2024 11:27:44 +0100
Subject: [PATCH 2/2] added clarifying comment

---
 clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp | 8 
 1 file changed, 8 insertions(+)

diff --git 

[clang] [clang][analyzer] Add function 'fscanf' to StreamChecker. (PR #78180)

2024-01-15 Thread Ben Shi via cfe-commits


@@ -975,6 +981,61 @@ void StreamChecker::evalFprintf(const FnDescription *Desc,
   C.addTransition(StateFailed);
 }
 
+void StreamChecker::evalFscanf(const FnDescription *Desc, const CallEvent 
,
+   CheckerContext ) const {
+  ProgramStateRef State = C.getState();
+  if (Call.getNumArgs() < 2)
+return;
+  SymbolRef StreamSym = getStreamArg(Desc, Call).getAsSymbol();
+  if (!StreamSym)
+return;
+
+  const CallExpr *CE = dyn_cast_or_null(Call.getOriginExpr());
+  if (!CE)
+return;
+
+  const StreamState *OldSS = State->get(StreamSym);
+  if (!OldSS)
+return;
+
+  assertStreamStateOpened(OldSS);
+
+  SValBuilder  = C.getSValBuilder();
+  ASTContext  = C.getASTContext();
+
+  if (OldSS->ErrorState != ErrorFEof) {

benshi001 wrote:

Shall we add a comment like

```
'fscanf' return matched input items, this can be from zero to the total number 
of input items. 
If the stream read is successful but match fails, we still think 'fscanf' is 
success.
```

Something like that. You can reorganize my words.

https://github.com/llvm/llvm-project/pull/78180
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang][analyzer] Add function 'fscanf' to StreamChecker. (PR #78180)

2024-01-15 Thread via cfe-commits

llvmbot wrote:




@llvm/pr-subscribers-clang

Author: Balázs Kéri (balazske)


Changes



---
Full diff: https://github.com/llvm/llvm-project/pull/78180.diff


3 Files Affected:

- (modified) clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp (+61) 
- (modified) clang/test/Analysis/stream-error.c (+25) 
- (modified) clang/test/Analysis/stream.c (+6) 


``diff
diff --git a/clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp 
b/clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp
index 95c7503e49e0d32..ab23a428e9397f2 100644
--- a/clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp
@@ -266,6 +266,9 @@ class StreamChecker : public Checker(Call.getOriginExpr());
+  if (!CE)
+return;
+
+  const StreamState *OldSS = State->get(StreamSym);
+  if (!OldSS)
+return;
+
+  assertStreamStateOpened(OldSS);
+
+  SValBuilder  = C.getSValBuilder();
+  ASTContext  = C.getASTContext();
+
+  if (OldSS->ErrorState != ErrorFEof) {
+NonLoc RetVal = makeRetVal(C, CE).castAs();
+ProgramStateRef StateNotFailed =
+State->BindExpr(CE, C.getLocationContext(), RetVal);
+auto RetGeZero =
+SVB.evalBinOp(StateNotFailed, BO_GE, RetVal,
+  SVB.makeZeroVal(ACtx.IntTy), SVB.getConditionType())
+.getAs();
+if (!RetGeZero)
+  return;
+StateNotFailed = StateNotFailed->assume(*RetGeZero, true);
+
+C.addTransition(StateNotFailed);
+  }
+
+  // Add transition for the failed state.
+  // Error occurs if nothing is matched yet and reading the input fails.
+  // Error can be EOF, or other error. At "other error" FERROR or 'errno' can
+  // be set but it is not further specified if all are required to be set.
+  // Documentation does not mention, but file position will be set to
+  // indeterminate similarly as at 'fread'.
+  ProgramStateRef StateFailed = bindInt(*EofVal, State, C, CE);
+  StreamErrorState NewES = (OldSS->ErrorState == ErrorFEof)
+   ? ErrorFEof
+   : ErrorNone | ErrorFEof | ErrorFError;
+  StreamState NewSS = StreamState::getOpened(Desc, NewES, !NewES.isFEof());
+  StateFailed = StateFailed->set(StreamSym, NewSS);
+  if (OldSS->ErrorState != ErrorFEof)
+C.addTransition(StateFailed, constructSetEofNoteTag(C, StreamSym));
+  else
+C.addTransition(StateFailed);
+}
+
 void StreamChecker::evalUngetc(const FnDescription *Desc, const CallEvent 
,
CheckerContext ) const {
   ProgramStateRef State = C.getState();
diff --git a/clang/test/Analysis/stream-error.c 
b/clang/test/Analysis/stream-error.c
index 0f7fdddc0dd4cd7..2cf46e1d4ad51f1 100644
--- a/clang/test/Analysis/stream-error.c
+++ b/clang/test/Analysis/stream-error.c
@@ -208,6 +208,31 @@ void error_fprintf(void) {
   fprintf(F, "ccc"); // expected-warning {{Stream might be already closed}}
 }
 
+void error_fscanf(int *A) {
+  FILE *F = tmpfile();
+  if (!F)
+return;
+  int Ret = fscanf(F, "a%ib", A);
+  if (Ret >= 0) {
+clang_analyzer_eval(feof(F) || ferror(F)); // expected-warning {{FALSE}}
+fscanf(F, "bbb");  // no-warning
+  } else {
+if (ferror(F)) {
+  clang_analyzer_warnIfReached(); // expected-warning {{REACHABLE}}
+  fscanf(F, "bbb");   // expected-warning {{might be 
'indeterminate'}}
+} else if (feof(F)) {
+  clang_analyzer_warnIfReached(); // expected-warning {{REACHABLE}}
+  fscanf(F, "bbb");   // expected-warning {{is in EOF state}}
+  clang_analyzer_eval(feof(F));   // expected-warning {{TRUE}}
+} else {
+  clang_analyzer_warnIfReached(); // expected-warning {{REACHABLE}}
+  fscanf(F, "bbb");   // expected-warning {{might be 
'indeterminate'}}
+}
+  }
+  fclose(F);
+  fscanf(F, "ccc"); // expected-warning {{Stream might be already closed}}
+}
+
 void error_ungetc() {
   FILE *F = tmpfile();
   if (!F)
diff --git a/clang/test/Analysis/stream.c b/clang/test/Analysis/stream.c
index e8f06922bdb2f37..36a9b4e26b07a28 100644
--- a/clang/test/Analysis/stream.c
+++ b/clang/test/Analysis/stream.c
@@ -45,6 +45,12 @@ void check_fprintf(void) {
   fclose(fp);
 }
 
+void check_fscanf(void) {
+  FILE *fp = tmpfile();
+  fscanf(fp, "ABC"); // expected-warning {{Stream pointer might be NULL}}
+  fclose(fp);
+}
+
 void check_ungetc(void) {
   FILE *fp = tmpfile();
   ungetc('A', fp); // expected-warning {{Stream pointer might be NULL}}

``




https://github.com/llvm/llvm-project/pull/78180
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang][analyzer] Add function 'fscanf' to StreamChecker. (PR #78180)

2024-01-15 Thread via cfe-commits

llvmbot wrote:




@llvm/pr-subscribers-clang-static-analyzer-1

Author: Balázs Kéri (balazske)


Changes



---
Full diff: https://github.com/llvm/llvm-project/pull/78180.diff


3 Files Affected:

- (modified) clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp (+61) 
- (modified) clang/test/Analysis/stream-error.c (+25) 
- (modified) clang/test/Analysis/stream.c (+6) 


``diff
diff --git a/clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp 
b/clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp
index 95c7503e49e0d32..ab23a428e9397f2 100644
--- a/clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp
@@ -266,6 +266,9 @@ class StreamChecker : public Checker(Call.getOriginExpr());
+  if (!CE)
+return;
+
+  const StreamState *OldSS = State->get(StreamSym);
+  if (!OldSS)
+return;
+
+  assertStreamStateOpened(OldSS);
+
+  SValBuilder  = C.getSValBuilder();
+  ASTContext  = C.getASTContext();
+
+  if (OldSS->ErrorState != ErrorFEof) {
+NonLoc RetVal = makeRetVal(C, CE).castAs();
+ProgramStateRef StateNotFailed =
+State->BindExpr(CE, C.getLocationContext(), RetVal);
+auto RetGeZero =
+SVB.evalBinOp(StateNotFailed, BO_GE, RetVal,
+  SVB.makeZeroVal(ACtx.IntTy), SVB.getConditionType())
+.getAs();
+if (!RetGeZero)
+  return;
+StateNotFailed = StateNotFailed->assume(*RetGeZero, true);
+
+C.addTransition(StateNotFailed);
+  }
+
+  // Add transition for the failed state.
+  // Error occurs if nothing is matched yet and reading the input fails.
+  // Error can be EOF, or other error. At "other error" FERROR or 'errno' can
+  // be set but it is not further specified if all are required to be set.
+  // Documentation does not mention, but file position will be set to
+  // indeterminate similarly as at 'fread'.
+  ProgramStateRef StateFailed = bindInt(*EofVal, State, C, CE);
+  StreamErrorState NewES = (OldSS->ErrorState == ErrorFEof)
+   ? ErrorFEof
+   : ErrorNone | ErrorFEof | ErrorFError;
+  StreamState NewSS = StreamState::getOpened(Desc, NewES, !NewES.isFEof());
+  StateFailed = StateFailed->set(StreamSym, NewSS);
+  if (OldSS->ErrorState != ErrorFEof)
+C.addTransition(StateFailed, constructSetEofNoteTag(C, StreamSym));
+  else
+C.addTransition(StateFailed);
+}
+
 void StreamChecker::evalUngetc(const FnDescription *Desc, const CallEvent 
,
CheckerContext ) const {
   ProgramStateRef State = C.getState();
diff --git a/clang/test/Analysis/stream-error.c 
b/clang/test/Analysis/stream-error.c
index 0f7fdddc0dd4cd7..2cf46e1d4ad51f1 100644
--- a/clang/test/Analysis/stream-error.c
+++ b/clang/test/Analysis/stream-error.c
@@ -208,6 +208,31 @@ void error_fprintf(void) {
   fprintf(F, "ccc"); // expected-warning {{Stream might be already closed}}
 }
 
+void error_fscanf(int *A) {
+  FILE *F = tmpfile();
+  if (!F)
+return;
+  int Ret = fscanf(F, "a%ib", A);
+  if (Ret >= 0) {
+clang_analyzer_eval(feof(F) || ferror(F)); // expected-warning {{FALSE}}
+fscanf(F, "bbb");  // no-warning
+  } else {
+if (ferror(F)) {
+  clang_analyzer_warnIfReached(); // expected-warning {{REACHABLE}}
+  fscanf(F, "bbb");   // expected-warning {{might be 
'indeterminate'}}
+} else if (feof(F)) {
+  clang_analyzer_warnIfReached(); // expected-warning {{REACHABLE}}
+  fscanf(F, "bbb");   // expected-warning {{is in EOF state}}
+  clang_analyzer_eval(feof(F));   // expected-warning {{TRUE}}
+} else {
+  clang_analyzer_warnIfReached(); // expected-warning {{REACHABLE}}
+  fscanf(F, "bbb");   // expected-warning {{might be 
'indeterminate'}}
+}
+  }
+  fclose(F);
+  fscanf(F, "ccc"); // expected-warning {{Stream might be already closed}}
+}
+
 void error_ungetc() {
   FILE *F = tmpfile();
   if (!F)
diff --git a/clang/test/Analysis/stream.c b/clang/test/Analysis/stream.c
index e8f06922bdb2f37..36a9b4e26b07a28 100644
--- a/clang/test/Analysis/stream.c
+++ b/clang/test/Analysis/stream.c
@@ -45,6 +45,12 @@ void check_fprintf(void) {
   fclose(fp);
 }
 
+void check_fscanf(void) {
+  FILE *fp = tmpfile();
+  fscanf(fp, "ABC"); // expected-warning {{Stream pointer might be NULL}}
+  fclose(fp);
+}
+
 void check_ungetc(void) {
   FILE *fp = tmpfile();
   ungetc('A', fp); // expected-warning {{Stream pointer might be NULL}}

``




https://github.com/llvm/llvm-project/pull/78180
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang][analyzer] Add function 'fscanf' to StreamChecker. (PR #78180)

2024-01-15 Thread Balázs Kéri via cfe-commits

https://github.com/balazske created 
https://github.com/llvm/llvm-project/pull/78180

None

From aacfc3f06ee51ede08464cb23ec32b210e703b6b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bal=C3=A1zs=20K=C3=A9ri?= 
Date: Mon, 15 Jan 2024 16:41:34 +0100
Subject: [PATCH] [clang][analyzer] Add function 'fscanf' to StreamChecker.

---
 .../StaticAnalyzer/Checkers/StreamChecker.cpp | 61 +++
 clang/test/Analysis/stream-error.c| 25 
 clang/test/Analysis/stream.c  |  6 ++
 3 files changed, 92 insertions(+)

diff --git a/clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp 
b/clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp
index 95c7503e49e0d3..ab23a428e9397f 100644
--- a/clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp
@@ -266,6 +266,9 @@ class StreamChecker : public Checker(Call.getOriginExpr());
+  if (!CE)
+return;
+
+  const StreamState *OldSS = State->get(StreamSym);
+  if (!OldSS)
+return;
+
+  assertStreamStateOpened(OldSS);
+
+  SValBuilder  = C.getSValBuilder();
+  ASTContext  = C.getASTContext();
+
+  if (OldSS->ErrorState != ErrorFEof) {
+NonLoc RetVal = makeRetVal(C, CE).castAs();
+ProgramStateRef StateNotFailed =
+State->BindExpr(CE, C.getLocationContext(), RetVal);
+auto RetGeZero =
+SVB.evalBinOp(StateNotFailed, BO_GE, RetVal,
+  SVB.makeZeroVal(ACtx.IntTy), SVB.getConditionType())
+.getAs();
+if (!RetGeZero)
+  return;
+StateNotFailed = StateNotFailed->assume(*RetGeZero, true);
+
+C.addTransition(StateNotFailed);
+  }
+
+  // Add transition for the failed state.
+  // Error occurs if nothing is matched yet and reading the input fails.
+  // Error can be EOF, or other error. At "other error" FERROR or 'errno' can
+  // be set but it is not further specified if all are required to be set.
+  // Documentation does not mention, but file position will be set to
+  // indeterminate similarly as at 'fread'.
+  ProgramStateRef StateFailed = bindInt(*EofVal, State, C, CE);
+  StreamErrorState NewES = (OldSS->ErrorState == ErrorFEof)
+   ? ErrorFEof
+   : ErrorNone | ErrorFEof | ErrorFError;
+  StreamState NewSS = StreamState::getOpened(Desc, NewES, !NewES.isFEof());
+  StateFailed = StateFailed->set(StreamSym, NewSS);
+  if (OldSS->ErrorState != ErrorFEof)
+C.addTransition(StateFailed, constructSetEofNoteTag(C, StreamSym));
+  else
+C.addTransition(StateFailed);
+}
+
 void StreamChecker::evalUngetc(const FnDescription *Desc, const CallEvent 
,
CheckerContext ) const {
   ProgramStateRef State = C.getState();
diff --git a/clang/test/Analysis/stream-error.c 
b/clang/test/Analysis/stream-error.c
index 0f7fdddc0dd4cd..2cf46e1d4ad51f 100644
--- a/clang/test/Analysis/stream-error.c
+++ b/clang/test/Analysis/stream-error.c
@@ -208,6 +208,31 @@ void error_fprintf(void) {
   fprintf(F, "ccc"); // expected-warning {{Stream might be already closed}}
 }
 
+void error_fscanf(int *A) {
+  FILE *F = tmpfile();
+  if (!F)
+return;
+  int Ret = fscanf(F, "a%ib", A);
+  if (Ret >= 0) {
+clang_analyzer_eval(feof(F) || ferror(F)); // expected-warning {{FALSE}}
+fscanf(F, "bbb");  // no-warning
+  } else {
+if (ferror(F)) {
+  clang_analyzer_warnIfReached(); // expected-warning {{REACHABLE}}
+  fscanf(F, "bbb");   // expected-warning {{might be 
'indeterminate'}}
+} else if (feof(F)) {
+  clang_analyzer_warnIfReached(); // expected-warning {{REACHABLE}}
+  fscanf(F, "bbb");   // expected-warning {{is in EOF state}}
+  clang_analyzer_eval(feof(F));   // expected-warning {{TRUE}}
+} else {
+  clang_analyzer_warnIfReached(); // expected-warning {{REACHABLE}}
+  fscanf(F, "bbb");   // expected-warning {{might be 
'indeterminate'}}
+}
+  }
+  fclose(F);
+  fscanf(F, "ccc"); // expected-warning {{Stream might be already closed}}
+}
+
 void error_ungetc() {
   FILE *F = tmpfile();
   if (!F)
diff --git a/clang/test/Analysis/stream.c b/clang/test/Analysis/stream.c
index e8f06922bdb2f3..36a9b4e26b07a2 100644
--- a/clang/test/Analysis/stream.c
+++ b/clang/test/Analysis/stream.c
@@ -45,6 +45,12 @@ void check_fprintf(void) {
   fclose(fp);
 }
 
+void check_fscanf(void) {
+  FILE *fp = tmpfile();
+  fscanf(fp, "ABC"); // expected-warning {{Stream pointer might be NULL}}
+  fclose(fp);
+}
+
 void check_ungetc(void) {
   FILE *fp = tmpfile();
   ungetc('A', fp); // expected-warning {{Stream pointer might be NULL}}

___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits