diff --git a/clang/lib/StaticAnalyzer/Checkers/ErrnoChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/ErrnoChecker.cpp --- a/clang/lib/StaticAnalyzer/Checkers/ErrnoChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/ErrnoChecker.cpp @@ -227,12 +227,12 @@ // If 'errno' is invalidated we can not know if it is checked or written into, // allow read and write without bug reports. if (llvm::is_contained(Regions, ErrnoRegion)) - return setErrnoStateIrrelevant(State); + return clearErrnoState(State); // Always reset errno state when the system memory space is invalidated. // The ErrnoRegion is not always found in the list in this case. if (llvm::is_contained(Regions, ErrnoRegion->getMemorySpace())) - return setErrnoStateIrrelevant(State); + return clearErrnoState(State); return State; } diff --git a/clang/lib/StaticAnalyzer/Checkers/ErrnoModeling.h b/clang/lib/StaticAnalyzer/Checkers/ErrnoModeling.h --- a/clang/lib/StaticAnalyzer/Checkers/ErrnoModeling.h +++ b/clang/lib/StaticAnalyzer/Checkers/ErrnoModeling.h @@ -67,6 +67,9 @@ /// Set the errno check state, do not modify the errno value. ProgramStateRef setErrnoState(ProgramStateRef State, ErrnoCheckState EState); +/// Clear state of errno (make it irrelevant). +ProgramStateRef clearErrnoState(ProgramStateRef State); + /// Determine if a `Decl` node related to 'errno'. /// This is true if the declaration is the errno variable or a function /// that returns a pointer to the 'errno' value (usually the 'errno' macro is @@ -101,11 +104,26 @@ ProgramStateRef setErrnoForStdFailure(ProgramStateRef State, CheckerContext &C, NonLoc ErrnoSym); +/// Set errno state for the common case when a standard function indicates +/// failure only by \c errno. Sets \c ErrnoCheckState to \c MustBeChecked, and +/// invalidates the errno region (clear of previous value). +/// At the state transition a note tag created by +/// \c getNoteTagForStdMustBeChecked can be used. +/// \arg \c InvalE Expression that causes invalidation of \c errno. +ProgramStateRef setErrnoStdMustBeChecked(ProgramStateRef State, + CheckerContext &C, const Expr *InvalE); + /// Generate the note tag that can be applied at the state generated by /// \c setErrnoForStdSuccess . /// \arg \c Fn Name of the (standard) function that is modeled. const NoteTag *getNoteTagForStdSuccess(CheckerContext &C, llvm::StringRef Fn); +/// Generate the note tag that can be applied at the state generated by +/// \c setErrnoStdMustBeChecked . +/// \arg \c Fn Name of the (standard) function that is modeled. +const NoteTag *getNoteTagForStdMustBeChecked(CheckerContext &C, + llvm::StringRef Fn); + } // namespace errno_modeling } // namespace ento } // namespace clang diff --git a/clang/lib/StaticAnalyzer/Checkers/ErrnoModeling.cpp b/clang/lib/StaticAnalyzer/Checkers/ErrnoModeling.cpp --- a/clang/lib/StaticAnalyzer/Checkers/ErrnoModeling.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/ErrnoModeling.cpp @@ -246,12 +246,16 @@ return loc::MemRegionVal{ErrnoR}; } +ErrnoCheckState getErrnoState(ProgramStateRef State) { + return State->get(); +} + ProgramStateRef setErrnoState(ProgramStateRef State, ErrnoCheckState EState) { return State->set(EState); } -ErrnoCheckState getErrnoState(ProgramStateRef State) { - return State->get(); +ProgramStateRef clearErrnoState(ProgramStateRef State) { + return setErrnoState(State, Irrelevant); } bool isErrno(const Decl *D) { @@ -299,6 +303,19 @@ return setErrnoValue(State, C.getLocationContext(), ErrnoSym, Irrelevant); } +ProgramStateRef setErrnoStdMustBeChecked(ProgramStateRef State, + CheckerContext &C, + const Expr *InvalE) { + const MemRegion *ErrnoR = State->get(); + if (!ErrnoR) + return State; + State = State->invalidateRegions(ErrnoR, InvalE, C.blockCount(), + C.getLocationContext(), false); + if (!State) + return nullptr; + return setErrnoState(State, MustBeChecked); +} + const NoteTag *getNoteTagForStdSuccess(CheckerContext &C, llvm::StringRef Fn) { return getErrnoNoteTag( C, (Twine("Assuming that function '") + Twine(Fn) + @@ -307,6 +324,14 @@ .str()); } +const NoteTag *getNoteTagForStdMustBeChecked(CheckerContext &C, + llvm::StringRef Fn) { + return getErrnoNoteTag( + C, (Twine("Function '") + Twine(Fn) + + Twine("' indicates failure only by setting of 'errno'")) + .str()); +} + } // namespace errno_modeling } // namespace ento } // namespace clang diff --git a/clang/lib/StaticAnalyzer/Checkers/StdLibraryFunctionsChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/StdLibraryFunctionsChecker.cpp --- a/clang/lib/StaticAnalyzer/Checkers/StdLibraryFunctionsChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/StdLibraryFunctionsChecker.cpp @@ -254,6 +254,8 @@ bool CannotBeNull = true; public: + NotNullConstraint(ArgNo ArgN, bool CannotBeNull = true) + : ValueConstraint(ArgN), CannotBeNull(CannotBeNull) {} std::string describe(DescriptionKind DK, ProgramStateRef State, const Summary &Summary) const override; StringRef getName() const override { return "NonNull"; } @@ -419,6 +421,31 @@ static int Tag; }; + /// Reset errno constraints to irrelevant. + /// This is applicable to functions that may change 'errno' and are not + /// modeled elsewhere. + class ResetErrnoConstraint : public ErrnoConstraintBase { + public: + ProgramStateRef apply(ProgramStateRef State, const CallEvent &Call, + const Summary &Summary, + CheckerContext &C) const override { + return errno_modeling::setErrnoState(State, errno_modeling::Irrelevant); + } + }; + + /// Do not change errno constraints. + /// This is applicable to functions that are modeled in another checker + /// and the already set errno constraints should not be changed in the + /// post-call event. + class NoErrnoConstraint : public ErrnoConstraintBase { + public: + ProgramStateRef apply(ProgramStateRef State, const CallEvent &Call, + const Summary &Summary, + CheckerContext &C) const override { + return State; + } + }; + /// Set errno constraint at failure cases of standard functions. /// Failure case: 'errno' becomes not equal to 0 and may or may not be checked /// by the program. \c ErrnoChecker does not emit a bug report after such a @@ -456,14 +483,18 @@ } }; - /// Set errno constraints if use of 'errno' is irrelevant to the - /// modeled function or modeling is not possible. - class NoErrnoConstraint : public ErrnoConstraintBase { + class ErrnoMustBeCheckedConstraint : public ErrnoConstraintBase { public: ProgramStateRef apply(ProgramStateRef State, const CallEvent &Call, const Summary &Summary, CheckerContext &C) const override { - return errno_modeling::setErrnoState(State, errno_modeling::Irrelevant); + return errno_modeling::setErrnoStdMustBeChecked(State, C, + Call.getOriginExpr()); + } + + const NoteTag *describe(CheckerContext &C, + StringRef FunctionName) const override { + return errno_modeling::getNoteTagForStdMustBeChecked(C, FunctionName); } }; @@ -729,7 +760,9 @@ /// Usually if a failure return value exists for function, that function /// needs different cases for success and failure with different errno /// constraints (and different return value constraints). - const NoErrnoConstraint ErrnoIrrelevant{}; + const NoErrnoConstraint ErrnoUnchanged{}; + const ResetErrnoConstraint ErrnoIrrelevant{}; + const ErrnoMustBeCheckedConstraint ErrnoMustBeChecked{}; const SuccessErrnoConstraint ErrnoMustNotBeChecked{}; const FailureErrnoConstraint ErrnoNEZeroIrrelevant{}; }; @@ -1019,6 +1052,16 @@ /*IsPrunable=*/true); C.addTransition(NewState, Tag); } + } else if (NewState == State) { + // It is possible that the function was evaluated in a checker callback + // where the state constraints are already applied, then no change happens + // here to the state (if the ErrnoConstraint did not change it either). + // If the evaluated function requires a NoteTag for errno change, it is + // added here. + if (const auto *D = dyn_cast_or_null(Call.getDecl())) + if (const NoteTag *NT = + Case.getErrnoConstraint().describe(C, D->getNameAsString())) + C.addTransition(NewState, NT); } } } @@ -1353,11 +1396,19 @@ auto NotNull = [&](ArgNo ArgN) { return std::make_shared(ArgN); }; + auto IsNull = [&](ArgNo ArgN) { + return std::make_shared(ArgN, false); + }; Optional FileTy = lookupTy("FILE"); Optional FilePtrTy = getPointerTy(FileTy); Optional FilePtrRestrictTy = getRestrictTy(FilePtrTy); + Optional FPosTTy = lookupTy("fpos_t"); + Optional FPosTPtrTy = getPointerTy(FPosTTy); + Optional ConstFPosTPtrTy = getPointerTy(getConstTy(FPosTTy)); + Optional FPosTPtrRestrictTy = getRestrictTy(FPosTPtrTy); + // We are finally ready to define specifications for all supported functions. // // Argument ranges should always cover all variants. If return value @@ -1582,11 +1633,23 @@ // read()-like functions that never return more than buffer size. auto FreadSummary = Summary(NoEvalCall) - .Case({ReturnValueCondition(LessThanOrEq, ArgNo(2)), + .Case({ArgumentCondition(1U, WithinRange, Range(1, SizeMax)), + ArgumentCondition(2U, WithinRange, Range(1, SizeMax)), + ReturnValueCondition(BO_LT, ArgNo(2)), ReturnValueCondition(WithinRange, Range(0, SizeMax))}, - ErrnoIrrelevant) + ErrnoNEZeroIrrelevant) + .Case({ArgumentCondition(1U, WithinRange, Range(1, SizeMax)), + ReturnValueCondition(BO_EQ, ArgNo(2)), + ReturnValueCondition(WithinRange, Range(0, SizeMax))}, + ErrnoMustNotBeChecked) + .Case({ArgumentCondition(1U, WithinRange, SingleValue(0)), + ReturnValueCondition(WithinRange, SingleValue(0))}, + ErrnoMustNotBeChecked) .ArgConstraint(NotNull(ArgNo(0))) .ArgConstraint(NotNull(ArgNo(3))) + // FIXME: It should be allowed to have a null buffer if any of + // args 1 or 2 are zero. Remove NotNull check of arg 0, add a check + // for non-null buffer if non-zero size to BufferSizeConstraint? .ArgConstraint(BufferSize(/*Buffer=*/ArgNo(0), /*BufSize=*/ArgNo(1), /*BufSizeMultiplier=*/ArgNo(2))); @@ -1672,6 +1735,142 @@ } if (ModelPOSIX) { + const auto ReturnsZeroOrMinusOne = + ConstraintSet{ReturnValueCondition(WithinRange, Range(-1, 0))}; + const auto ReturnsZero = + ConstraintSet{ReturnValueCondition(WithinRange, SingleValue(0))}; + const auto ReturnsMinusOne = + ConstraintSet{ReturnValueCondition(WithinRange, SingleValue(-1))}; + const auto ReturnsNonnegative = + ConstraintSet{ReturnValueCondition(WithinRange, Range(0, IntMax))}; + const auto ReturnsNonZero = + ConstraintSet{ReturnValueCondition(OutOfRange, SingleValue(0))}; + const auto ReturnsFileDescriptor = + ConstraintSet{ReturnValueCondition(WithinRange, Range(-1, IntMax))}; + const auto &ReturnsValidFileDescriptor = ReturnsNonnegative; + + // FILE *fopen(const char *restrict pathname, const char *restrict mode); + addToFunctionSummaryMap( + "fopen", + Signature(ArgTypes{ConstCharPtrRestrictTy, ConstCharPtrRestrictTy}, + RetType{FilePtrTy}), + Summary(NoEvalCall) + .Case({NotNull(Ret)}, ErrnoMustNotBeChecked) + .Case({IsNull(Ret)}, ErrnoNEZeroIrrelevant) + .ArgConstraint(NotNull(ArgNo(0))) + .ArgConstraint(NotNull(ArgNo(1)))); + + // FILE *tmpfile(void); + addToFunctionSummaryMap("tmpfile", + Signature(ArgTypes{}, RetType{FilePtrTy}), + Summary(NoEvalCall) + .Case({NotNull(Ret)}, ErrnoMustNotBeChecked) + .Case({IsNull(Ret)}, ErrnoNEZeroIrrelevant)); + + // FILE *freopen(const char *restrict pathname, const char *restrict mode, + // FILE *restrict stream); + addToFunctionSummaryMap( + "freopen", + Signature(ArgTypes{ConstCharPtrRestrictTy, ConstCharPtrRestrictTy, + FilePtrRestrictTy}, + RetType{FilePtrTy}), + Summary(NoEvalCall) + .Case({ReturnValueCondition(BO_EQ, ArgNo(2))}, + ErrnoMustNotBeChecked) + .Case({IsNull(Ret)}, ErrnoNEZeroIrrelevant) + .ArgConstraint(NotNull(ArgNo(1))) + .ArgConstraint(NotNull(ArgNo(2)))); + + // int fclose(FILE *stream); + addToFunctionSummaryMap( + "fclose", Signature(ArgTypes{FilePtrTy}, RetType{IntTy}), + Summary(NoEvalCall) + .Case(ReturnsZero, ErrnoMustNotBeChecked) + .Case({ReturnValueCondition(WithinRange, SingleValue(EOFv))}, + ErrnoNEZeroIrrelevant) + .ArgConstraint(NotNull(ArgNo(0)))); + + // int fseek(FILE *stream, long offset, int whence); + // FIXME: It can be possible to get the 'SEEK_' values (like EOFv) and use + // these for condition of arg 2. + // Now the range [0,2] is used (the `SEEK_*` constants are usually 0,1,2). + addToFunctionSummaryMap( + "fseek", Signature(ArgTypes{FilePtrTy, LongTy, IntTy}, RetType{IntTy}), + Summary(NoEvalCall) + .Case(ReturnsZero, ErrnoMustNotBeChecked) + .Case(ReturnsMinusOne, ErrnoNEZeroIrrelevant) + .ArgConstraint(NotNull(ArgNo(0))) + .ArgConstraint(ArgumentCondition(2, WithinRange, {{0, 2}}))); + + // int fgetpos(FILE *restrict stream, fpos_t *restrict pos); + // From 'The Open Group Base Specifications Issue 7, 2018 edition': + // "The fgetpos() function shall not change the setting of errno if + // successful." + addToFunctionSummaryMap( + "fgetpos", + Signature(ArgTypes{FilePtrRestrictTy, FPosTPtrRestrictTy}, + RetType{IntTy}), + Summary(NoEvalCall) + .Case(ReturnsZero, ErrnoUnchanged) + .Case(ReturnsNonZero, ErrnoNEZeroIrrelevant) + .ArgConstraint(NotNull(ArgNo(0))) + .ArgConstraint(NotNull(ArgNo(1)))); + + // int fsetpos(FILE *stream, const fpos_t *pos); + // From 'The Open Group Base Specifications Issue 7, 2018 edition': + // "The fsetpos() function shall not change the setting of errno if + // successful." + addToFunctionSummaryMap( + "fsetpos", + Signature(ArgTypes{FilePtrTy, ConstFPosTPtrTy}, RetType{IntTy}), + Summary(NoEvalCall) + .Case(ReturnsZero, ErrnoUnchanged) + .Case(ReturnsNonZero, ErrnoNEZeroIrrelevant) + .ArgConstraint(NotNull(ArgNo(0))) + .ArgConstraint(NotNull(ArgNo(1)))); + + // long ftell(FILE *stream); + // From 'The Open Group Base Specifications Issue 7, 2018 edition': + // "The ftell() function shall not change the setting of errno if + // successful." + addToFunctionSummaryMap( + "ftell", Signature(ArgTypes{FilePtrTy}, RetType{LongTy}), + Summary(NoEvalCall) + .Case({ReturnValueCondition(WithinRange, Range(1, LongMax))}, + ErrnoUnchanged) + .Case(ReturnsMinusOne, ErrnoNEZeroIrrelevant) + .ArgConstraint(NotNull(ArgNo(0)))); + + // int fileno(FILE *stream); + addToFunctionSummaryMap( + "fileno", Signature(ArgTypes{FilePtrTy}, RetType{IntTy}), + Summary(NoEvalCall) + .Case(ReturnsValidFileDescriptor, ErrnoMustNotBeChecked) + .Case(ReturnsMinusOne, ErrnoNEZeroIrrelevant) + .ArgConstraint(NotNull(ArgNo(0)))); + + // void rewind(FILE *stream); + // This function indicates error only by setting of 'errno'. + addToFunctionSummaryMap("rewind", + Signature(ArgTypes{FilePtrTy}, RetType{VoidTy}), + Summary(NoEvalCall) + .Case({}, ErrnoMustBeChecked) + .ArgConstraint(NotNull(ArgNo(0)))); + + // void clearerr(FILE *stream); + addToFunctionSummaryMap( + "clearerr", Signature(ArgTypes{FilePtrTy}, RetType{VoidTy}), + Summary(NoEvalCall).ArgConstraint(NotNull(ArgNo(0)))); + + // int feof(FILE *stream); + addToFunctionSummaryMap( + "feof", Signature(ArgTypes{FilePtrTy}, RetType{IntTy}), + Summary(NoEvalCall).ArgConstraint(NotNull(ArgNo(0)))); + + // int ferror(FILE *stream); + addToFunctionSummaryMap( + "ferror", Signature(ArgTypes{FilePtrTy}, RetType{IntTy}), + Summary(NoEvalCall).ArgConstraint(NotNull(ArgNo(0)))); // long a64l(const char *str64); addToFunctionSummaryMap( @@ -1685,18 +1884,6 @@ .ArgConstraint(ArgumentCondition( 0, WithinRange, Range(0, LongMax)))); - const auto ReturnsZeroOrMinusOne = - ConstraintSet{ReturnValueCondition(WithinRange, Range(-1, 0))}; - const auto ReturnsZero = - ConstraintSet{ReturnValueCondition(WithinRange, SingleValue(0))}; - const auto ReturnsMinusOne = - ConstraintSet{ReturnValueCondition(WithinRange, SingleValue(-1))}; - const auto ReturnsNonnegative = - ConstraintSet{ReturnValueCondition(WithinRange, Range(0, IntMax))}; - const auto ReturnsFileDescriptor = - ConstraintSet{ReturnValueCondition(WithinRange, Range(-1, IntMax))}; - const auto &ReturnsValidFileDescriptor = ReturnsNonnegative; - // int access(const char *pathname, int amode); addToFunctionSummaryMap( "access", Signature(ArgTypes{ConstCharPtrTy, IntTy}, RetType{IntTy}), @@ -2183,14 +2370,6 @@ "rand_r", Signature(ArgTypes{UnsignedIntPtrTy}, RetType{IntTy}), Summary(NoEvalCall).ArgConstraint(NotNull(ArgNo(0)))); - // int fileno(FILE *stream); - addToFunctionSummaryMap( - "fileno", Signature(ArgTypes{FilePtrTy}, RetType{IntTy}), - Summary(NoEvalCall) - .Case(ReturnsValidFileDescriptor, ErrnoMustNotBeChecked) - .Case(ReturnsMinusOne, ErrnoNEZeroIrrelevant) - .ArgConstraint(NotNull(ArgNo(0)))); - // int fseeko(FILE *stream, off_t offset, int whence); addToFunctionSummaryMap( "fseeko", @@ -2983,6 +3162,15 @@ "__test_restrict_param_2"}, Signature(ArgTypes{VoidPtrRestrictTy}, RetType{VoidTy}), Summary(EvalCallAsPure)); + + // Test the application of cases. + addToFunctionSummaryMap( + "__test_case_note", Signature(ArgTypes{}, RetType{IntTy}), + Summary(EvalCallAsPure) + .Case({ReturnValueCondition(WithinRange, SingleValue(0))}, + ErrnoIrrelevant, "Function returns 0") + .Case({ReturnValueCondition(WithinRange, SingleValue(1))}, + ErrnoIrrelevant, "Function returns 1")); } SummariesInitialized = true; diff --git a/clang/test/Analysis/std-c-library-functions-POSIX.c b/clang/test/Analysis/std-c-library-functions-POSIX.c --- a/clang/test/Analysis/std-c-library-functions-POSIX.c +++ b/clang/test/Analysis/std-c-library-functions-POSIX.c @@ -7,6 +7,12 @@ // RUN: -analyzer-config eagerly-assume=false \ // RUN: -triple i686-unknown-linux 2>&1 | FileCheck %s +// CHECK: Loaded summary for: FILE *fopen(const char *restrict pathname, const char *restrict mode) +// CHECK: Loaded summary for: FILE *tmpfile(void) +// CHECK: Loaded summary for: FILE *freopen(const char *restrict pathname, const char *restrict mode, FILE *restrict stream) +// CHECK: Loaded summary for: int fclose(FILE *stream) +// CHECK: Loaded summary for: int fseek(FILE *stream, long offset, int whence) +// CHECK: Loaded summary for: int fileno(FILE *stream) // CHECK: Loaded summary for: long a64l(const char *str64) // CHECK: Loaded summary for: char *l64a(long value) // CHECK: Loaded summary for: int access(const char *pathname, int amode) @@ -63,7 +69,6 @@ // CHECK: Loaded summary for: void rewinddir(DIR *dir) // CHECK: Loaded summary for: void seekdir(DIR *dirp, long loc) // CHECK: Loaded summary for: int rand_r(unsigned int *seedp) -// CHECK: Loaded summary for: int fileno(FILE *stream) // CHECK: Loaded summary for: int fseeko(FILE *stream, off_t offset, int whence) // CHECK: Loaded summary for: off_t ftello(FILE *stream) // CHECK: Loaded summary for: void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset) @@ -121,6 +126,16 @@ // CHECK: Loaded summary for: int pthread_mutex_trylock(pthread_mutex_t *mutex) // CHECK: Loaded summary for: int pthread_mutex_unlock(pthread_mutex_t *mutex) +typedef struct { + int x; +} FILE; +FILE *fopen(const char *restrict pathname, const char *restrict mode); +FILE *tmpfile(void); +FILE *freopen(const char *restrict pathname, const char *restrict mode, + FILE *restrict stream); +int fclose(FILE *stream); +int fseek(FILE *stream, long offset, int whence); +int fileno(FILE *stream); long a64l(const char *str64); char *l64a(long value); int access(const char *pathname, int amode); @@ -181,9 +196,6 @@ DIR *opendir(const char *name); DIR *fdopendir(int fd); int isatty(int fildes); -typedef struct { - int x; -} FILE; FILE *popen(const char *command, const char *type); int pclose(FILE *stream); int close(int fildes); diff --git a/clang/test/Analysis/std-c-library-functions-arg-constraints-note-tags.cpp b/clang/test/Analysis/std-c-library-functions-arg-constraints-note-tags.cpp --- a/clang/test/Analysis/std-c-library-functions-arg-constraints-note-tags.cpp +++ b/clang/test/Analysis/std-c-library-functions-arg-constraints-note-tags.cpp @@ -49,3 +49,18 @@ clang_analyzer_express(buf); // expected-warning {{}} // the message does not really matter \ // expected-note {{}} } + +int __test_case_note(); + +int test_case_note_1(int y) { + int x = __test_case_note(); // expected-note{{Function returns 0}} \ + // expected-note{{'x' initialized here}} + return y / x; // expected-warning{{Division by zero}} \ + // expected-note{{Division by zero}} +} + +int test_case_note_2(int y) { + int x = __test_case_note(); // expected-note{{Function returns 1}} + return y / (x - 1); // expected-warning{{Division by zero}} \ + // expected-note{{Division by zero}} +} diff --git a/clang/test/Analysis/std-c-library-functions-vs-stream-checker.c b/clang/test/Analysis/std-c-library-functions-vs-stream-checker.c --- a/clang/test/Analysis/std-c-library-functions-vs-stream-checker.c +++ b/clang/test/Analysis/std-c-library-functions-vs-stream-checker.c @@ -45,12 +45,13 @@ clang_analyzer_eval(x <= 10); // \ // stream-warning{{TRUE}} \ // stdLib-warning{{TRUE}} \ - // both-warning{{TRUE}} \ + // both-warning{{TRUE}} clang_analyzer_eval(x == 10); // \ // stream-warning{{TRUE}} \ // stream-warning{{FALSE}} \ - // stdLib-warning{{UNKNOWN}} \ + // stdLib-warning{{TRUE}} \ + // stdLib-warning{{FALSE}} \ // both-warning{{TRUE}} \ // both-warning{{FALSE}}