diff --git a/infer/models/cpp/include/infer_model/common.h b/infer/models/cpp/include/infer_model/common.h index b19a9f7e5..4e6913b46 100644 --- a/infer/models/cpp/include/infer_model/common.h +++ b/infer/models/cpp/include/infer_model/common.h @@ -16,7 +16,9 @@ namespace infer_model { // code compiled with infer headers is not supposed to be executed struct AbortWhenRun { - AbortWhenRun() { + AbortWhenRun() { __infer_skip__(); } + // will be skipped by analyzer + void __infer_skip__() { fprintf(stderr, "!!! This program must not be run !!!\n" "This code was compiled to be analyzed by Infer.\n" diff --git a/infer/models/cpp/include/infer_model/vector.h b/infer/models/cpp/include/infer_model/vector.h index 127b26457..90ed3a26b 100644 --- a/infer/models/cpp/include/infer_model/vector.h +++ b/infer/models/cpp/include/infer_model/vector.h @@ -49,6 +49,10 @@ struct vector_ref { typedef bool_ref ref; }; +// this function will be treated as SKIP by infer +template +T* __infer_skip__get_nondet_val() {} + // WARNING: do not add any new fields to std::vector model. sizeof(std::vector) // = 24 !! template > @@ -71,17 +75,48 @@ class vector { typedef std::reverse_iterator reverse_iterator; typedef std::reverse_iterator const_reverse_iterator; + /* INFER SPECIFIC HELPER FUNCTIONS */ bool isEmpty = true; - mutable value_type* modelPtr = nullptr; - // intended to keep semantics of empty vector - // vector.begin() == vector.end() <=> vector.empty() - // also required to keep sizeof(std::vector) same. + + // required to keep sizeof(std::vector) same as in standard + value_type* beginPtr = nullptr; value_type* endPtr = nullptr; - ~vector() {} + value_type* get() const { + if (isEmpty) { + return nullptr; + } + // infer will angelically assume that __infer_skip__get_nondet_val + // returns non-null with unknown value which means there will be no + // null dereference + return __infer_skip__get_nondet_val(); + } + + void allocate(size_type size) { + if (size > 0) { + isEmpty = false; + } else { + isEmpty = true; + } + } + + template + void allocate_iter(Iter begin, Iter end) { + if (begin != end) { + allocate(1); + } else { + allocate(0); + } + } + + /* std::vector implementation */ + + vector() noexcept(is_nothrow_default_constructible::value) { + allocate(0); + } + + explicit vector(const allocator_type& __a) noexcept { allocate(0); } - vector() noexcept(is_nothrow_default_constructible::value) {} - explicit vector(const allocator_type& __a) noexcept {} explicit vector(size_type __n); // introduced in C++14 explicit vector(size_type __n, const allocator_type& __a); @@ -164,14 +199,16 @@ class vector { const_reverse_iterator crend() const noexcept { return rend(); } - size_type size() const noexcept { /* TODO */ + size_type size() const noexcept { + if (!isEmpty) { + return 10; + } + return 0; } - size_type capacity() const noexcept { /* TODO */ - } + size_type capacity() const noexcept {} - bool empty() const noexcept { /* TODO */ - } + bool empty() const noexcept { return isEmpty; } size_type max_size() const noexcept; void reserve(size_type __n); void shrink_to_fit() noexcept; @@ -181,14 +218,14 @@ class vector { reference at(size_type __n); const_reference at(size_type __n) const; - reference front() {} - const_reference front() const {} - reference back() {} - const_reference back() const {} + reference front() { return (reference)*get(); } + const_reference front() const { return (const_reference)*get(); } + reference back() { return (reference)*get(); } + const_reference back() const { return (const_reference)*get(); } - value_type* data() noexcept {} + value_type* data() noexcept { return get(); } - const value_type* data() const noexcept {} + const value_type* data() const noexcept { return get(); } void push_back(const_reference __x); void push_back(value_type&& __x); @@ -222,8 +259,7 @@ class vector { iterator erase(const_iterator __position); iterator erase(const_iterator __first, const_iterator __last); - void clear() noexcept { /* TODO */ - } + void clear() noexcept { isEmpty = true; } void resize(size_type __sz); void resize(size_type __sz, const_reference __x); @@ -240,22 +276,29 @@ class vector { template typename vector<_Tp, _Allocator>::size_type vector<_Tp, _Allocator>::max_size() const noexcept { - /*TODO*/ } template -vector<_Tp, _Allocator>::vector(size_type __n) {} +vector<_Tp, _Allocator>::vector(size_type __n) { + allocate(__n); +} template -vector<_Tp, _Allocator>::vector(size_type __n, const allocator_type& __a) {} +vector<_Tp, _Allocator>::vector(size_type __n, const allocator_type& __a) { + allocate(__n); +} template -vector<_Tp, _Allocator>::vector(size_type __n, const_reference __x) {} +vector<_Tp, _Allocator>::vector(size_type __n, const_reference __x) { + allocate(__n); +} template vector<_Tp, _Allocator>::vector(size_type __n, const_reference __x, - const allocator_type& __a) {} + const allocator_type& __a) { + allocate(__n); +} template template @@ -265,7 +308,9 @@ vector<_Tp, _Allocator>::vector( is_constructible< value_type, typename iterator_traits<_ForwardIterator>::reference>::value, - _ForwardIterator>::type __last) {} + _ForwardIterator>::type __last) { + allocate_iter(__first, __last); +} template template @@ -276,47 +321,57 @@ vector<_Tp, _Allocator>::vector( typename enable_if::reference>::value>::type*) { + allocate_iter(__first, __last); } template -vector<_Tp, _Allocator>::vector(const vector& __x) {} +vector<_Tp, _Allocator>::vector(const vector& __x) { + isEmpty = __x.isEmpty; +} template -vector<_Tp, _Allocator>::vector(const vector& __x, const allocator_type& __a) {} +vector<_Tp, _Allocator>::vector(const vector& __x, const allocator_type& __a) { + isEmpty = __x.isEmpty; +} template inline vector<_Tp, _Allocator>::vector(vector&& __x) noexcept { - /* TODO - this->data = __x.data; __x.data = nullptr; - */ + isEmpty = __x.isEmpty; + __x.isEmpty = true; } template inline vector<_Tp, _Allocator>::vector(vector&& __x, const allocator_type& __a) { - /* TODO - see above */ + isEmpty = __x.isEmpty; + __x.isEmpty = true; } template -inline vector<_Tp, _Allocator>::vector(initializer_list __il) {} +inline vector<_Tp, _Allocator>::vector(initializer_list __il) { + allocate_iter(__il.begin(), __il.end()); +} template inline vector<_Tp, _Allocator>::vector(initializer_list __il, - const allocator_type& __a) {} + const allocator_type& __a) { + allocate_iter(__il.begin(), __il.end()); +} template inline vector<_Tp, _Allocator>& vector<_Tp, _Allocator>::operator=( vector&& __x) noexcept /*((__noexcept_move_assign_container<_Allocator, __alloc_traits>::value)) */ { - /* TODO - see above */ + isEmpty = __x.isEmpty; + __x.isEmpty = true; + return *this; } template inline vector<_Tp, _Allocator>& vector<_Tp, _Allocator>::operator=( const vector& __x) { - if (this != &__x) { - // assign(__x.__begin_, __x.__end_); - } + isEmpty = __x.isEmpty; return *this; } @@ -327,10 +382,14 @@ typename enable_if::reference>::value, void>::type vector<_Tp, _Allocator>::assign(_ForwardIterator __first, - _ForwardIterator __last) {} + _ForwardIterator __last) { + allocate_iter(__first, __last); +} template -void vector<_Tp, _Allocator>::assign(size_type __n, const_reference __u) {} +void vector<_Tp, _Allocator>::assign(size_type __n, const_reference __u) { + allocate(__n); +} template inline typename vector<_Tp, _Allocator>::iterator @@ -347,42 +406,50 @@ vector<_Tp, _Allocator>::__make_iter(const_pointer __p) const noexcept { template inline typename vector<_Tp, _Allocator>::iterator vector<_Tp, _Allocator>::begin() noexcept { - // return __make_iter(this->__begin_); + return __make_iter(beginPtr); } template inline typename vector<_Tp, _Allocator>::const_iterator vector<_Tp, _Allocator>::begin() const noexcept { - // return __make_iter(this->__begin_); + return __make_iter(beginPtr); } template inline typename vector<_Tp, _Allocator>::iterator vector<_Tp, _Allocator>::end() noexcept { - // return __make_iter(this->__end_); + return __make_iter(endPtr); } template inline typename vector<_Tp, _Allocator>::const_iterator vector<_Tp, _Allocator>::end() const noexcept { - // return __make_iter(this->__end_); + return __make_iter(endPtr); } template inline typename vector<_Tp, _Allocator>::reference vector<_Tp, _Allocator>:: -operator[](size_type __n) {} +operator[](size_type __n) { + return (reference)*get(); +} template inline typename vector<_Tp, _Allocator>::const_reference - vector<_Tp, _Allocator>::operator[](size_type __n) const {} + vector<_Tp, _Allocator>::operator[](size_type __n) const { + return (const_reference)*get(); +} template typename vector<_Tp, _Allocator>::reference vector<_Tp, _Allocator>::at( - size_type __n) {} + size_type __n) { + return (reference)*get(); +} template typename vector<_Tp, _Allocator>::const_reference vector<_Tp, _Allocator>::at( - size_type __n) const {} + size_type __n) const { + return (const_reference)*get(); +} template void vector<_Tp, _Allocator>::reserve(size_type __n) {} @@ -391,10 +458,14 @@ template void vector<_Tp, _Allocator>::shrink_to_fit() noexcept {} template -inline void vector<_Tp, _Allocator>::push_back(const_reference __x) {} +inline void vector<_Tp, _Allocator>::push_back(const_reference __x) { + allocate(1); +} template -inline void vector<_Tp, _Allocator>::push_back(value_type&& __x) {} +inline void vector<_Tp, _Allocator>::push_back(value_type&& __x) { + allocate(1); +} template template @@ -404,6 +475,7 @@ inline void vector<_Tp, _Allocator>::emplace_back(_Args&&... __args) { _VSTD::__to_raw_pointer(this->__end_), _VSTD::forward<_Args>(__args)...); */ + allocate(1); } template @@ -438,11 +510,16 @@ template typename vector<_Tp, _Allocator>::iterator vector<_Tp, _Allocator>::emplace( const_iterator __position, _Args&&... __args) { // TODO consider constructing the object + allocate(1); } template typename vector<_Tp, _Allocator>::iterator vector<_Tp, _Allocator>::insert( - const_iterator __position, size_type __n, const_reference __x) {} + const_iterator __position, size_type __n, const_reference __x) { + if (isEmpty) { + allocate(__n); + } +} template template @@ -457,13 +534,21 @@ vector<_Tp, _Allocator>::insert(const_iterator __position, } template -void vector<_Tp, _Allocator>::resize(size_type __sz) {} +void vector<_Tp, _Allocator>::resize(size_type __sz) { + allocate(__sz); +} template -void vector<_Tp, _Allocator>::resize(size_type __sz, const_reference __x) {} +void vector<_Tp, _Allocator>::resize(size_type __sz, const_reference __x) { + allocate(__sz); +} template -void vector<_Tp, _Allocator>::swap(vector& __x) {} +void vector<_Tp, _Allocator>::swap(vector& __x) { + bool tmp = __x.isEmpty; + __x.isEmpty = isEmpty; + isEmpty = tmp; +} template struct hash> diff --git a/infer/src/clang/cFrontend_decl.ml b/infer/src/clang/cFrontend_decl.ml index ae286b64b..0defe2332 100644 --- a/infer/src/clang/cFrontend_decl.ml +++ b/infer/src/clang/cFrontend_decl.ml @@ -160,7 +160,13 @@ struct top_qual = "google" && IList.mem (=) fun_name CFrontend_config.google_whitelisting_functions | _ -> false in - translate_location || always_translate_decl + let never_translate_decl = match dec with + | Clang_ast_t.FunctionDecl (_, name_info, _, _) + | Clang_ast_t.CXXMethodDecl (_, name_info, _, _, _) -> + let fun_name = name_info.Clang_ast_t.ni_name in + Str.string_match (Str.regexp "__infer_skip__" ) fun_name 0 + | _ -> false in + (not never_translate_decl) && (translate_location || always_translate_decl) (* Translate one global declaration *) let rec translate_one_declaration tenv cg cfg decl_trans_context dec = diff --git a/infer/tests/codetoanalyze/cpp/errors/vector/empty_access.cpp b/infer/tests/codetoanalyze/cpp/errors/vector/empty_access.cpp new file mode 100644 index 000000000..4ba8e9f16 --- /dev/null +++ b/infer/tests/codetoanalyze/cpp/errors/vector/empty_access.cpp @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2016 - present Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#include + +int access_empty() { + const std::vector vec; + return vec[0]; +} + +int access_nonempty() { + const std::vector vec(1); + return vec[0]; +} + +int clear_empty() { + std::vector vec(1); + vec.clear(); + return vec[0]; +} + +int resize0_empty() { + std::vector vec(1); + vec.resize(0); + return vec[0]; +} + +int resize1_nonempty() { + std::vector vec; + vec.resize(1); + return vec[0]; +} + +int push_back_nonempty() { + std::vector vec; + vec.push_back(1); + return vec[0]; +} + +int copy_empty() { + std::vector vec1; + std::vector vec2 = vec1; + return vec2[0]; +} + +int copy_nonempty() { + std::vector vec1(10); + std::vector vec2 = vec1; + return vec2[0]; +} + +int assign_empty() { + std::vector vec1; + std::vector vec2(1); + vec2 = vec1; + return vec2[0]; +} + +int assign_nonempty() { + std::vector vec1(1); + std::vector vec2; + vec2 = vec1; + return vec2[0]; +} + +int empty_check_nonempty() { + std::vector vec; + if (vec.empty()) { + return 1; + } + return vec[0]; +} + +int empty_check_nonempty2() { + std::vector vec; + if (vec.empty()) { + vec.push_back(1); + } + return vec[0]; +} + +int empty_check_access_empty() { + std::vector vec; + if (vec.empty()) { + return vec[0]; + } + return 1; +} + +int size_check0_empty() { + std::vector vec; + if (vec.size() == 0) { + return vec[0]; + } + return 1; +} + +int size_check1_nonempty() { + std::vector vec; + if (vec.size() > 0) { + return vec[0]; + } + return 1; +} diff --git a/infer/tests/endtoend/cpp/VectorEmptyAccessTest.java b/infer/tests/endtoend/cpp/VectorEmptyAccessTest.java new file mode 100644 index 000000000..d907701a3 --- /dev/null +++ b/infer/tests/endtoend/cpp/VectorEmptyAccessTest.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2016 - present Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +package endtoend.cpp; + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.ResultContainsExactly.containsExactly; + + +import com.google.common.collect.ImmutableList; + +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; + +import java.io.IOException; + +import utils.DebuggableTemporaryFolder; +import utils.InferException; +import utils.InferResults; +import utils.InferRunner; + +public class VectorEmptyAccessTest { + + public static final String FILE = + "infer/tests/codetoanalyze/cpp/errors/vector/empty_access.cpp"; + + private static ImmutableList inferCmd; + + public static final String NULL_DEREFERENCE = "NULL_DEREFERENCE"; + + @ClassRule + public static DebuggableTemporaryFolder folder = + new DebuggableTemporaryFolder(); + + @BeforeClass + public static void runInfer() throws InterruptedException, IOException { + inferCmd = InferRunner.createCPPInferCommand(folder, FILE); + } + + @Test + public void whenInferRunsNullDerefFunctionsErrorIsFound() + throws InterruptedException, IOException, InferException { + String[] procedures = { + "access_empty", + "clear_empty", + "resize0_empty", + "copy_empty", + "assign_empty", + "empty_check_access_empty", + "size_check0_empty", + }; + InferResults inferResults = InferRunner.runInferCPP(inferCmd); + assertThat( + "Results should contain empty vector access", + inferResults, + containsExactly( + NULL_DEREFERENCE, + FILE, + procedures + ) + ); + } +}