Index: include/clang/StaticAnalyzer/Checkers/Checkers.td =================================================================== --- include/clang/StaticAnalyzer/Checkers/Checkers.td +++ include/clang/StaticAnalyzer/Checkers/Checkers.td @@ -522,6 +522,10 @@ HelpText<"Check for calls to blocking functions inside a critical section">, DescFile<"BlockInCriticalSectionChecker.cpp">; +def TimeChecker : Checker<"Time">, + HelpText<"Check time.h related functionality">, + DescFile<"TimeChecker.cpp">; + } // end "alpha.unix" let ParentPackage = CString in { Index: lib/StaticAnalyzer/Checkers/CMakeLists.txt =================================================================== --- lib/StaticAnalyzer/Checkers/CMakeLists.txt +++ lib/StaticAnalyzer/Checkers/CMakeLists.txt @@ -87,6 +87,7 @@ StreamChecker.cpp TaintTesterChecker.cpp TestAfterDivZeroChecker.cpp + TimeChecker.cpp TraversalChecker.cpp TrustNonnullChecker.cpp UndefBranchChecker.cpp Index: lib/StaticAnalyzer/Checkers/TimeChecker.cpp =================================================================== --- /dev/null +++ lib/StaticAnalyzer/Checkers/TimeChecker.cpp @@ -0,0 +1,224 @@ +//===-- TimeChecker.cpp -------------------------------------------*- C++ -*--// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Defines a checker for proper definition/use of time.h related functionality. +// +// Reference used: +// +// ISO/IEC 9899:TC3 Committee Draft - September 7, 2007 WG14/N1256 +// http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1256.pdf +// Section 7.23 Date and time +// +// The intention for this work is to audit user code for identifying potential +// issues in the light of the "Year 2038" (aka: "Y2K38") problem. +// +// Checks implemented: +// +// 0. Warn if the time_t type is not defined to be a signed integer type, +// at-least 64-bits in size, having file scope. (When the size of the time_t +// type is less than 64-bits, report the actual size in the warning). +// +// 1. Warn when a time_t value is cast to: +// a. A non-integer type +// b. An integer type of lesser width +// c. An unsigned integer type +// +//===----------------------------------------------------------------------===// + +#include "ClangSACheckers.h" +#include "clang/AST/RecursiveASTVisitor.h" +#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" +#include "clang/StaticAnalyzer/Core/Checker.h" +#include "clang/StaticAnalyzer/Core/CheckerManager.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" + +using namespace clang; +using namespace ento; + +namespace { +class TimeVisitor : public RecursiveASTVisitor { + BugReporter &BR; + const CheckerBase *Checker; + AnalysisDeclContext *AC; + + bool hasTimeTypeSynonym(QualType T); + +public: + explicit TimeVisitor(BugReporter &B, const CheckerBase *Checker, + AnalysisDeclContext *A) + : BR(B), Checker(Checker), AC(A) {} + + bool VisitCastExpr(const CastExpr *CE); + bool VisitTypedefNameDecl(const TypedefNameDecl *TD); +}; +} // namespace + +/// Peel through layers of typedefs, to check if there's a time_t type in there +/// somewhere. +bool TimeVisitor::hasTimeTypeSynonym(QualType T) { + + const Type *TPtr = nullptr; + const TypedefType *TDPtr = nullptr; + + while ((TPtr = T.getTypePtr()) && (TDPtr = dyn_cast(TPtr)) && + TDPtr->isSugared()) { + + if (T.getBaseTypeIdentifier() && + (T.getBaseTypeIdentifier()->getName().str() == "time_t")) + return true; + else + T = TDPtr->desugar(); + } + + return false; +} + +/// Audit cast-expressions on a time_t value. +bool TimeVisitor::VisitCastExpr(const CastExpr *CE) { + + if (!CE) + return true; + + ASTContext &Ctx = AC->getASTContext(); + const Expr *E = CE->getSubExpr(); + + if (!E) + return true; + + QualType ToTy = Ctx.getCanonicalType(CE->getType()); + QualType OrigTy = Ctx.getCanonicalType(E->getType()); + + if (!OrigTy.getTypePtr() || !ToTy.getTypePtr()) + return true; + + if (!OrigTy.getTypePtr()->isIntegerType()) + return true; + + if (hasTimeTypeSynonym(E->getType())) { + + if (ToTy.getTypePtr()->hasIntegerRepresentation()) { + + if (Ctx.getIntWidth(CE->getType()) < Ctx.getIntWidth(E->getType())) { + + SmallString<128> buf; + llvm::raw_svector_ostream output(buf); + + output << "Y2K38 Issue: "; + output << (dyn_cast(CE) ? "Explicit" : "Implicit"); + output << " cast of time_t value to a type of lesser width"; + + SourceRange Sr[1] = {CE->getSourceRange()}; + PathDiagnosticLocation Loc(CE, BR.getSourceManager(), AC); + BR.EmitBasicReport( + AC->getDecl(), Checker, + "Y2K38 Issue: Cast of time_t value to a type of lesser width", + categories::LogicError, output.str(), Loc, Sr); + } + + if (!ToTy.getTypePtr()->hasSignedIntegerRepresentation()) { + + SmallString<128> buf; + llvm::raw_svector_ostream output(buf); + + output << "Y2K38 Issue: "; + output << (dyn_cast(CE) ? "Explicit" : "Implicit"); + output << " cast of time_t value to an unsigned type"; + + SourceRange Sr[1] = {E->getSourceRange()}; + PathDiagnosticLocation Loc(E, BR.getSourceManager(), AC); + BR.EmitBasicReport( + AC->getDecl(), Checker, + "Y2K38 Issue: Cast of time_t value to an unsigned type", + categories::LogicError, output.str(), Loc, Sr); + } + } else { + + SmallString<128> buf; + llvm::raw_svector_ostream output(buf); + + output << "Y2K38 Issue: "; + output << (dyn_cast(CE) ? "Explicit" : "Implicit"); + output << " cast of time_t value to a non-integer type"; + + SourceRange Sr[1] = {E->getSourceRange()}; + PathDiagnosticLocation Loc(E, BR.getSourceManager(), AC); + BR.EmitBasicReport( + AC->getDecl(), Checker, + "Y2K38 Issue: Cast of time_t value to a non-integer type", + categories::LogicError, output.str(), Loc, Sr); + } + } + + return true; +} + +/// Audit typedef's on the time_t type. +bool TimeVisitor::VisitTypedefNameDecl(const TypedefNameDecl *TD) { + + if (!TD) + return true; + + ASTContext &Ctx = AC->getASTContext(); + + if (TD->getName().str() == "time_t") { + + QualType T = TD->getUnderlyingType(); + if (!T.getTypePtr()->isIntegerType() || + !T.getTypePtr()->hasSignedIntegerRepresentation() || + Ctx.getIntWidth(T) < 64 /* bits */ || + TD->isLexicallyWithinFunctionOrMethod()) { + SourceRange Sr[1] = {TD->getSourceRange()}; + PathDiagnosticLocation Loc(TD, BR.getSourceManager()); + BR.EmitBasicReport( + AC->getDecl(), Checker, "Y2K38 Issue: time_t incorrectly defined", + categories::LogicError, + "Y2K38 Issue: time_t expected to be a signed " + "integer type, at-least 64-bits in size, having file scope", + Loc, Sr); + } + if (TD->isLexicallyWithinFunctionOrMethod()) { + SourceRange Sr[1] = {TD->getSourceRange()}; + PathDiagnosticLocation Loc(TD, BR.getSourceManager()); + BR.EmitBasicReport( + AC->getDecl(), Checker, "Y2K38 Issue: time_t incorrectly defined", + categories::LogicError, + "Y2K38 Issue: time_t declaration is local to function", Loc, Sr); + } + if (Ctx.getIntWidth(T) < 64 /* bits */) { + + SmallString<128> buf; + llvm::raw_svector_ostream output(buf); + + output << "Y2K38 Issue: time_t size is " << Ctx.getIntWidth(T) << " bits"; + + SourceRange Sr[1] = {TD->getSourceRange()}; + PathDiagnosticLocation Loc(TD, BR.getSourceManager()); + BR.EmitBasicReport(AC->getDecl(), Checker, + "Y2K38 Issue: time_t incorrectly defined", + categories::LogicError, output.str(), Loc, Sr); + } + } + + return true; +} + +namespace { +class TimeChecker : public Checker> { +public: + void checkASTDecl(const Decl *D, AnalysisManager &Mgr, + BugReporter &BR) const { + TimeVisitor Visitor(BR, this, Mgr.getAnalysisDeclContext(D)); + Visitor.TraverseDecl(const_cast(D)); + } +}; +} // end anonymous namespace + +void ento::registerTimeChecker(CheckerManager &mgr) { + mgr.registerChecker(); +} Index: test/Analysis/TimeChecker-0.c =================================================================== --- /dev/null +++ test/Analysis/TimeChecker-0.c @@ -0,0 +1,3 @@ +// RUN: %clang_cc1 -analyzer-checker-help | FileCheck %s + +CHECK: alpha.unix.Time Check time.h related functionality Index: test/Analysis/TimeChecker-1.c =================================================================== --- /dev/null +++ test/Analysis/TimeChecker-1.c @@ -0,0 +1,216 @@ +// RUN: %clang --analyze -Xclang -analyzer-checker=alpha.unix.Time -Xclang -verify %s + +typedef signed char int8_t; + +typedef long int __time_t; +typedef __time_t time_t; +// TODO: EXPECTED-WARNING-RE@9 {{Y2K38 Issue: The type 'time_t' is defined in file '{{(.*)}}/TimeChecker-1.c' which has a non-standard name; expected to see type 'time_t' defined in standard header named: 'time.h'}} + +int8_t ImplicitCastOnVar(void) { + + time_t t = 0; + + return t; // expected-warning {{Y2K38 Issue: Implicit cast of time_t value to a type of lesser width}} +} + +int8_t ImplicitCastOnVal(void) { + + return ((time_t) 0); // expected-warning {{Y2K38 Issue: Implicit cast of time_t value to a type of lesser width}} +} + +_Bool Bad10E(void) { + + time_t t = 0; + + return (_Bool) t; + // expected-warning@25 {{Y2K38 Issue: Explicit cast of time_t value to a type of lesser width}} + // expected-warning@25 {{Y2K38 Issue: Explicit cast of time_t value to an unsigned type}} +} + +_Bool Bad10I(void) { + + time_t t = 0; + + return t; + // expected-warning@34 {{Y2K38 Issue: Implicit cast of time_t value to a type of lesser width}} + // expected-warning@34 {{Y2K38 Issue: Implicit cast of time_t value to an unsigned type}} +} + +struct tm { + char t; +}; + +extern time_t mktime(struct tm *tm); + +char *Bad11E(void) { + return (char *) mktime(0); // expected-warning {{Y2K38 Issue: Explicit cast of time_t value to a non-integer type}} +} + +char *Bad11I(void) { + return mktime(0); // expected-warning {{Y2K38 Issue: Implicit cast of time_t value to a non-integer type}} +} + +typedef time_t time_t_0; +typedef time_t_0 time_t_1; +typedef time_t_1 time_t_2; +typedef time_t_2 time_t_3; +typedef time_t_3 time_t_4; +typedef time_t_4 time_t_5; +typedef time_t_5 time_t_6; +typedef time_t_6 time_t_7; + +char Bad12E(void) { + + time_t_7 t = 0; + + return (char) t; // expected-warning {{Y2K38 Issue: Explicit cast of time_t value to a type of lesser width}} +} + +char Bad12I(void) { + + time_t_7 t = 0; + + return t; // expected-warning {{Y2K38 Issue: Implicit cast of time_t value to a type of lesser width}} +} + +struct foo13 { + + time_t t; + +}; + +char *Bad13(void) { + + struct foo13 FOO; + + FOO.t = 0x0; + + return (char *) FOO.t; // expected-warning {{Y2K38 Issue: Explicit cast of time_t value to a non-integer type}} +} + +char *Bad14E(void) { + + time_t t = 0; + + return (char *) t; // expected-warning {{Y2K38 Issue: Explicit cast of time_t value to a non-integer type}} +} + +char *Bad14I(void) { + + time_t t = 0; + + return t; // expected-warning {{Y2K38 Issue: Implicit cast of time_t value to a non-integer type}} +} + +int Bad21E(void) { + + time_t t = 0; + int n = 0; + + n = n + (int) t; // expected-warning {{Y2K38 Issue: Explicit cast of time_t value to a type of lesser width}} + + return n; +} + +int Bad21I(void) { + + time_t t = 0; + int n = 0; + + n = n + t; // TODO: Do we catch this?! n gets promoted to a long; + // then, the result of the long addition is downcast to int + + return n; +} + +extern int gorp(int x); + +int Bad22(void) { + + time_t t = 0; + + gorp(t); // expected-warning {{Y2K38 Issue: Implicit cast of time_t value to a type of lesser width}} + return 0; +} + +int8_t f4(void) { + + time_t t = 0; + int8_t c = 0; + + c = (int8_t) t; // expected-warning {{Y2K38 Issue: Explicit cast of time_t value to a type of lesser width}} + + return c; +} + +typedef char A2C[2]; + +void Bad5(void) { + + time_t t = 0; + A2C a2c = { 0, 0 }; + + a2c[0] = t; // expected-warning {{Y2K38 Issue: Implicit cast of time_t value to a type of lesser width}} + a2c[1] = (char) t; // expected-warning {{Y2K38 Issue: Explicit cast of time_t value to a type of lesser width}} +} + +float Bad6E(void) { + + time_t t = 0; + float f; + + f = (float) t; // expected-warning {{Y2K38 Issue: Explicit cast of time_t value to a non-integer type}} + + return f; +} + +float Bad6I(void) { + + time_t t = 0; + float f; + + f = t; // expected-warning {{Y2K38 Issue: Implicit cast of time_t value to a non-integer type}} + + return f; +} + +double Bad7E(void) { + + time_t t = 0; + double f; + + f = (double) t; // expected-warning {{Y2K38 Issue: Explicit cast of time_t value to a non-integer type}} + + return f; +} + +double Bad7I(void) { + + time_t t = 0; + double f; + + f = t; // expected-warning {{Y2K38 Issue: Implicit cast of time_t value to a non-integer type}} + + return f; +} + +unsigned long int Bad8E(void) { + + time_t t = 0; + + return (unsigned long int) t; // expected-warning {{Y2K38 Issue: Explicit cast of time_t value to an unsigned type}} +} + +unsigned long int Bad8I(void) { + + time_t t = 0; + + return t; // expected-warning {{Y2K38 Issue: Implicit cast of time_t value to an unsigned type}} +} + +void Bad9(void) { + + time_t t = 0; + + return (void) t; // expected-warning {{Y2K38 Issue: Explicit cast of time_t value to a non-integer type}} +} Index: test/Analysis/TimeChecker-10.c =================================================================== --- /dev/null +++ test/Analysis/TimeChecker-10.c @@ -0,0 +1,8 @@ +// RUN: %clang --analyze -Xclang -analyzer-checker=alpha.unix.Time -Xclang -verify %s + +typedef signed char int8_t; +typedef struct time_t_ { + int8_t t[8]; +} time_t; +// expected-warning@6 {{Y2K38 Issue: time_t expected to be a signed integer type, at-least 64-bits in size}} +// TODO: EXPECTED-WARNING-RE@7 {{Y2K38 Issue: The type 'time_t' is defined in file '{{(.*)}}/TimeChecker-10.c' which has a non-standard name; expected to see type 'time_t' defined in standard header named: 'time.h'}} Index: test/Analysis/TimeChecker-11.c =================================================================== --- /dev/null +++ test/Analysis/TimeChecker-11.c @@ -0,0 +1,12 @@ +// RUN: %clang --analyze -Xclang -analyzer-checker=alpha.unix.Time -Xclang -verify %s + +int unwise(void) { + + typedef long int __time_t; + typedef __time_t time_t; + // TODO: EXPECTED-WARNING-RE@8 {{Y2K38 Issue: The type 'time_t' is defined in file '{{(.*)}}/TimeChecker-11.c' which has a non-standard name; expected to see type 'time_t' defined in standard header named: 'time.h'}} + // expected-warning@6 {{Y2K38 Issue: time_t declaration is local to function}} + // expected-warning@6 {{Y2K38 Issue: time_t expected to be a signed integer type, at-least 64-bits in size, having file scope}} + + return 0; +} Index: test/Analysis/TimeChecker-12.c =================================================================== --- /dev/null +++ test/Analysis/TimeChecker-12.c @@ -0,0 +1,10 @@ +// RUN: %clang --analyze -Xclang -analyzer-checker=alpha.unix.Time -Xclang -verify %s + +#include + +extern int wrap(int n); + +int test(void) { + + return wrap(time(NULL)); // expected-warning {{Y2K38 Issue: Implicit cast of time_t value to a type of lesser width}} +} Index: test/Analysis/TimeChecker-2.c =================================================================== --- /dev/null +++ test/Analysis/TimeChecker-2.c @@ -0,0 +1,7 @@ +// RUN: %clang --analyze -Xclang -analyzer-checker=alpha.unix.Time -Xclang -verify %s + +typedef unsigned char __time_t; +typedef __time_t time_t; +// expected-warning@4 {{Y2K38 Issue: time_t expected to be a signed integer type, at-least 64-bits in size, having file scope}} +// expected-warning@4 {{Y2K38 Issue: time_t size is 8 bits}} +// TODO: EXPECTED-WARNING-RE@4 {{Y2K38 Issue: The type 'time_t' is defined in file '{{(.*)}}/TimeChecker-2.c' which has a non-standard name; expected to see type 'time_t' defined in standard header named: 'time.h'}} Index: test/Analysis/TimeChecker-3.c =================================================================== --- /dev/null +++ test/Analysis/TimeChecker-3.c @@ -0,0 +1,7 @@ +// RUN: %clang --analyze -Xclang -analyzer-checker=alpha.unix.Time -Xclang -verify %s + +typedef signed char __time_t; +typedef __time_t time_t; +// expected-warning@4 {{Y2K38 Issue: time_t expected to be a signed integer type, at-least 64-bits in size}} +// expected-warning@4 {{Y2K38 Issue: time_t size is 8 bits}} +// TODO: EXPECTED-WARNING-RE@4 {{Y2K38 Issue: The type 'time_t' is defined in file '{{(.*)}}/TimeChecker-3.c' which has a non-standard name; expected to see type 'time_t' defined in standard header named: 'time.h'}} Index: test/Analysis/TimeChecker-4.c =================================================================== --- /dev/null +++ test/Analysis/TimeChecker-4.c @@ -0,0 +1,5 @@ +// RUN: %clang --analyze -Xclang -analyzer-checker=alpha.unix.Time -Xclang -verify %s + +typedef long int _time_t; +// expected-no-diagnostics +// TODO: EXPECTED-WARNING-RE@3 {{Y2K38 Issue: No typedef for type 'time_t' in translation unit '{{(.*)}}/TimeChecker-4.c'}} Index: test/Analysis/TimeChecker-5.c =================================================================== --- /dev/null +++ test/Analysis/TimeChecker-5.c @@ -0,0 +1,15 @@ +// RUN: %clang --analyze -Xclang -analyzer-checker=alpha.unix.Time -Xclang -verify %s + +typedef char T0; +typedef T0 T1; +typedef T1 T2; +typedef T2 T3; +typedef T3 T4; +typedef T4 T5; +typedef T5 T6; +typedef T6 T7; +typedef T7 time_t; +typedef time_t T8; +// expected-warning@11 {{Y2K38 Issue: time_t expected to be a signed integer type, at-least 64-bits in size, having file scope}} +// expected-warning@11 {{Y2K38 Issue: time_t size is 8 bits}} +// TODO: EXPECTED-WARNING-RE@11 {{Y2K38 Issue: The type 'time_t' is defined in file '{{(.*)}}/TimeChecker-5.c' which has a non-standard name; expected to see type 'time_t' defined in standard header named: 'time.h'}} Index: test/Analysis/TimeChecker-6.c =================================================================== --- /dev/null +++ test/Analysis/TimeChecker-6.c @@ -0,0 +1,14 @@ +// RUN: %clang --analyze -Xclang -analyzer-checker=alpha.unix.Time -Xclang -verify %s + +typedef long int T0; +typedef T0 T1; +typedef T1 T2; +typedef T2 T3; +typedef T3 T4; +typedef T4 T5; +typedef T5 T6; +typedef T6 T7; +typedef T7 time_t; +typedef time_t T8; +// expected-no-diagnostics +// TODO: EXPECTED-WARNING-RE@11 {{Y2K38 Issue: The type 'time_t' is defined in file '{{(.*)}}/TimeChecker-6.c' which has a non-standard name; expected to see type 'time_t' defined in standard header named: 'time.h'}} Index: test/Analysis/TimeChecker-7.c =================================================================== --- /dev/null +++ test/Analysis/TimeChecker-7.c @@ -0,0 +1,8 @@ +// RUN: %clang --analyze -Xclang -analyzer-checker=alpha.unix.Time -Xclang -verify %s + +typedef signed char int8_t; +typedef struct time_t_ { + int8_t t[256]; +} time_t; +// expected-warning@6 {{Y2K38 Issue: time_t expected to be a signed integer type, at-least 64-bits in size, having file scope}} +// TODO: EXPECTED-WARNING-RE@6 {{Y2K38 Issue: The type 'time_t' is defined in file '{{(.*)}}/TimeChecker-7.c' which has a non-standard name; expected to see type 'time_t' defined in standard header named: 'time.h'}} Index: test/Analysis/TimeChecker-8.c =================================================================== --- /dev/null +++ test/Analysis/TimeChecker-8.c @@ -0,0 +1,14 @@ +// RUN: %clang --analyze -Xclang -analyzer-checker=alpha.unix.Time -Xclang -verify %s + +typedef unsigned long int __time_t; +typedef __time_t time_t; +// expected-warning@4 {{Y2K38 Issue: time_t expected to be a signed integer type, at-least 64-bits in size, having file scope}} +// TODO: EXPECTED-WARNING-RE@4 {{Y2K38 Issue: The type 'time_t' is defined in file '{{(.*)}}/TimeChecker-8.c' which has a non-standard name; expected to see type 'time_t' defined in standard header named: 'time.h'}} + +time_t Bad(void) { + + time_t t = 0; + + return t; +} +// expected-warning@12 {{Y2K38 Issue: Implicit cast of time_t value to an unsigned type}} Index: test/Analysis/TimeChecker-9.c =================================================================== --- /dev/null +++ test/Analysis/TimeChecker-9.c @@ -0,0 +1,14 @@ +// RUN: %clang --analyze -Xclang -analyzer-checker=alpha.unix.Time -Xclang -verify %s + +typedef short __time_t; +typedef __time_t time_t; +// expected-warning@4 {{Y2K38 Issue: time_t expected to be a signed integer type, at-least 64-bits in size}} +// expected-warning@4 {{Y2K38 Issue: time_t size is 16 bits}} +// TODO: EXPECTED-WARNING-RE@4 {{Y2K38 Issue: The type 'time_t' is defined in file '{{(.*)}}/TimeChecker-9.c' which has a non-standard name; expected to see type 'time_t' defined in standard header named: 'time.h'}} + +time_t Bad(void) { + + time_t t = 0; + + return t; +} Index: www/analyzer/alpha_checks.html =================================================================== --- www/analyzer/alpha_checks.html +++ www/analyzer/alpha_checks.html @@ -1047,6 +1047,47 @@ } + + + +