diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/UncountedCallArgsChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/WebKit/UncountedCallArgsChecker.cpp index 8d344f9b63961..8b41a949fd673 100644 --- a/clang/lib/StaticAnalyzer/Checkers/WebKit/UncountedCallArgsChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/UncountedCallArgsChecker.cpp @@ -170,6 +170,9 @@ class UncountedCallArgsChecker if (!Callee) return false; + if (isMethodOnWTFContainerType(Callee)) + return true; + auto overloadedOperatorType = Callee->getOverloadedOperator(); if (overloadedOperatorType == OO_EqualEqual || overloadedOperatorType == OO_ExclaimEqual || @@ -198,6 +201,31 @@ class UncountedCallArgsChecker return false; } + bool isMethodOnWTFContainerType(const FunctionDecl *Decl) const { + if (!isa(Decl)) + return false; + auto *ClassDecl = Decl->getParent(); + if (!ClassDecl || !isa(ClassDecl)) + return false; + + auto *NsDecl = ClassDecl->getParent(); + if (!NsDecl || !isa(NsDecl)) + return false; + + auto MethodName = safeGetName(Decl); + auto ClsNameStr = safeGetName(ClassDecl); + StringRef ClsName = ClsNameStr; // FIXME: Make safeGetName return StringRef. + auto NamespaceName = safeGetName(NsDecl); + // FIXME: These should be implemented via attributes. + return NamespaceName == "WTF" && + (MethodName == "find" || MethodName == "findIf" || + MethodName == "reverseFind" || MethodName == "reverseFindIf" || + MethodName == "get" || MethodName == "inlineGet" || + MethodName == "contains" || MethodName == "containsIf") && + (ClsName.ends_with("Vector") || ClsName.ends_with("Set") || + ClsName.ends_with("Map")); + } + void reportBug(const Expr *CallArg, const ParmVarDecl *Param) const { assert(CallArg); diff --git a/clang/test/Analysis/Checkers/WebKit/call-args-wtf-containers.cpp b/clang/test/Analysis/Checkers/WebKit/call-args-wtf-containers.cpp new file mode 100644 index 0000000000000..0a63a78985612 --- /dev/null +++ b/clang/test/Analysis/Checkers/WebKit/call-args-wtf-containers.cpp @@ -0,0 +1,146 @@ +// RUN: %clang_analyze_cc1 -analyzer-checker=alpha.webkit.UncountedCallArgsChecker -verify %s + +#include "mock-types.h" + +namespace WTF { + + template + class HashSet { + public: + template T* find(U&) const; + template bool contains(U&) const; + unsigned size() { return m_size; } + template void add(U&) const; + template void remove(U&) const; + + private: + T* m_table { nullptr }; + unsigned m_size { 0 }; + }; + + template + class HashMap { + public: + struct Item { + T key; + S value; + }; + + template Item* find(U&) const; + template bool contains(U&) const; + template S* get(U&) const; + template S* inlineGet(U&) const; + template void add(U&) const; + template void remove(U&) const; + + private: + Item* m_table { nullptr }; + }; + + template + class WeakHashSet { + public: + template T* find(U&) const; + template bool contains(U&) const; + template void add(U&) const; + template void remove(U&) const; + }; + + template + class Vector { + public: + unsigned size() { return m_size; } + T& at(unsigned i) { return m_buffer[i]; } + T& operator[](unsigned i) { return m_buffer[i]; } + template unsigned find(U&); + template unsigned reverseFind(U&); + template bool contains(U&); + template unsigned findIf(const MatchFunction& match) + { + for (unsigned i = 0; i < m_size; ++i) { + if (match(at(i))) + return i; + } + return static_cast(-1); + } + template unsigned reverseFindIf(const MatchFunction& match) + { + for (unsigned i = 0; i < m_size; ++i) { + if (match(at(m_size - i))) + return i; + } + return static_cast(-1); + } + template bool containsIf(const MatchFunction& match) + { + for (unsigned i = 0; i < m_size; ++i) { + if (match(at(m_size - i))) + return true; + } + return false; + } + template void append(U&) const; + template void remove(U&) const; + + private: + T* m_buffer { nullptr }; + unsigned m_size { 0 }; + }; + +} + +using WTF::HashSet; +using WTF::HashMap; +using WTF::WeakHashSet; +using WTF::Vector; + +class RefCounted { +public: + void ref() const; + void deref() const; +}; + +RefCounted* object(); + +void test() { + HashSet> set; + set.find(*object()); + set.contains(*object()); + set.add(*object()); + // expected-warning@-1{{Call argument is uncounted and unsafe}} + set.remove(*object()); + // expected-warning@-1{{Call argument is uncounted and unsafe}} + + HashMap, unsigned> map; + map.find(*object()); + map.contains(*object()); + map.inlineGet(*object()); + map.add(*object()); + // expected-warning@-1{{Call argument is uncounted and unsafe}} + map.remove(*object()); + // expected-warning@-1{{Call argument is uncounted and unsafe}} + + WeakHashSet> weakSet; + weakSet.find(*object()); + weakSet.contains(*object()); + weakSet.add(*object()); + // expected-warning@-1{{Call argument is uncounted and unsafe}} + weakSet.remove(*object()); + // expected-warning@-1{{Call argument is uncounted and unsafe}} + + Vector> vector; + vector.at(0); + vector[0]; + vector.find(*object()); + vector.reverseFind(*object()); + vector.contains(*object()); + vector.append(*object()); + // expected-warning@-1{{Call argument is uncounted and unsafe}} + vector.remove(*object()); + // expected-warning@-1{{Call argument is uncounted and unsafe}} + + auto* obj = object(); + vector.findIf([&](Ref key) { return key.ptr() == obj; }); + vector.reverseFindIf([&](Ref key) { return key.ptr() == obj; }); + vector.containsIf([&](Ref key) { return key.ptr() == obj; }); +} \ No newline at end of file