[clang] [clang][analyzer] Support `fflush` in the StreamChecker (PR #74296)

Balázs Kéri via cfe-commits cfe-commits at lists.llvm.org
Wed Dec 13 07:03:14 PST 2023


================
@@ -1191,6 +1199,84 @@ void StreamChecker::evalSetFeofFerror(const FnDescription *Desc,
   C.addTransition(State);
 }
 
+void StreamChecker::preFflush(const FnDescription *Desc, const CallEvent &Call,
+                              CheckerContext &C) const {
+  ProgramStateRef State = C.getState();
+  SVal StreamVal = getStreamArg(Desc, Call);
+  std::optional<DefinedSVal> Stream = StreamVal.getAs<DefinedSVal>();
+  SymbolRef StreamSym = StreamVal.getAsSymbol();
+  if (!Stream || !StreamSym)
+    return;
+
+  ProgramStateRef StateNotNull, StateNull;
+  std::tie(StateNotNull, StateNull) =
+      C.getConstraintManager().assumeDual(State, *Stream);
+  if (StateNotNull) {
+    if (StateNotNull = ensureStreamOpened(StreamVal, C, StateNotNull))
+      C.addTransition(StateNotNull);
+  } else if (StateNull) {
+    const StreamState *SS = StateNull->get<StreamMap>(StreamSym);
+    if (!SS || !SS->isOpenFailed()) {
+      StateNull = StateNull->set<StreamMap>(StreamSym,
+                                            StreamState::getOpenFailed(Desc));
+      if (StateNull)
+        C.addTransition(StateNull);
+    }
+  }
+}
+
+void StreamChecker::evalFflush(const FnDescription *Desc, const CallEvent &Call,
+                               CheckerContext &C) const {
+  ProgramStateRef State = C.getState();
+  SymbolRef StreamSym = getStreamArg(Desc, Call).getAsSymbol();
+  const StreamState *SS = nullptr;
+
+  // Skip if the input stream's state is unknown.
+  // FIXME: We should skip non-NULL constant, such as `(FILE *) 0x1234`.
+  if (StreamSym) {
+    if (!(SS = State->get<StreamMap>(StreamSym)))
+      return;
+    assert((SS->isOpened() || SS->isOpenFailed()) &&
+           "Stream is expected to opened or open-failed");
+  }
+
+  const CallExpr *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr());
+  if (!CE)
+    return;
+
+  // `fflush` returns EOF on failure, otherwise returns 0.
+  ProgramStateRef StateFailed = bindInt(*EofVal, State, C, CE);
+  C.addTransition(StateFailed);
+
+  // Clear error states if `fflush` returns 0, but retain their EOF flags.
+  ProgramStateRef StateNotFailed = bindInt(0, State, C, CE);
+  auto ClearError = [&StateNotFailed, Desc](SymbolRef Sym,
+                                            const StreamState *SS) {
+    if (SS->ErrorState & ErrorFError) {
+      StreamErrorState NewES =
+          (SS->ErrorState & ErrorFEof) ? ErrorFEof : ErrorNone;
+      StreamState NewSS = StreamState::getOpened(Desc, NewES, false);
+      StateNotFailed = StateNotFailed->set<StreamMap>(Sym, NewSS);
+    }
+  };
+
+  if (SS && SS->isOpened()) {
----------------
balazske wrote:

This decision can not be made with `SS`. If a stream pointer is known to be non-null but unknown to the checker, `SS` is null, but not NULL is passed to `fflush`. The check can be made with assumptions on `getStreamArg(Desc, Call)`. If it can be both NULL and non-NULL, we can't do anything in the `evalCall` (for the same reason as in `preFflush`), otherwise we can get if it is clearly NULL or not NULL.

https://github.com/llvm/llvm-project/pull/74296


More information about the cfe-commits mailing list