├── test_files ├── Test1.h └── Test1.cpp ├── Makefile.am ├── .gitignore ├── CxxTokenize.d ├── LICENSE ├── TARGETS ├── CONTRIBUTING.md ├── configure.ac ├── README.md ├── FileCategories.d ├── Ignored.d ├── m4 ├── ac_cxx_compile_stdcxx_0x.m4 ├── ax_boost_filesystem.m4 └── ax_boost_base.m4 ├── CxxReplace.d ├── Main.d ├── Tokenizer.d └── Test.d /test_files/Test1.h: -------------------------------------------------------------------------------- 1 | #ifndef TEST1_H 2 | #define TEST1_H 3 | 4 | #include 5 | 6 | #endif 7 | -------------------------------------------------------------------------------- /test_files/Test1.cpp: -------------------------------------------------------------------------------- 1 | #include "Test1.h" 2 | 3 | int main(int argc, char **argv) { 4 | std::string s("asd"); 5 | std::map abc; 6 | std::map def; 7 | std::map asd; 8 | printf("%s\n", s.c_str()); 9 | } 10 | -------------------------------------------------------------------------------- /Makefile.am: -------------------------------------------------------------------------------- 1 | bin_PROGRAMS = flint 2 | noinst_PROGRAMS = flint_test cxx_replace cxx_tokenize 3 | 4 | ACLOCAL_AMFLAGS = -I m4 5 | 6 | FLINT_SRCS := \ 7 | Checks.d \ 8 | FileCategories.d \ 9 | Ignored.d \ 10 | Tokenizer.d 11 | 12 | flint$(EXEEXT): Main.d $(FLINT_SRCS) 13 | $(DC) -of$@ $^ 14 | 15 | flint_test$(EXEEXT): Test.d $(FLINT_SRCS) 16 | $(DC) -of$@ $^ 17 | 18 | cxx_replace$(EXEEXT): CxxReplace.d $(FLINT_SRCS) 19 | $(DC) -of$@ $^ 20 | 21 | cxx_tokenize$(EXEEXT): CxxTokenize.d $(FLINT_SRCS) 22 | $(DC) -of$@ $^ 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *.o 3 | *.la 4 | *.lo 5 | 6 | config.log 7 | config.status 8 | 9 | cxx_replace 10 | cxx_tokenize 11 | fcxx_replace 12 | fcxx_tokenize 13 | flint 14 | flint_test 15 | fflint 16 | fflint_test 17 | 18 | libtool 19 | m4/libtool.m4 20 | m4/ltoptions.m4 21 | m4/ltsugar.m4 22 | m4/ltversion.m4 23 | m4/lt~obsolete.m4 24 | aclocal.m4 25 | Makefile.in 26 | 27 | Makefile 28 | configure 29 | 30 | .deps/* 31 | autom4te.cache/* 32 | build-aux/* 33 | 34 | cxx/.deps/* 35 | cxx/.libs/* 36 | cxx/gtest-1.6.0.zip 37 | cxx/gtest-1.6.0/* 38 | -------------------------------------------------------------------------------- /CxxTokenize.d: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014- Facebook 2 | // License: Boost License 1.0, http://boost.org/LICENSE_1_0.txt 3 | 4 | import Tokenizer; 5 | import std.conv, std.exception, std.file, std.range, std.stdio; 6 | 7 | /** 8 | * Reads the files passed as arguments and prints out Cxx tokens one by line 9 | */ 10 | void main(string[] args) { 11 | enforce(args.length > 1, 12 | text("Usage:", args[0], " files...")); 13 | 14 | foreach (fi; 1 .. args.length) { 15 | auto filename = args[fi]; 16 | 17 | Token[] tokens; 18 | auto file = std.file.readText(filename); 19 | tokenize(file, filename, tokens); 20 | 21 | // Strip terminating \0 22 | assert(tokens.back.type_ == tk!"\0"); 23 | tokens.popBack; 24 | 25 | foreach (idx; 0 .. tokens.length) { 26 | stdout.writef("%s %s\n", tokens[idx].type_.sym(), tokens[idx].value_); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Boost Software License - Version 1.0 - August 17th, 2003 2 | 3 | Permission is hereby granted, free of charge, to any person or organization 4 | obtaining a copy of the software and accompanying documentation covered by 5 | this license (the "Software") to use, reproduce, display, distribute, 6 | execute, and transmit the Software, and to prepare derivative works of the 7 | Software, and to permit third-parties to whom the Software is furnished to 8 | do so, all subject to the following: 9 | 10 | The copyright notices in the Software and this entire statement, including 11 | the above license grant, this restriction and the following disclaimer, 12 | must be included in all copies of the Software, in whole or in part, and 13 | all derivative works of the Software, unless such copies or derivative 14 | works are solely in the form of machine-executable object code generated by 15 | a source language processor. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 20 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 21 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 22 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /TARGETS: -------------------------------------------------------------------------------- 1 | cpp_library ( 2 | name = "libflint-cxx", 3 | srcs = [ 4 | "cxx/Tokenizer.cpp", 5 | "cxx/Checks.cpp", 6 | "cxx/FileCategories.cpp", 7 | "cxx/Ignored.cpp", 8 | ], 9 | deps = [ 10 | "@/folly", 11 | ], 12 | external_deps = [ 13 | ('boost', None, 'boost_filesystem'), 14 | ], 15 | ) 16 | 17 | cpp_unittest ( 18 | name = "test-cxx", 19 | srcs = [ 20 | "cxx/Test.cpp", 21 | ], 22 | deps = [ 23 | ":libflint-cxx" 24 | ], 25 | type = 'gtest', 26 | ) 27 | 28 | d_unittest ( 29 | name = "test", 30 | srcs = [ 31 | "Test.d", 32 | "Checks.d", 33 | "Tokenizer.d", 34 | "FileCategories.d", 35 | ], 36 | deps = [ 37 | ], 38 | ) 39 | 40 | cpp_binary ( 41 | name = "flint-cxx", 42 | srcs = [ 43 | "cxx/Main.cpp", 44 | ], 45 | deps = [ 46 | ":libflint-cxx" 47 | ], 48 | ) 49 | 50 | d_binary ( 51 | name = "flint", 52 | srcs = [ 53 | "Main.d", 54 | "Checks.d", 55 | "FileCategories.d", 56 | "Ignored.d", 57 | "Tokenizer.d", 58 | ], 59 | deps = [ 60 | ], 61 | ) 62 | 63 | cpp_binary ( 64 | name = "cxx_replace-cxx", 65 | srcs = [ 66 | "cxx/CxxReplace.cpp", 67 | ], 68 | deps = [ 69 | ":libflint-cxx" 70 | ], 71 | ) 72 | 73 | d_binary ( 74 | name = "cxx_replace", 75 | srcs = [ 76 | "CxxReplace.d", 77 | "Tokenizer.d", 78 | ], 79 | deps = [ 80 | ], 81 | ) 82 | 83 | d_binary ( 84 | name = "cxx_tokenize", 85 | srcs = [ 86 | "CxxTokenize.d", 87 | "Tokenizer.d", 88 | ], 89 | deps = [ 90 | ], 91 | ) 92 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to flint 2 | We want to make contributing to this project as easy and transparent as 3 | possible. 4 | 5 | ## Our Development Process 6 | 7 | Just submit pull requests as is usual for git (thank you!). We won't 8 | pull the request directly though. Upon approval, we'll cherry-pick 9 | your changes internally while of course maintaining you as an author, 10 | and then we'll publish them to github. After all that, we'll close the 11 | pull request. 12 | 13 | This somewhat roundabout process is motivated by our need to maintain 14 | an internal source of truth inside the larger context of our larger 15 | internal repository. 16 | 17 | ## Pull Requests 18 | We actively welcome your pull requests. 19 | 1. Fork the repo and create your branch from `master`. 20 | 2. If you've added code that should be tested, add tests 21 | 3. If you've changed APIs, update the documentation. 22 | 4. Ensure the test suite passes. 23 | 5. Make sure your code lints. 24 | 6. If you haven't already, complete the Contributor License Agreement ("CLA"). 25 | 26 | ## Contributor License Agreement ("CLA") 27 | In order to accept your pull request, we need you to submit a CLA. You only need 28 | to do this once to work on any of Facebook's open source projects. 29 | 30 | Complete your CLA here: 31 | 32 | ## Issues 33 | We use GitHub issues to track public bugs. Please ensure your description is 34 | clear and has sufficient instructions to be able to reproduce the issue. 35 | 36 | ## Coding Style 37 | * 2 spaces for indentation rather than tabs 38 | * 80 character line length 39 | * For spacing around punctuation and similar minutia, follow the 40 | practices in the file(s) you're working on 41 | 42 | ## License 43 | By contributing to flint, you agree that your contributions will be licensed 44 | under its Boost license. 45 | -------------------------------------------------------------------------------- /configure.ac: -------------------------------------------------------------------------------- 1 | # -*- Autoconf -*- 2 | # Process this file with autoconf to produce a configure script. 3 | 4 | AC_PREREQ(2.59) 5 | AC_INIT(flint, 0.1, flint@fb.com) 6 | AC_CONFIG_AUX_DIR([build-aux]) 7 | 8 | AM_INIT_AUTOMAKE([foreign nostdinc]) 9 | 10 | AC_CONFIG_MACRO_DIR([m4]) 11 | 12 | AC_PROG_INSTALL 13 | AM_PROG_LIBTOOL 14 | 15 | AC_LANG([C++]) 16 | 17 | # Checks for programs. 18 | AC_PROG_CXX 19 | AC_CXX_COMPILE_STDCXX_0X 20 | 21 | AC_CHECK_PROGS([DC],[gdc dmd ldc ldc2]) 22 | if test -z "$DC" ; then 23 | AC_MSG_ERROR([\ 24 | *** No D Compiler found. Please install one of gdc, ldc, or dmd]) 25 | fi 26 | 27 | # Be sure to add any -std option to CXXFLAGS before we invoke any 28 | # AC_COMPILE_IFELSE() or similar macros. Any such macros that are invoked 29 | # before we update CXXFLAGS will not be run with the same options that we use 30 | # during the real build. 31 | STD="" 32 | if test "x$ac_cv_cxx_compile_cxx0x_cxx" = xyes; then 33 | STD="-std=c++0x" 34 | fi 35 | if test "x$ac_cv_cxx_compile_cxx0x_gxx" = xyes; then 36 | STD="-std=gnu++0x" 37 | fi 38 | 39 | CXXFLAGS="$STD $CXXFLAGS" 40 | 41 | # Checks for libraries. 42 | AC_CHECK_LIB([folly],[getenv],[],[AC_MSG_ERROR( 43 | [Please install the folly library])]) 44 | AC_CHECK_LIB([gflags],[getenv],[],[AC_MSG_ERROR( 45 | [Please install google-gflags library])]) 46 | 47 | # Checks for header files. 48 | AC_HEADER_STDC 49 | # Checks for typedefs, structures, and compiler characteristics. 50 | AC_HEADER_STDBOOL 51 | AC_C_CONST 52 | AC_C_INLINE 53 | AC_TYPE_SIZE_T 54 | AC_HEADER_TIME 55 | AC_C_VOLATILE 56 | 57 | # check for boost libs 58 | AX_BOOST_BASE([1.20.0], [], [AC_MSG_ERROR( 59 | [Please install boost >= 1.20.0 (filesystem)])]) 60 | AX_BOOST_FILESYSTEM 61 | 62 | # Output 63 | AC_CONFIG_FILES([Makefile]) 64 | AC_OUTPUT 65 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | - - - 2 | 3 | **_This project is not actively maintained. Proceed at your own risk!_** 4 | 5 | - - - 6 | 7 | `flint`: Facebook's C++ Linter 8 | ----------------------------- 9 | 10 | `flint` is an open-source lint program for C++ developed and used at Facebook. 11 | 12 | `flint` is published on Github at https://github.com/facebook/flint; for 13 | discussions, there is a Google group at https://groups.google.com/d/forum/facebook-flint. 14 | 15 | There are two versions of `flint`. The main one (`flint/*.d`) is written in the D programming language and is supported going forward. We also provide our older implementation in C++ (`flint/cxx/*.cpp`) for historical perspective and comparison purposes. 16 | 17 | Currently `flint`'s build has only been tested on Ubuntu. The motivated user should have no problem adapting it to other systems. More officially supported OSs to follow. 18 | 19 | Dependencies 20 | ------------ 21 | 22 | - folly (https://github.com/facebook/folly) 23 | 24 | Follow the instructions to download and install folly. 25 | 26 | - double-conversion (https://github.com/google/double-conversion) 27 | 28 | Follow the instructions listed on the Folly page to build and install 29 | 30 | - googletest (Google C++ Testing Framework) 31 | 32 | Grab gtest 1.6.0 from: http://googletest.googlecode.com/files/gtest-1.6.0.zip. Unzip it inside of the cxx/ subdirectory. 33 | 34 | - additional platform specific dependencies 35 | 36 | Ubuntu 13.10 64-bit 37 | - g++ (tested with 4.7.1, 4.8.1) 38 | - gdc 39 | - automake 40 | - autoconf 41 | - autoconf-archive 42 | - libtool 43 | - libboost1.54-all-dev 44 | - libgoogle-glog-dev 45 | - libgflags-dev 46 | 47 | To Build 48 | -------- 49 | 50 | autoreconf --install 51 | LDFLAGS="-L" CPPFLAGS="-I/src" ./configure 52 | make 53 | -------------------------------------------------------------------------------- /FileCategories.d: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014- Facebook 2 | // License: Boost License 1.0, http://boost.org/LICENSE_1_0.txt 3 | // @author Andrei Alexandrescu (andrei.alexandrescu@facebook.com) 4 | 5 | import std.algorithm, std.range; 6 | 7 | immutable string[] 8 | extsHeader = [ ".h", ".hpp", ".hh", ], 9 | extsSourceC = [ ".c", ], 10 | extsSourceCpp = [ ".C", ".cc", ".cpp", ".CPP", ".c++", ".cp", ".cxx", ]; 11 | 12 | enum FileCategory { 13 | header, inl_header, source_c, source_cpp, unknown, 14 | }; 15 | 16 | FileCategory getFileCategory(string fpath) { 17 | foreach (ext; extsHeader) { 18 | if (fpath.endsWith(chain("-inl", ext))) return FileCategory.inl_header; 19 | if (fpath.endsWith(ext)) return FileCategory.header; 20 | } 21 | foreach (ext; extsSourceC) { 22 | if (fpath.endsWith(ext)) { 23 | return FileCategory.source_c; 24 | } 25 | } 26 | foreach (ext; extsSourceCpp) { 27 | if (fpath.endsWith(ext)) { 28 | return FileCategory.source_cpp; 29 | } 30 | } 31 | return FileCategory.unknown; 32 | } 33 | 34 | bool isHeader(string fpath) { 35 | auto fileCategory = getFileCategory(fpath); 36 | return fileCategory == FileCategory.header || 37 | fileCategory == FileCategory.inl_header; 38 | } 39 | 40 | bool isSource(string fpath) { 41 | auto fileCategory = getFileCategory(fpath); 42 | return fileCategory == FileCategory.source_c || 43 | fileCategory == FileCategory.source_cpp; 44 | } 45 | 46 | bool isTestFile(string fpath) { 47 | import std.string : toLower; 48 | return !fpath.toLower.find("test").empty; 49 | } 50 | 51 | string getFileNameBase(string filename) { 52 | foreach (ext; extsHeader) { 53 | auto inlExt = "-inl" ~ ext; 54 | if (filename.endsWith(inlExt)) { 55 | return filename[0 .. $ - inlExt.length]; 56 | } else if (filename.endsWith(ext)) { 57 | return filename[0 .. $ - ext.length]; 58 | } 59 | } 60 | foreach (ext; chain(extsSourceC, extsSourceCpp)) { 61 | if (filename.endsWith(ext)) { 62 | return filename[0 .. $ - ext.length]; 63 | } 64 | } 65 | return filename; 66 | } 67 | -------------------------------------------------------------------------------- /Ignored.d: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014- Facebook 2 | // License: Boost License 1.0, http://boost.org/LICENSE_1_0.txt 3 | // @author Andrei Alexandrescu (andrei.alexandrescu@facebook.com) 4 | 5 | import std.range, std.stdio, std.string; 6 | 7 | const kIgnorePause = "// %flint: pause"; 8 | const kIgnoreResume = "// %flint: resume"; 9 | 10 | string removeIgnoredCode(string file, string fpath) { 11 | string result; 12 | size_t pos = 0; 13 | 14 | while (true) { 15 | // Find the position of ignorePause starting from position 'pos' 16 | auto posPause = file[pos .. $].indexOf(kIgnorePause); 17 | // If no instance of posPause is found, then add everything from 18 | // pos to end_of_file to result and return. 19 | if (posPause < 0) { 20 | result ~= file[pos .. $]; 21 | break; 22 | } 23 | 24 | posPause += pos; 25 | // If an instance of ignorePause was found, then find the corresponding 26 | // position of ignoreResume. 27 | auto posResume = file[posPause + kIgnorePause.length .. $] 28 | .indexOf(kIgnoreResume); 29 | // If no instance of ignoreResume was found, then show an error to the 30 | // user, with the line number for ignorePause. 31 | if (posResume < 0) { 32 | auto lineNo = file[0 .. posPause].count('\n') + 1; 33 | stderr.writef("%s:%d: No matching \"%s\" found for \"%s\"\n", 34 | fpath, lineNo, kIgnoreResume, kIgnorePause); 35 | result ~= file[pos .. $]; 36 | break; 37 | } 38 | posResume += posPause + kIgnorePause.length; 39 | 40 | // Otherwise add everything from pos to posPause - 1, empty line for 41 | // each line from posPause to posResume + size(posResume) - 1 so that 42 | // other lint errors show correct line number, and set pos to the next 43 | // position after ignoreResume. 44 | result ~= file[pos .. posPause]; 45 | auto emptyLinesToAdd = 46 | file[posPause .. posResume + kIgnoreResume.length].count('\n'); 47 | result ~= std.range.repeat('\n', emptyLinesToAdd).array; 48 | pos = posResume + kIgnoreResume.length; 49 | } 50 | return result; 51 | } 52 | -------------------------------------------------------------------------------- /m4/ac_cxx_compile_stdcxx_0x.m4: -------------------------------------------------------------------------------- 1 | # =========================================================================== 2 | # http://autoconf-archive.cryp.to/ac_cxx_compile_stdcxx_0x.html 3 | # =========================================================================== 4 | # 5 | # SYNOPSIS 6 | # 7 | # AC_CXX_COMPILE_STDCXX_0X 8 | # 9 | # DESCRIPTION 10 | # 11 | # Check for baseline language coverage in the compiler for the C++0x 12 | # standard. 13 | # 14 | # LAST MODIFICATION 15 | # 16 | # 2008-04-17 17 | # 18 | # COPYLEFT 19 | # 20 | # Copyright (c) 2008 Benjamin Kosnik 21 | # 22 | # Copying and distribution of this file, with or without modification, are 23 | # permitted in any medium without royalty provided the copyright notice 24 | # and this notice are preserved. 25 | 26 | AC_DEFUN([AC_CXX_COMPILE_STDCXX_0X], [ 27 | AC_CACHE_CHECK(if g++ supports C++0x features without additional flags, 28 | ac_cv_cxx_compile_cxx0x_native, 29 | [AC_LANG_SAVE 30 | AC_LANG_CPLUSPLUS 31 | AC_TRY_COMPILE([ 32 | template 33 | struct check 34 | { 35 | static_assert(sizeof(int) <= sizeof(T), "not big enough"); 36 | }; 37 | 38 | typedef check> right_angle_brackets; 39 | 40 | int a; 41 | decltype(a) b; 42 | 43 | typedef check check_type; 44 | check_type c; 45 | check_type&& cr = static_cast(c);],, 46 | ac_cv_cxx_compile_cxx0x_native=yes, ac_cv_cxx_compile_cxx0x_native=no) 47 | AC_LANG_RESTORE 48 | ]) 49 | 50 | AC_CACHE_CHECK(if g++ supports C++0x features with -std=c++0x, 51 | ac_cv_cxx_compile_cxx0x_cxx, 52 | [AC_LANG_SAVE 53 | AC_LANG_CPLUSPLUS 54 | ac_save_CXXFLAGS="$CXXFLAGS" 55 | CXXFLAGS="$CXXFLAGS -std=c++0x" 56 | AC_TRY_COMPILE([ 57 | template 58 | struct check 59 | { 60 | static_assert(sizeof(int) <= sizeof(T), "not big enough"); 61 | }; 62 | 63 | typedef check> right_angle_brackets; 64 | 65 | int a; 66 | decltype(a) b; 67 | 68 | typedef check check_type; 69 | check_type c; 70 | check_type&& cr = static_cast(c);],, 71 | ac_cv_cxx_compile_cxx0x_cxx=yes, ac_cv_cxx_compile_cxx0x_cxx=no) 72 | CXXFLAGS="$ac_save_CXXFLAGS" 73 | AC_LANG_RESTORE 74 | ]) 75 | 76 | AC_CACHE_CHECK(if g++ supports C++0x features with -std=gnu++0x, 77 | ac_cv_cxx_compile_cxx0x_gxx, 78 | [AC_LANG_SAVE 79 | AC_LANG_CPLUSPLUS 80 | ac_save_CXXFLAGS="$CXXFLAGS" 81 | CXXFLAGS="$CXXFLAGS -std=gnu++0x" 82 | AC_TRY_COMPILE([ 83 | template 84 | struct check 85 | { 86 | static_assert(sizeof(int) <= sizeof(T), "not big enough"); 87 | }; 88 | 89 | typedef check> right_angle_brackets; 90 | 91 | int a; 92 | decltype(a) b; 93 | 94 | typedef check check_type; 95 | check_type c; 96 | check_type&& cr = static_cast(c);],, 97 | ac_cv_cxx_compile_cxx0x_gxx=yes, ac_cv_cxx_compile_cxx0x_gxx=no) 98 | CXXFLAGS="$ac_save_CXXFLAGS" 99 | AC_LANG_RESTORE 100 | ]) 101 | 102 | if test "$ac_cv_cxx_compile_cxx0x_native" = yes || 103 | test "$ac_cv_cxx_compile_cxx0x_cxx" = yes || 104 | test "$ac_cv_cxx_compile_cxx0x_gxx" = yes; then 105 | AC_DEFINE(HAVE_STDCXX_0X,,[Define if g++ supports C++0x features. ]) 106 | else 107 | AC_MSG_ERROR([Could not find cxx0x support in g++]) 108 | fi 109 | ]) 110 | -------------------------------------------------------------------------------- /CxxReplace.d: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014- Facebook 2 | // License: Boost License 1.0, http://boost.org/LICENSE_1_0.txt 3 | // @author Andrei Alexandrescu (andrei.alexandrescu@facebook.com) 4 | 5 | import Tokenizer; 6 | import std.algorithm, std.conv, std.exception, std.file, std.range, std.stdio; 7 | 8 | bool verbose; 9 | 10 | bool compareTokens(ref const Token lhs, ref const Token rhs) { 11 | if (lhs.type_ != rhs.type_) return false; 12 | if (lhs.type_ == tk!"identifier") { 13 | // Must compare values too 14 | return lhs.value_ == rhs.value_; 15 | } 16 | return true; 17 | } 18 | 19 | /** 20 | * Entry point. Reads in turn and operates on each file passed onto 21 | * the command line. 22 | */ 23 | int main(string[] args) { 24 | enforce(args.length > 3, text("Usage: ", args[0], 25 | " 'replace this code' 'with this code' files...")); 26 | 27 | // Tokenize the code to replace 28 | string oldCode = args[1], newCode = args[2]; 29 | string oldFilename = "old_code"; 30 | Token[] oldTokens; 31 | tokenize(oldCode, oldFilename, oldTokens); 32 | // Get rid of the EOF token 33 | enforce(!oldTokens.empty && oldTokens.back.type_ == tk!"\0"); 34 | oldTokens.popBack; 35 | 36 | // Tokenize the code to replace with 37 | string newFilename = "new_code"; 38 | Token[] newTokens; 39 | tokenize(newCode, newFilename, newTokens); 40 | enforce(!newTokens.empty && newTokens.back.type_ == tk!"\0"); 41 | newTokens.popBack; 42 | 43 | // Operate on each file 44 | foreach (idx; 3 .. args.length) { 45 | writef("Processing: %s\n", args[idx]); 46 | // Get file intro memory 47 | auto file = std.file.readText(args[idx]); 48 | string filename = args[idx]; 49 | Token[] tokens; 50 | // Tokenize the file's contents 51 | tokenize(file, filename, tokens); 52 | // No need for the terminating \0 53 | assert(tokens.back.type_ == tk!"\0"); 54 | string eofWS = tokens.back.precedingWhitespace_; 55 | tokens.popBack; 56 | 57 | // This is the output of the processing 58 | Token[] result; 59 | uint replacements = 0; 60 | 61 | for (auto b = tokens; ; ) { 62 | auto i = b.find!compareTokens(oldTokens); 63 | if (i.empty && !replacements) { 64 | // Nothing was ever found, we're done with this file 65 | break; 66 | } 67 | 68 | // Copy whatever was before the match 69 | result ~= b[0 .. b.length - i.length]; 70 | 71 | if (i.empty) { 72 | // Done 73 | break; 74 | } 75 | 76 | // Copy the replacement tokens, making sure the comment (if any) 77 | // is tacked just before the first token 78 | if (!newTokens.empty) { 79 | newTokens.front.precedingWhitespace_ = i.front.precedingWhitespace_; 80 | result ~= newTokens; 81 | } 82 | ++replacements; 83 | // Move on 84 | if (i.empty) { 85 | // Done with this file 86 | break; 87 | } 88 | // Skip over the old tokens 89 | b = i[oldTokens.length .. $]; 90 | } 91 | 92 | if (!replacements) { 93 | // Nothing to do for this file 94 | continue; 95 | } 96 | 97 | // We now have the new tokens, write'em to the replacement file 98 | auto newFile = args[idx] ~ ".tmp"; 99 | auto f = File(newFile, "w"); 100 | foreach (ref t; result) { 101 | f.write(t.precedingWhitespace_, t.value); 102 | } 103 | f.write(eofWS); 104 | f.close; 105 | 106 | // Forcibly move the new file over the old one 107 | std.file.rename(newFile, args[idx]); 108 | 109 | // Gloat if asked to 110 | if (verbose) { 111 | stderr.writef("%s: %u replacements.\n", args[idx], replacements); 112 | } 113 | } 114 | 115 | return 0; 116 | } 117 | -------------------------------------------------------------------------------- /Main.d: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014- Facebook 2 | // License: Boost License 1.0, http://boost.org/LICENSE_1_0.txt 3 | // @author Andrei Alexandrescu (andrei.alexandrescu@facebook.com) 4 | 5 | import std.conv, std.file, std.getopt, std.stdio, std.string, std.path; 6 | import Checks, FileCategories, Ignored, Tokenizer; 7 | 8 | bool recursive = true; 9 | bool include_what_you_use = false; 10 | string[] exclude_checks; 11 | 12 | uint function(string, Token[])[string] checks; 13 | uint function(string, Token[])[string] cppChecks; 14 | 15 | /** 16 | * Entry point. Reads in turn and verifies each file passed onto the 17 | * command line. 18 | */ 19 | int main(string[] args) { 20 | try { 21 | getopt(args, 22 | "recursive", &recursive, 23 | "c_mode", &c_mode, 24 | "include_what_you_use", &include_what_you_use, 25 | "exclude", &exclude_checks); 26 | } catch (Exception e) { 27 | stderr.writeln(e.msg); 28 | stderr.writeln("usage: flint " ~ 29 | "[--recursive] [--c_mode], [--include_what_you_use]" ~ 30 | "[--exclude=,...]"); 31 | } 32 | 33 | checks = mixin( 34 | makeHashtable!( 35 | checkBlacklistedSequences, 36 | checkBlacklistedIdentifiers, 37 | checkDefinedNames, 38 | checkIfEndifBalance, 39 | checkIncludeGuard, 40 | checkMemset, 41 | checkQuestionableIncludes, 42 | checkInlHeaderInclusions, 43 | checkInitializeFromItself, 44 | checkRandomUsage, 45 | checkSleepUsage, 46 | checkSmartPtrUsage, 47 | checkUniquePtrUsage, 48 | checkBannedIdentifiers, 49 | checkOSSIncludes, 50 | checkMultipleIncludes, 51 | checkBreakInSynchronized, 52 | checkBogusComparisons, 53 | checkExitStatus, 54 | checkAttributeArgumentUnderscores 55 | ) 56 | ); 57 | 58 | version(facebook) { 59 | checks["checkAngleBracketIncludes"] = &checkAngleBracketIncludes; 60 | } 61 | 62 | cppChecks = mixin( 63 | makeHashtable!( 64 | checkNamespaceScopedStatics, 65 | checkIncludeAssociatedHeader, 66 | checkCatchByReference, 67 | checkConstructors, 68 | checkVirtualDestructors, 69 | checkThrowSpecification, 70 | checkThrowsHeapException, 71 | checkUsingNamespaceDirectives, 72 | checkUsingDirectives, 73 | checkFollyDetail, 74 | checkFollyStringPieceByValue, 75 | checkProtectedInheritance, 76 | checkImplicitCast, 77 | checkUpcaseNull, 78 | checkExceptionInheritance, 79 | checkMutexHolderHasName) 80 | ); 81 | 82 | if (include_what_you_use) { 83 | cppChecks["checkDirectStdInclude"] = &checkDirectStdInclude; 84 | } 85 | 86 | foreach (string s ; exclude_checks) { 87 | string ss = strip(s); 88 | checks.remove(ss); 89 | cppChecks.remove(ss); 90 | } 91 | 92 | // Check each file 93 | uint errors = 0; 94 | foreach (arg; args) { 95 | errors += checkEntry(arg); 96 | } 97 | 98 | return 0; 99 | } 100 | 101 | auto makeHashtable(T...)() { 102 | string result = `[`; 103 | foreach (t; T) { 104 | string name = __traits(identifier, t); 105 | result ~= `"` ~ name ~ `" : &` ~ name ~ ", "; 106 | } 107 | return result ~= `]`; 108 | } 109 | 110 | bool dontLintPath(string path) { 111 | return std.path.buildPath(path, ".nolint").exists; 112 | } 113 | 114 | uint checkEntry(string path) { 115 | if (!path.exists) { 116 | return 0; 117 | } 118 | 119 | uint errors = 0; 120 | 121 | if (path.isDir) { 122 | if (!recursive || dontLintPath(path)) { 123 | return 0; 124 | } 125 | foreach (entry; dirEntries(path, SpanMode.shallow)) { 126 | if (!dontLintPath(entry)) { 127 | errors += checkEntry(entry); 128 | } 129 | } 130 | return errors; 131 | } 132 | 133 | if (getFileCategory(path) == FileCategory.unknown) { 134 | return 0; 135 | } 136 | 137 | try { 138 | // Get file intro memory 139 | string file = to!string(path.read); 140 | Token[] tokens; 141 | // Remove code that occurs in pairs of 142 | // "// %flint: pause" & "// %flint: resume" 143 | file = removeIgnoredCode(file, path); 144 | // Tokenize the file's contents 145 | tokens = tokenize(file, path); 146 | 147 | // *** Checks each lint rule 148 | foreach (uint function(string, Token[]) check ; checks.byValue()) { 149 | errors += check(path, tokens); 150 | } 151 | if (!c_mode) { 152 | foreach (uint function(string, Token[]) check ; cppChecks.byValue()) { 153 | errors += check(path, tokens); 154 | } 155 | } 156 | } catch (Exception e) { 157 | stderr.writef("Flint was unable to lint %s\n", path); 158 | stderr.writeln(e.toString()); 159 | } 160 | return errors; 161 | } 162 | -------------------------------------------------------------------------------- /m4/ax_boost_filesystem.m4: -------------------------------------------------------------------------------- 1 | # =========================================================================== 2 | # http://www.gnu.org/software/autoconf-archive/ax_boost_filesystem.html 3 | # =========================================================================== 4 | # 5 | # SYNOPSIS 6 | # 7 | # AX_BOOST_FILESYSTEM 8 | # 9 | # DESCRIPTION 10 | # 11 | # Test for Filesystem library from the Boost C++ libraries. The macro 12 | # requires a preceding call to AX_BOOST_BASE. Further documentation is 13 | # available at . 14 | # 15 | # This macro calls: 16 | # 17 | # AC_SUBST(BOOST_FILESYSTEM_LIB) 18 | # 19 | # And sets: 20 | # 21 | # HAVE_BOOST_FILESYSTEM 22 | # 23 | # LICENSE 24 | # 25 | # Copyright (c) 2009 Thomas Porschberg 26 | # Copyright (c) 2009 Michael Tindal 27 | # Copyright (c) 2009 Roman Rybalko 28 | # 29 | # Copying and distribution of this file, with or without modification, are 30 | # permitted in any medium without royalty provided the copyright notice 31 | # and this notice are preserved. This file is offered as-is, without any 32 | # warranty. 33 | 34 | #serial 26 35 | 36 | AC_DEFUN([AX_BOOST_FILESYSTEM], 37 | [ 38 | AC_ARG_WITH([boost-filesystem], 39 | AS_HELP_STRING([--with-boost-filesystem@<:@=special-lib@:>@], 40 | [use the Filesystem library from boost - it is possible to specify a certain library for the linker 41 | e.g. --with-boost-filesystem=boost_filesystem-gcc-mt ]), 42 | [ 43 | if test "$withval" = "no"; then 44 | want_boost="no" 45 | elif test "$withval" = "yes"; then 46 | want_boost="yes" 47 | ax_boost_user_filesystem_lib="" 48 | else 49 | want_boost="yes" 50 | ax_boost_user_filesystem_lib="$withval" 51 | fi 52 | ], 53 | [want_boost="yes"] 54 | ) 55 | 56 | if test "x$want_boost" = "xyes"; then 57 | AC_REQUIRE([AC_PROG_CC]) 58 | CPPFLAGS_SAVED="$CPPFLAGS" 59 | CPPFLAGS="$CPPFLAGS $BOOST_CPPFLAGS" 60 | export CPPFLAGS 61 | 62 | LDFLAGS_SAVED="$LDFLAGS" 63 | LDFLAGS="$LDFLAGS $BOOST_LDFLAGS" 64 | export LDFLAGS 65 | 66 | LIBS_SAVED=$LIBS 67 | LIBS="$LIBS $BOOST_SYSTEM_LIB" 68 | export LIBS 69 | 70 | AC_CACHE_CHECK(whether the Boost::Filesystem library is available, 71 | ax_cv_boost_filesystem, 72 | [AC_LANG_PUSH([C++]) 73 | AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[@%:@include ]], 74 | [[using namespace boost::filesystem; 75 | path my_path( "foo/bar/data.txt" ); 76 | return 0;]])], 77 | ax_cv_boost_filesystem=yes, ax_cv_boost_filesystem=no) 78 | AC_LANG_POP([C++]) 79 | ]) 80 | if test "x$ax_cv_boost_filesystem" = "xyes"; then 81 | AC_DEFINE(HAVE_BOOST_FILESYSTEM,,[define if the Boost::Filesystem library is available]) 82 | BOOSTLIBDIR=`echo $BOOST_LDFLAGS | sed -e 's/@<:@^\/@:>@*//'` 83 | if test "x$ax_boost_user_filesystem_lib" = "x"; then 84 | for libextension in `ls -r $BOOSTLIBDIR/libboost_filesystem* 2>/dev/null | sed 's,.*/lib,,' | sed 's,\..*,,'` ; do 85 | ax_lib=${libextension} 86 | AC_CHECK_LIB($ax_lib, exit, 87 | [BOOST_FILESYSTEM_LIB="-l$ax_lib"; AC_SUBST(BOOST_FILESYSTEM_LIB) link_filesystem="yes"; break], 88 | [link_filesystem="no"]) 89 | done 90 | if test "x$link_filesystem" != "xyes"; then 91 | for libextension in `ls -r $BOOSTLIBDIR/boost_filesystem* 2>/dev/null | sed 's,.*/,,' | sed -e 's,\..*,,'` ; do 92 | ax_lib=${libextension} 93 | AC_CHECK_LIB($ax_lib, exit, 94 | [BOOST_FILESYSTEM_LIB="-l$ax_lib"; AC_SUBST(BOOST_FILESYSTEM_LIB) link_filesystem="yes"; break], 95 | [link_filesystem="no"]) 96 | done 97 | fi 98 | else 99 | for ax_lib in $ax_boost_user_filesystem_lib boost_filesystem-$ax_boost_user_filesystem_lib; do 100 | AC_CHECK_LIB($ax_lib, exit, 101 | [BOOST_FILESYSTEM_LIB="-l$ax_lib"; AC_SUBST(BOOST_FILESYSTEM_LIB) link_filesystem="yes"; break], 102 | [link_filesystem="no"]) 103 | done 104 | 105 | fi 106 | if test "x$ax_lib" = "x"; then 107 | AC_MSG_ERROR(Could not find a version of the library!) 108 | fi 109 | if test "x$link_filesystem" != "xyes"; then 110 | AC_MSG_ERROR(Could not link against $ax_lib !) 111 | fi 112 | fi 113 | 114 | CPPFLAGS="$CPPFLAGS_SAVED" 115 | LDFLAGS="$LDFLAGS_SAVED" 116 | LIBS="$LIBS_SAVED" 117 | fi 118 | ]) 119 | -------------------------------------------------------------------------------- /m4/ax_boost_base.m4: -------------------------------------------------------------------------------- 1 | # =========================================================================== 2 | # http://www.gnu.org/software/autoconf-archive/ax_boost_base.html 3 | # =========================================================================== 4 | # 5 | # SYNOPSIS 6 | # 7 | # AX_BOOST_BASE([MINIMUM-VERSION], [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND]) 8 | # 9 | # DESCRIPTION 10 | # 11 | # Test for the Boost C++ libraries of a particular version (or newer) 12 | # 13 | # If no path to the installed boost library is given the macro searchs 14 | # under /usr, /usr/local, /opt and /opt/local and evaluates the 15 | # $BOOST_ROOT environment variable. Further documentation is available at 16 | # . 17 | # 18 | # This macro calls: 19 | # 20 | # AC_SUBST(BOOST_CPPFLAGS) / AC_SUBST(BOOST_LDFLAGS) 21 | # 22 | # And sets: 23 | # 24 | # HAVE_BOOST 25 | # 26 | # LICENSE 27 | # 28 | # Copyright (c) 2008 Thomas Porschberg 29 | # Copyright (c) 2009 Peter Adolphs 30 | # 31 | # Copying and distribution of this file, with or without modification, are 32 | # permitted in any medium without royalty provided the copyright notice 33 | # and this notice are preserved. This file is offered as-is, without any 34 | # warranty. 35 | 36 | #serial 25 37 | 38 | AC_DEFUN([AX_BOOST_BASE], 39 | [ 40 | AC_ARG_WITH([boost], 41 | [AS_HELP_STRING([--with-boost@<:@=ARG@:>@], 42 | [use Boost library from a standard location (ARG=yes), 43 | from the specified location (ARG=), 44 | or disable it (ARG=no) 45 | @<:@ARG=yes@:>@ ])], 46 | [ 47 | if test "$withval" = "no"; then 48 | want_boost="no" 49 | elif test "$withval" = "yes"; then 50 | want_boost="yes" 51 | ac_boost_path="" 52 | else 53 | want_boost="yes" 54 | ac_boost_path="$withval" 55 | fi 56 | ], 57 | [want_boost="yes"]) 58 | 59 | 60 | AC_ARG_WITH([boost-libdir], 61 | AS_HELP_STRING([--with-boost-libdir=LIB_DIR], 62 | [Force given directory for boost libraries. Note that this will override library path detection, so use this parameter only if default library detection fails and you know exactly where your boost libraries are located.]), 63 | [ 64 | if test -d "$withval" 65 | then 66 | ac_boost_lib_path="$withval" 67 | else 68 | AC_MSG_ERROR(--with-boost-libdir expected directory name) 69 | fi 70 | ], 71 | [ac_boost_lib_path=""] 72 | ) 73 | 74 | if test "x$want_boost" = "xyes"; then 75 | boost_lib_version_req=ifelse([$1], ,1.20.0,$1) 76 | boost_lib_version_req_shorten=`expr $boost_lib_version_req : '\([[0-9]]*\.[[0-9]]*\)'` 77 | boost_lib_version_req_major=`expr $boost_lib_version_req : '\([[0-9]]*\)'` 78 | boost_lib_version_req_minor=`expr $boost_lib_version_req : '[[0-9]]*\.\([[0-9]]*\)'` 79 | boost_lib_version_req_sub_minor=`expr $boost_lib_version_req : '[[0-9]]*\.[[0-9]]*\.\([[0-9]]*\)'` 80 | if test "x$boost_lib_version_req_sub_minor" = "x" ; then 81 | boost_lib_version_req_sub_minor="0" 82 | fi 83 | WANT_BOOST_VERSION=`expr $boost_lib_version_req_major \* 100000 \+ $boost_lib_version_req_minor \* 100 \+ $boost_lib_version_req_sub_minor` 84 | AC_MSG_CHECKING(for boostlib >= $boost_lib_version_req) 85 | succeeded=no 86 | 87 | dnl On 64-bit systems check for system libraries in both lib64 and lib. 88 | dnl The former is specified by FHS, but e.g. Debian does not adhere to 89 | dnl this (as it rises problems for generic multi-arch support). 90 | dnl The last entry in the list is chosen by default when no libraries 91 | dnl are found, e.g. when only header-only libraries are installed! 92 | libsubdirs="lib" 93 | ax_arch=`uname -m` 94 | case $ax_arch in 95 | x86_64) 96 | libsubdirs="lib64 libx32 lib lib64" 97 | ;; 98 | ppc64|s390x|sparc64|aarch64|ppc64le) 99 | libsubdirs="lib64 lib lib64 ppc64le" 100 | ;; 101 | esac 102 | 103 | dnl allow for real multi-arch paths e.g. /usr/lib/x86_64-linux-gnu. Give 104 | dnl them priority over the other paths since, if libs are found there, they 105 | dnl are almost assuredly the ones desired. 106 | AC_REQUIRE([AC_CANONICAL_HOST]) 107 | libsubdirs="lib/${host_cpu}-${host_os} $libsubdirs" 108 | 109 | case ${host_cpu} in 110 | i?86) 111 | libsubdirs="lib/i386-${host_os} $libsubdirs" 112 | ;; 113 | esac 114 | 115 | dnl first we check the system location for boost libraries 116 | dnl this location ist chosen if boost libraries are installed with the --layout=system option 117 | dnl or if you install boost with RPM 118 | if test "$ac_boost_path" != ""; then 119 | BOOST_CPPFLAGS="-I$ac_boost_path/include" 120 | for ac_boost_path_tmp in $libsubdirs; do 121 | if test -d "$ac_boost_path"/"$ac_boost_path_tmp" ; then 122 | BOOST_LDFLAGS="-L$ac_boost_path/$ac_boost_path_tmp" 123 | break 124 | fi 125 | done 126 | elif test "$cross_compiling" != yes; then 127 | for ac_boost_path_tmp in /usr /usr/local /opt /opt/local ; do 128 | if test -d "$ac_boost_path_tmp/include/boost" && test -r "$ac_boost_path_tmp/include/boost"; then 129 | for libsubdir in $libsubdirs ; do 130 | if ls "$ac_boost_path_tmp/$libsubdir/libboost_"* >/dev/null 2>&1 ; then break; fi 131 | done 132 | BOOST_LDFLAGS="-L$ac_boost_path_tmp/$libsubdir" 133 | BOOST_CPPFLAGS="-I$ac_boost_path_tmp/include" 134 | break; 135 | fi 136 | done 137 | fi 138 | 139 | dnl overwrite ld flags if we have required special directory with 140 | dnl --with-boost-libdir parameter 141 | if test "$ac_boost_lib_path" != ""; then 142 | BOOST_LDFLAGS="-L$ac_boost_lib_path" 143 | fi 144 | 145 | CPPFLAGS_SAVED="$CPPFLAGS" 146 | CPPFLAGS="$CPPFLAGS $BOOST_CPPFLAGS" 147 | export CPPFLAGS 148 | 149 | LDFLAGS_SAVED="$LDFLAGS" 150 | LDFLAGS="$LDFLAGS $BOOST_LDFLAGS" 151 | export LDFLAGS 152 | 153 | AC_REQUIRE([AC_PROG_CXX]) 154 | AC_LANG_PUSH(C++) 155 | AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ 156 | @%:@include 157 | ]], [[ 158 | #if BOOST_VERSION >= $WANT_BOOST_VERSION 159 | // Everything is okay 160 | #else 161 | # error Boost version is too old 162 | #endif 163 | ]])],[ 164 | AC_MSG_RESULT(yes) 165 | succeeded=yes 166 | found_system=yes 167 | ],[ 168 | ]) 169 | AC_LANG_POP([C++]) 170 | 171 | 172 | 173 | dnl if we found no boost with system layout we search for boost libraries 174 | dnl built and installed without the --layout=system option or for a staged(not installed) version 175 | if test "x$succeeded" != "xyes"; then 176 | _version=0 177 | if test "$ac_boost_path" != ""; then 178 | if test -d "$ac_boost_path" && test -r "$ac_boost_path"; then 179 | for i in `ls -d $ac_boost_path/include/boost-* 2>/dev/null`; do 180 | _version_tmp=`echo $i | sed "s#$ac_boost_path##" | sed 's/\/include\/boost-//' | sed 's/_/./'` 181 | V_CHECK=`expr $_version_tmp \> $_version` 182 | if test "$V_CHECK" = "1" ; then 183 | _version=$_version_tmp 184 | fi 185 | VERSION_UNDERSCORE=`echo $_version | sed 's/\./_/'` 186 | BOOST_CPPFLAGS="-I$ac_boost_path/include/boost-$VERSION_UNDERSCORE" 187 | done 188 | fi 189 | else 190 | if test "$cross_compiling" != yes; then 191 | for ac_boost_path in /usr /usr/local /opt /opt/local ; do 192 | if test -d "$ac_boost_path" && test -r "$ac_boost_path"; then 193 | for i in `ls -d $ac_boost_path/include/boost-* 2>/dev/null`; do 194 | _version_tmp=`echo $i | sed "s#$ac_boost_path##" | sed 's/\/include\/boost-//' | sed 's/_/./'` 195 | V_CHECK=`expr $_version_tmp \> $_version` 196 | if test "$V_CHECK" = "1" ; then 197 | _version=$_version_tmp 198 | best_path=$ac_boost_path 199 | fi 200 | done 201 | fi 202 | done 203 | 204 | VERSION_UNDERSCORE=`echo $_version | sed 's/\./_/'` 205 | BOOST_CPPFLAGS="-I$best_path/include/boost-$VERSION_UNDERSCORE" 206 | if test "$ac_boost_lib_path" = ""; then 207 | for libsubdir in $libsubdirs ; do 208 | if ls "$best_path/$libsubdir/libboost_"* >/dev/null 2>&1 ; then break; fi 209 | done 210 | BOOST_LDFLAGS="-L$best_path/$libsubdir" 211 | fi 212 | fi 213 | 214 | if test "x$BOOST_ROOT" != "x"; then 215 | for libsubdir in $libsubdirs ; do 216 | if ls "$BOOST_ROOT/stage/$libsubdir/libboost_"* >/dev/null 2>&1 ; then break; fi 217 | done 218 | if test -d "$BOOST_ROOT" && test -r "$BOOST_ROOT" && test -d "$BOOST_ROOT/stage/$libsubdir" && test -r "$BOOST_ROOT/stage/$libsubdir"; then 219 | version_dir=`expr //$BOOST_ROOT : '.*/\(.*\)'` 220 | stage_version=`echo $version_dir | sed 's/boost_//' | sed 's/_/./g'` 221 | stage_version_shorten=`expr $stage_version : '\([[0-9]]*\.[[0-9]]*\)'` 222 | V_CHECK=`expr $stage_version_shorten \>\= $_version` 223 | if test "$V_CHECK" = "1" -a "$ac_boost_lib_path" = "" ; then 224 | AC_MSG_NOTICE(We will use a staged boost library from $BOOST_ROOT) 225 | BOOST_CPPFLAGS="-I$BOOST_ROOT" 226 | BOOST_LDFLAGS="-L$BOOST_ROOT/stage/$libsubdir" 227 | fi 228 | fi 229 | fi 230 | fi 231 | 232 | CPPFLAGS="$CPPFLAGS $BOOST_CPPFLAGS" 233 | export CPPFLAGS 234 | LDFLAGS="$LDFLAGS $BOOST_LDFLAGS" 235 | export LDFLAGS 236 | 237 | AC_LANG_PUSH(C++) 238 | AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ 239 | @%:@include 240 | ]], [[ 241 | #if BOOST_VERSION >= $WANT_BOOST_VERSION 242 | // Everything is okay 243 | #else 244 | # error Boost version is too old 245 | #endif 246 | ]])],[ 247 | AC_MSG_RESULT(yes) 248 | succeeded=yes 249 | found_system=yes 250 | ],[ 251 | ]) 252 | AC_LANG_POP([C++]) 253 | fi 254 | 255 | if test "$succeeded" != "yes" ; then 256 | if test "$_version" = "0" ; then 257 | AC_MSG_NOTICE([[We could not detect the boost libraries (version $boost_lib_version_req_shorten or higher). If you have a staged boost library (still not installed) please specify \$BOOST_ROOT in your environment and do not give a PATH to --with-boost option. If you are sure you have boost installed, then check your version number looking in . See http://randspringer.de/boost for more documentation.]]) 258 | else 259 | AC_MSG_NOTICE([Your boost libraries seems to old (version $_version).]) 260 | fi 261 | # execute ACTION-IF-NOT-FOUND (if present): 262 | ifelse([$3], , :, [$3]) 263 | else 264 | AC_SUBST(BOOST_CPPFLAGS) 265 | AC_SUBST(BOOST_LDFLAGS) 266 | AC_DEFINE(HAVE_BOOST,,[define if the Boost library is available]) 267 | # execute ACTION-IF-FOUND (if present): 268 | ifelse([$2], , :, [$2]) 269 | fi 270 | 271 | CPPFLAGS="$CPPFLAGS_SAVED" 272 | LDFLAGS="$LDFLAGS_SAVED" 273 | fi 274 | 275 | ]) 276 | -------------------------------------------------------------------------------- /Tokenizer.d: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014- Facebook 2 | // License: Boost License 1.0, http://boost.org/LICENSE_1_0.txt 3 | // @author Andrei Alexandrescu (andrei.alexandrescu@facebook.com) 4 | 5 | import std.algorithm, std.array, std.ascii, std.conv, std.exception, std.regex, 6 | std.stdio, std.typecons, std.typetuple, std.format; 7 | 8 | struct TokenizerGenerator(alias tokens, alias reservedTokens) { 9 | /** 10 | * All token types include regular and reservedTokens, plus the null 11 | * token ("") and the end-of-stream token ("\0"). 12 | */ 13 | private enum totalTokens = tokens.length + reservedTokens.length + 2; 14 | 15 | /** 16 | * Representation for token types. 17 | */ 18 | static if (totalTokens <= ubyte.max) 19 | alias TokenIDRep = ubyte; 20 | else static if (totalTokens <= ushort.max) 21 | alias TokenIDRep = ushort; 22 | else // seriously? 23 | alias TokenIDRep = uint; 24 | 25 | struct TokenType2 { 26 | TokenIDRep id; 27 | 28 | string sym() const { 29 | assert(id <= tokens.length + reservedTokens.length + 2, text(id)); 30 | if (id == 0) return ""; 31 | auto i = id - 1; 32 | if (i >= tokens.length + reservedTokens.length) return "\0"; 33 | return i < tokens.length 34 | ? tokens[i] 35 | : reservedTokens[i - tokens.length]; 36 | } 37 | } 38 | 39 | template tk(string symbol) { 40 | import std.range; 41 | static if (symbol == "") { 42 | // Token ID 0 is reserved for "unrecognized token". 43 | enum tk = TokenType2(0); 44 | } else static if (symbol == "\0") { 45 | // Token ID max is reserved for "end of input". 46 | enum tk = TokenType2( 47 | cast(TokenIDRep) (1 + tokens.length + reservedTokens.length)); 48 | } else { 49 | //enum id = chain(tokens, reservedTokens).countUntil(symbol); 50 | // Find the id within the regular tokens realm 51 | enum idTokens = tokens.countUntil(symbol); 52 | static if (idTokens >= 0) { 53 | // Found, regular token. Add 1 because 0 is reserved. 54 | enum id = idTokens + 1; 55 | } else { 56 | // not found, only chance is within the reserved tokens realm 57 | enum idResTokens = reservedTokens.countUntil(symbol); 58 | enum id = idResTokens >= 0 ? tokens.length + idResTokens + 1 : -1; 59 | } 60 | static assert(id >= 0 && id < TokenIDRep.max, 61 | "Invalid token: " ~ symbol); 62 | enum tk = TokenType2(cast(ubyte) id); 63 | } 64 | } 65 | 66 | /** 67 | * Defines one token together with file and line information. The 68 | * precedingComment_ is set if there was one comment before the token. 69 | */ 70 | struct Token { 71 | TokenType2 type_; 72 | string value_; 73 | string precedingWhitespace_; 74 | size_t line_; 75 | string file_; 76 | 77 | string value() const { 78 | return value_ ? value_ : type_.sym; 79 | } 80 | }; 81 | 82 | static string generateCases(string[] tokens, size_t index = 0, 83 | bool* mayFallThrough = null) { 84 | assert(tokens.length > 1); 85 | 86 | static bool mustEscape(char c) { 87 | return c == '\\' || c == '"' || c == '\''; 88 | } 89 | 90 | static string escape(string s) { 91 | string result; 92 | foreach (c; s) { 93 | if (mustEscape(c)) result ~= "\\"; 94 | result ~= c; 95 | } 96 | return result; 97 | } 98 | 99 | string result; 100 | for (size_t i = 0; i < tokens.length; ++i) { 101 | if (index >= tokens[i].length) { 102 | result ~= "default: t = tk!\"" 103 | ~ tokens[i] ~ "\"; break token_search;\n"; 104 | } else { 105 | result ~= "case '" ~ escape(tokens[i][index .. index + 1]) ~ "': "; 106 | auto j = i + 1; 107 | while (j < tokens.length && tokens[j][index] == tokens[i][index]) { 108 | ++j; 109 | } 110 | // i through j-1 are tokens with the same prefix 111 | if (j == i + 1) { 112 | if (tokens[i].length > index + 1) { 113 | result ~= "if ("; 114 | result ~= tokens[i].length == index + 2 115 | ? ("pc["~to!string(index + 1)~"] == '" 116 | ~ escape(tokens[i][index + 1 .. index + 2]) ~ "') ") 117 | : ("pc["~to!string(index + 1)~" .. $].startsWith(\"" 118 | ~ escape(tokens[i][index + 1 .. $]) ~ "\")) "); 119 | result ~= "{ t = tk!\"" 120 | ~ escape(tokens[i]) ~ 121 | "\"; break token_search; } else break;\n"; 122 | if (mayFallThrough) *mayFallThrough = true; 123 | } else { 124 | result ~= "t = tk!\"" ~ escape(tokens[i]) 125 | ~ "\"; break token_search;\n"; 126 | } 127 | continue; 128 | } 129 | auto endOfToken = false; 130 | foreach (k; i .. j) { 131 | if (index + 1 >= tokens[k].length) { 132 | endOfToken = true; 133 | break; 134 | } 135 | } 136 | result ~= "switch (pc["~to!string(index + 1)~"]) {\n"; 137 | if (!endOfToken) result ~= "default: break;\n"; 138 | bool mft; 139 | result ~= generateCases(tokens[i .. j], index + 1, &mft) 140 | ~ "}"; 141 | if (!endOfToken || mft) { 142 | result ~= " break;\n"; 143 | if (mayFallThrough) *mayFallThrough = true; 144 | } 145 | else { 146 | result ~= "\n"; 147 | } 148 | i = j - 1; 149 | } 150 | } 151 | return result; 152 | } 153 | 154 | //pragma(msg, generateCases([NonAlphaTokens, Keywords])); 155 | 156 | static Tuple!(size_t, size_t, TokenType2) 157 | match(R)(R r) { 158 | size_t charsBefore = 0, linesBefore = 0; 159 | TokenType2 t; 160 | auto pc = r; 161 | 162 | token_search: 163 | for (;;) { 164 | assert(pc.length); 165 | switch (pc[0]) { 166 | case '\n': 167 | ++linesBefore; 168 | goto case; // fall through to also increment charsBefore 169 | case ' ': case '\t': case '\r': 170 | ++charsBefore; 171 | pc = pc[1 .. $]; 172 | continue; 173 | case '\0': 174 | // done! 175 | t = tk!"\0"; 176 | break token_search; 177 | default: 178 | break; 179 | // Bunch of cases generated here 180 | mixin(generateCases(tokens)); 181 | } 182 | // Couldn't match any token 183 | t = tk!""; 184 | break; 185 | } 186 | return tuple(linesBefore, charsBefore, t); 187 | } 188 | } 189 | 190 | alias NonAlphaTokens = TypeTuple!( 191 | "~", "(", ")", "[", "]", "{", "}", ";", ",", "?", 192 | "<", "<=", "<<", "<<=", ">", ">=", ">>", ">>=", "%", "%=", "=", "==", 193 | "!", "!=", "^", "^=", "*", "*=", 194 | ":", "::", "+", "++", "+=", "&", "&&", "&=", "|", "||", "|=", 195 | "-", "--", "-=", "->", "->*", 196 | "/", "/=", "//", "/*", 197 | "\\", 198 | ".", ".*", "...", 199 | "'", 200 | "\"", 201 | "#", "##", 202 | "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", 203 | "@", "$", // allow as extensions 204 | "R\"(", 205 | ); 206 | 207 | alias Keywords = TypeTuple!( 208 | "and", "and_eq", "asm", "auto", 209 | "bitand", "bitor", "bool", "break", 210 | "case", "catch", "char", "class", "compl", "const", "const_cast", "constexpr", 211 | "continue", 212 | "default", "delete", "do", "double", "dynamic_cast", 213 | "else", "enum", "explicit", "extern", "false", "float", "for", "friend", 214 | "goto", 215 | "if", "inline", "int", 216 | "long", 217 | "mutable", 218 | "namespace", "new", "noexcept", "not", "not_eq", 219 | "operator", "or", "or_eq", 220 | "private", "protected", "public", 221 | "register", "reinterpret_cast", "return", 222 | "short", "signed", "sizeof", "static", "static_cast", "struct", "switch", 223 | "template", "this", "throw", "true", "try", "typedef", "typeid", "typename", 224 | "union", "unsigned", "using", 225 | "virtual", "void", "volatile", 226 | "wchar_t", "while", 227 | "xor", "xor_eq", 228 | ); 229 | 230 | static immutable string[] specialTokens = [ 231 | "identifier", "number", "string_literal", "char_literal", 232 | "preprocessor_directive" 233 | ]; 234 | 235 | alias TokenizerGenerator!([NonAlphaTokens, Keywords], specialTokens) 236 | CppLexer; 237 | alias tk = CppLexer.tk; 238 | alias Token = CppLexer.Token; 239 | alias TokenType = CppLexer.TokenType2; 240 | 241 | /** 242 | * This is the quintessential function. Given a string containing C++ 243 | * code and a filename, fills output with the tokens in the 244 | * file. Warning - don't use temporaries for input and filename 245 | * because the resulting tokens contain StringPiece objects pointing 246 | * into them. 247 | */ 248 | CppLexer.Token[] tokenize(string input, string initialFilename = null) { 249 | input ~= '\0'; // TODO revisit this 250 | CppLexer.Token[] output; 251 | auto file = initialFilename; 252 | size_t line = 1; 253 | 254 | for (;;) { 255 | auto t = nextToken(input, line, initialFilename); 256 | t.file_ = initialFilename; 257 | //writeln(t); 258 | output ~= t; 259 | if (t.type_ is CppLexer.tk!"\0") break; 260 | } 261 | 262 | return output; 263 | } 264 | 265 | void tokenize(string input, string initialFilename, ref CppLexer.Token[] t) { 266 | t = tokenize(input, initialFilename); 267 | } 268 | 269 | /** 270 | * Helper function, gets next token and updates pc and line. 271 | */ 272 | CppLexer.Token nextToken(ref string pc, ref size_t line, ref string fileName) { 273 | size_t charsBefore; 274 | string value; 275 | CppLexer.TokenType2 tt; 276 | auto initialPc = pc; 277 | auto initialLine = line; 278 | size_t tokenLine; 279 | 280 | for (;;) { 281 | auto t = CppLexer.match(pc); 282 | line += t[0]; 283 | tokenLine = line; 284 | charsBefore += t[1]; 285 | tt = t[2]; 286 | 287 | // Position pc to advance past the leading whitespace 288 | pc = pc[t[1] .. $]; 289 | 290 | // Unrecognized token? 291 | if (tt is tk!"") { 292 | auto c = pc[0]; 293 | if (std.ascii.isAlpha(c) || c == '_' || c == '$' || c == '@') { 294 | value = munchIdentifier(pc); 295 | //writeln("sym: ", value); 296 | tt = tk!"identifier"; 297 | break; 298 | } else { 299 | auto errWriter = appender!string(); 300 | formattedWrite(errWriter, "Illegal character: %s [%s] at: %s:%s", 301 | cast(uint) c, c, fileName, line); 302 | throw new Exception(errWriter.data); 303 | } 304 | } 305 | 306 | assert(tt.sym.length >= 1, "'" ~ tt.sym ~ "'"); 307 | // Number? 308 | if (isDigit(tt.sym[0]) || tt is tk!"." && isDigit(pc[1])) { 309 | tt = tk!"number"; 310 | value = munchNumber(pc); 311 | break; 312 | } 313 | 314 | // Single-line comment? 315 | if (tt is tk!"//") { 316 | charsBefore += munchSingleLineComment(pc, line).length; 317 | continue; 318 | } 319 | 320 | // Multi-line comment? 321 | if (tt is tk!"/*") { 322 | charsBefore += munchComment(pc, line).length; 323 | continue; 324 | } 325 | 326 | // #pragma/#error/#warning preprocessor directive (except #pragma once)? 327 | if (tt == tk!"#" && match(pc, ctRegex!(`^#\s*(error|warning|pragma)\s`)) 328 | && !match(pc, ctRegex!(`^#\s*pragma\s+once`))) { 329 | value = munchPreprocessorDirective(pc, line); 330 | tt = tk!"preprocessor_directive"; 331 | break; 332 | } 333 | 334 | // Literal string? 335 | if (tt is tk!"\"") { 336 | value = munchString(pc, line); 337 | tt = tk!"string_literal"; 338 | break; 339 | } 340 | 341 | //Raw string? 342 | if (tt is tk!"R\"(") { 343 | value = munchRawString(pc, line); 344 | tt = tk!"string_literal"; 345 | break; 346 | } 347 | 348 | // Literal char? 349 | if (tt is tk!"'") { 350 | value = munchCharLiteral(pc, line); 351 | tt = tk!"char_literal"; 352 | break; 353 | } 354 | 355 | // Keyword or identifier? 356 | char c = tt.sym[0]; 357 | if (std.ascii.isAlpha(c) || c == '_' || c == '$' || c == '@') { 358 | // This is a keyword, but it may be a prefix of a longer symbol 359 | assert(pc.length >= tt.sym.length, text(tt.sym, ": ", pc)); 360 | c = pc[tt.sym.length]; 361 | if (isAlphaNum(c) || c == '_' || c == '$' || c == '@') { 362 | // yep, longer symbol 363 | value = munchIdentifier(pc); 364 | // writeln("sym: ", tt.symbol, symbol); 365 | tt = tk!"identifier"; 366 | } else { 367 | // Keyword, just update the pc 368 | pc = pc[tt.sym.length .. $]; 369 | } 370 | break; 371 | } 372 | 373 | // End of stream? 374 | if (tt is tk!"\0") { 375 | break; 376 | } 377 | 378 | // We have something! 379 | pc = pc[tt.sym.length .. $]; 380 | break; 381 | } 382 | 383 | version (unittest) { 384 | // make sure the we munched the right number of characters 385 | auto delta = initialPc.length - pc.length; 386 | if (tt is tk!"\0") delta += 1; 387 | auto tsz = charsBefore + (value ? value.length : tt.sym().length); 388 | if (tsz != delta) { 389 | auto errWriter = appender!string(); 390 | formattedWrite(errWriter, 391 | "Internal flint tokenization error: Wrong size for " ~ 392 | "token type '%s': '%s'~'%s' of size %s != '%s' of size %s", 393 | tt.sym(), initialPc[0 .. charsBefore], value, tsz, 394 | initialPc[0 .. delta], delta); 395 | throw new Exception(errWriter.data); 396 | } 397 | 398 | // make sure that line was incremented the correct number of times 399 | auto lskip = std.algorithm.count(initialPc[0 .. delta], '\n'); 400 | if (initialLine + lskip != line) { 401 | auto errWriter = appender!string(); 402 | formattedWrite(errWriter, "Internal flint tokenization error: munched " ~ 403 | "'%s' (token type '%s') which contains %s newlines, but " ~ 404 | "line has been incremented by %s", 405 | initialPc[0 .. delta], tt.sym(), lskip, 406 | line - initialLine); 407 | throw new Exception(errWriter.data); 408 | } 409 | } 410 | 411 | return CppLexer.Token( 412 | tt, value, 413 | initialPc[0 .. charsBefore], 414 | tokenLine, fileName); 415 | } 416 | 417 | /** 418 | * Eats howMany characters out of pc, avances pc appropriately, and 419 | * returns the eaten portion. 420 | */ 421 | static string munchChars(ref string pc, size_t howMany) { 422 | assert(pc.length >= howMany); 423 | auto result = pc[0 .. howMany]; 424 | pc = pc[howMany .. $]; 425 | return result; 426 | } 427 | 428 | /** 429 | * Assuming pc is positioned at the start of a single-line comment, 430 | * munches it from pc and returns it. 431 | */ 432 | static string munchSingleLineComment(ref string pc, ref size_t line) { 433 | for (size_t i = 0; ; ++i) { 434 | assert(i < pc.length); 435 | auto c = pc[i]; 436 | if (c == '\n') { 437 | ++line; 438 | if (i > 0 && pc[i - 1] == '\\') { 439 | // multiline single-line comment (sic) 440 | continue; 441 | } 442 | // end of comment 443 | return munchChars(pc, i + 1); 444 | } 445 | if (!c) { 446 | // single-line comment at end of file, meh 447 | return munchChars(pc, i); 448 | } 449 | } 450 | assert(false); 451 | } 452 | 453 | /** 454 | * Assuming pc is positioned at the start of a C-style comment, 455 | * munches it from pc and returns it. 456 | */ 457 | static string munchComment(ref string pc, ref size_t line) { 458 | //assert(pc[0] == '/' && pc[1] == '*'); 459 | for (size_t i = 0; ; ++i) { 460 | assert(i < pc.length); 461 | auto c = pc[i]; 462 | if (c == '\n') { 463 | ++line; 464 | } 465 | else if (c == '*') { 466 | if (pc[i + 1] == '/') { 467 | // end of comment 468 | return munchChars(pc, i + 2); 469 | } 470 | } 471 | else if (!c) { 472 | // end of input 473 | enforce(false, "Unterminated comment: ", pc); 474 | } 475 | } 476 | assert(false); 477 | } 478 | 479 | /** 480 | * Assuming pc is positioned at the start of a specified preprocessor directive, 481 | * munches it from pc and returns it. 482 | */ 483 | static string munchPreprocessorDirective(ref string pc, ref size_t line) { 484 | for (size_t i = 0; ; ++i) { 485 | assert(i < pc.length); 486 | auto c = pc[i]; 487 | if (c == '\n') { 488 | if (i > 0 && pc[i - 1] == '\\') { 489 | // multiline directive 490 | ++line; 491 | continue; 492 | } 493 | // end of directive 494 | return munchChars(pc, i); 495 | } 496 | if (!c) { 497 | // directive at end of file 498 | return munchChars(pc, i); 499 | } 500 | } 501 | } 502 | 503 | /** 504 | * Assuming pc is positioned at the start of an identifier, munches it 505 | * from pc and returns it. 506 | */ 507 | static string munchIdentifier(ref string pc) { 508 | for (size_t i = 0; ; ++i) { 509 | assert(i < pc.length); 510 | const c = pc[i]; 511 | // g++ allows '$' in identifiers. Also, some crazy inline 512 | // assembler uses '@' in identifiers, see e.g. 513 | // fbcode/external/cryptopp/rijndael.cpp, line 527 514 | if (!isAlphaNum(c) && c != '_' && c != '$' && c != '@') { 515 | // done 516 | enforce(i > 0, "Invalid identifier: ", pc); 517 | return munchChars(pc, i); 518 | } 519 | } 520 | assert(false); 521 | } 522 | 523 | /** 524 | * Assuming pc is positioned at the start of a string literal, munches 525 | * it from pc and returns it. A reference to line is passed in order 526 | * to track multiline strings correctly. 527 | */ 528 | static string munchString(ref string pc, ref size_t line) { 529 | assert(pc[0] == '"'); 530 | for (size_t i = 1; ; ++i) { 531 | const c = pc[i]; 532 | if (c == '"') { 533 | // That's about it 534 | return munchChars(pc, i + 1); 535 | } 536 | if (c == '\\') { 537 | ++i; 538 | if (pc[i] == '\n') { 539 | ++line; 540 | } 541 | continue; 542 | } 543 | enforce(c, "Unterminated string constant: ", pc); 544 | } 545 | } 546 | 547 | /** 548 | * Assuming pc is positioned at the start of a raw string, munches 549 | * it from pc and returns it. 550 | */ 551 | static string munchRawString(ref string pc, ref size_t line) { 552 | assert(pc.startsWith(`R"(`)); 553 | for (size_t i = 3; ; ++i) { 554 | const c = pc[i]; 555 | if (c == ')') { 556 | if (pc[i + 1] == '"') { 557 | //End of a raw string literal 558 | return munchChars(pc, i + 2); 559 | } 560 | } 561 | enforce(c, "Unterminated raw string: ", pc); 562 | } 563 | } 564 | 565 | /** 566 | * Assuming pc is positioned at the start of a number (be it decimal 567 | * or floating-point), munches it off pc and returns it. Note that the 568 | * number is assumed to be correct so a number of checks are not 569 | * necessary. 570 | */ 571 | static string munchNumber(ref string pc) { 572 | bool sawDot = false, sawExp = false, sawX = false, sawSuffix = false; 573 | for (size_t i = 0; ; ++i) { 574 | assert(i < pc.length); 575 | auto const c = pc[i]; 576 | if (c == '.' && !sawDot && !sawExp && !sawSuffix) { 577 | sawDot = true; 578 | } else if (isDigit(c)) { 579 | // Nothing to do 580 | } else if (sawX && !sawExp && c && "AaBbCcDdEeFf".canFind(c)) { 581 | // Hex digit; nothing to do. The condition includes !sawExp 582 | // because the exponent is decimal even in a hex floating-point 583 | // number! 584 | } else if (c == '+' || c == '-') { 585 | // Sign may appear at the start or right after E or P 586 | if (i > 0 && !"EePp".canFind(pc[i - 1])) { 587 | // Done, the sign is the next token 588 | return munchChars(pc, i); 589 | } 590 | } else if (!sawExp && !sawSuffix && !sawX && (c == 'e' || c == 'E')) { 591 | sawExp = true; 592 | } else if (sawX && !sawExp && !sawSuffix && (c == 'p' || c == 'P')) { 593 | sawExp = true; 594 | } else if ((c == 'x' || c == 'X') && i == 1 && pc[0] == '0') { 595 | sawX = true; 596 | } else if (c && "FfLlUu".canFind(c)) { 597 | // It's a suffix. There could be several of them (including 598 | // repeats a la LL), so let's not return just yet 599 | sawSuffix = true; 600 | } else { 601 | // done 602 | enforce(i > 0, "Invalid number: ", pc); 603 | return munchChars(pc, i); 604 | } 605 | } 606 | assert(false); 607 | } 608 | 609 | /** 610 | * Assuming pc is positioned at the start of a character literal, 611 | * munches it from pc and returns it. A reference to line is passed in 612 | * order to track multiline character literals (yeah, that can 613 | * actually happen) correctly. 614 | */ 615 | static string munchCharLiteral(ref string pc, ref size_t line) { 616 | assert(pc[0] == '\''); 617 | for (size_t i = 1; ; ++i) { 618 | auto const c = pc[i]; 619 | if (c == '\n') { 620 | ++line; 621 | } 622 | if (c == '\'') { 623 | // That's about it 624 | return munchChars(pc, i + 1); 625 | } 626 | if (c == '\\') { 627 | ++i; 628 | continue; 629 | } 630 | enforce(c, "Unterminated character constant: ", pc); 631 | } 632 | } 633 | -------------------------------------------------------------------------------- /Test.d: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014- Facebook 2 | // License: Boost License 1.0, http://boost.org/LICENSE_1_0.txt 3 | // @author Andrei Alexandrescu (andrei.alexandrescu@facebook.com) 4 | 5 | import std.array, std.conv, std.exception, std.random, std.stdio, std.file; 6 | import Checks, Tokenizer, FileCategories; 7 | 8 | unittest { 9 | EXPECT_EQ(FileCategory.header, getFileCategory("foo.h")); 10 | EXPECT_EQ(FileCategory.header, getFileCategory("baz/bar/foo.h")); 11 | EXPECT_EQ(FileCategory.inl_header, getFileCategory("foo-inl.h")); 12 | EXPECT_EQ(FileCategory.source_cpp, getFileCategory("foo.cpp")); 13 | EXPECT_EQ(FileCategory.source_c, getFileCategory("foo.c")); 14 | EXPECT_EQ(FileCategory.unknown, getFileCategory("foo")); 15 | 16 | assert(isHeader("foo.h")); 17 | assert(!isHeader("foo.cpp")); 18 | assert(!isHeader("foo.c")); 19 | 20 | assert(!isSource("foo.h")); 21 | assert(isSource("foo.cpp")); 22 | assert(isSource("foo.c")); 23 | 24 | assert(!isTestFile("foo.h")); 25 | assert(isTestFile("testFoo.h")); 26 | assert(isTestFile("/test/foo.h")); 27 | 28 | EXPECT_EQ("foo", getFileNameBase("foo.h")); 29 | EXPECT_EQ("foo", getFileNameBase("foo.cpp")); 30 | EXPECT_EQ("foo", getFileNameBase("foo.c")); 31 | } 32 | 33 | unittest { 34 | string s = " 35 | int main() 36 | __attribute__((foo)) 37 | 38 | // Two on one line: 39 | __attribute__ ((foo)) __attribute__((foo)) 40 | 41 | // Two parameters: 42 | __attribute__ ( ( foo, bar ) ) 43 | 44 | // Here, we ding only the 'format' keyword. 45 | __attribute__((format (printf, 1, 2))) 46 | 47 | // Here, now with the proper __format__, we ding the format 48 | // sub-type, 'printf': 49 | __attribute__((__format__ (printf, 1, 2))) 50 | 51 | // This one is fine. 52 | __attribute__((__format__ (__printf__, 1, 2))) 53 | { 54 | } 55 | "; 56 | auto tokens = tokenize(s, "nofile.cpp"); 57 | EXPECT_EQ(checkAttributeArgumentUnderscores("nofile.cpp", tokens), 6); 58 | } 59 | 60 | unittest { 61 | string s = " 62 | #include 63 | // Some code does this, (good!), and someday we may want to exempt 64 | // such usage from this lint rule. 65 | #define strncpy(d,s,n) do_not_use_this_function 66 | int main() { 67 | // Using strncpy(a,b,c) in a comment does not provoke a warning. 68 | char buf[10]; 69 | strncpy(buf, \"foo\", 3); 70 | return 0; 71 | } 72 | "; 73 | auto tokens = tokenize(s, "nofile.cpp"); 74 | EXPECT_EQ(checkBlacklistedIdentifiers("nofile.cpp", tokens), 2); 75 | } 76 | 77 | unittest { 78 | string s = " 79 | #include 80 | #include 81 | #include 82 | #include \"map\" 83 | int main() { 84 | std::vector s; 85 | std::map s; 86 | std::list s!@#1; 87 | std::foo s; 88 | std::find(); 89 | std::find_if(); 90 | std::remove(); 91 | vector s; 92 | map s; 93 | list s!@#1; 94 | foo s; 95 | find(); 96 | find_if(); 97 | remove(); 98 | } 99 | 100 | #include 101 | void foo() { 102 | std::find(); 103 | std::find_if(); 104 | std::remove(); 105 | } 106 | "; 107 | auto tokens = tokenize(s, "nofile.cpp"); 108 | EXPECT_EQ(checkDirectStdInclude("nofile.cpp", tokens), 5); 109 | } 110 | 111 | unittest { 112 | import std.file; 113 | string fpath = "linters/flint/test_files/Test1.cpp"; 114 | if (fpath.exists()) { 115 | string file = fpath.readText; 116 | auto tokens = tokenize(file, fpath); 117 | EXPECT_EQ(checkDirectStdInclude(fpath, tokens), 3); 118 | } 119 | } 120 | 121 | unittest { 122 | string s = " 123 | #include 124 | int main() { 125 | printf(\"hello, world\"); 126 | } 127 | "; 128 | auto tokens = tokenize(s, "nofile.cpp"); 129 | assert(tokens.length == 19, text(tokens.length)); 130 | s = ":: () [] . -> ++ -- dynamic_cast static_cast reinterpret_cast 131 | const_cast typeid ++ -- ~ ! sizeof new delete * & + - .* ->* * / % << >> 132 | < > <= >= == != & ^ | && || ?: = *= /= %= += -= >>= <<= &= ^= |= ,"; 133 | tokens = tokenize(s, "nofile.cpp"); 134 | const CppLexer.TokenType2[] witness = [ 135 | tk!"::", 136 | tk!"(", 137 | tk!")", 138 | tk!"[", 139 | tk!"]", 140 | tk!".", 141 | tk!"->", 142 | tk!"++", 143 | tk!"--", 144 | tk!"dynamic_cast", 145 | tk!"static_cast", 146 | tk!"reinterpret_cast", 147 | tk!"const_cast", 148 | tk!"typeid", 149 | tk!"++", 150 | tk!"--", 151 | tk!"~", 152 | tk!"!", 153 | tk!"sizeof", 154 | tk!"new", 155 | tk!"delete", 156 | tk!"*", 157 | tk!"&", 158 | tk!"+", 159 | tk!"-", 160 | tk!".*", 161 | tk!"->*", 162 | tk!"*", 163 | tk!"/", 164 | tk!"%", 165 | tk!"<<", 166 | tk!">>", 167 | tk!"<", 168 | tk!">", 169 | tk!"<=", 170 | tk!">=", 171 | tk!"==", 172 | tk!"!=", 173 | tk!"&", 174 | tk!"^", 175 | tk!"|", 176 | tk!"&&", 177 | tk!"||", 178 | tk!"?", 179 | tk!":", 180 | tk!"=", 181 | tk!"*=", 182 | tk!"/=", 183 | tk!"%=", 184 | tk!"+=", 185 | tk!"-=", 186 | tk!">>=", 187 | tk!"<<=", 188 | tk!"&=", 189 | tk!"^=", 190 | tk!"|=", 191 | tk!",", 192 | tk!"\0", 193 | ]; 194 | 195 | assert(tokens.length == witness.length, 196 | text(tokens.length, " != ", witness.length)); 197 | size_t j = 0; 198 | foreach (t; tokens) { 199 | assert(t.type_ == witness[j], text(t.type_, " !is ", witness[j])); 200 | ++j; 201 | } 202 | } 203 | 204 | // Test multiline single-line comment 205 | unittest { 206 | string s = " 207 | int x; 208 | // This is a single-line comment\\ 209 | that extends on multiple\\ 210 | lines. Nyuk-nyuk... 211 | float y;"; 212 | Token[] tokens = tokenize(s, "nofile.cpp"); 213 | assert(tokens.length == 7); 214 | assert(tokens[3].type_ == tk!"float"); 215 | } 216 | 217 | // Test #pragma/#error preprocessor directives are tokenized 218 | unittest { 219 | string s = " 220 | #error this is an error 221 | #pragma omp parallel for 222 | # error with some leading spaces 223 | #pragma once 224 | #error with line \\ 225 | break 226 | #warning warning warning"; 227 | Token[] tokens = tokenize(s, "nofile.cpp"); 228 | assert(tokens.length == 9); 229 | assert(tokens[0].type_ == tk!"preprocessor_directive" && tokens[0].value() == 230 | "#error this is an error"); 231 | assert(tokens[1].type_ == tk!"preprocessor_directive" && tokens[1].value() == 232 | "#pragma omp parallel for"); 233 | assert(tokens[2].type_ == tk!"preprocessor_directive" && tokens[2].value() == 234 | "# error with some leading spaces"); 235 | assert(tokens[3].type_ == tk!"#"); 236 | assert(tokens[4].type_ == tk!"identifier" && tokens[4].value() == "pragma"); 237 | assert(tokens[5].type_ == tk!"identifier" && tokens[5].value() == "once"); 238 | assert(tokens[6].type_ == tk!"preprocessor_directive" && tokens[6].value() == 239 | "#error with line \\\nbreak"); 240 | assert(tokens[7].type_ == tk!"preprocessor_directive" && tokens[7].value() == 241 | "#warning warning warning"); 242 | assert(tokens[8].type_ == tk!"\0"); 243 | } 244 | 245 | // Test numeric literals 246 | unittest { 247 | string s = " 248 | .123 249 | 123.234 250 | 123.234e345 251 | 123.234e+345 252 | 0x234p4 253 | 0xabcde 254 | 0x1.fffffffffffffp1023 255 | 0x1.0P-1074 256 | 0x0.0000000000001P-1022 257 | "; 258 | Token[] tokens = tokenize(s, "nofile.cpp"); 259 | // All except the ending TK_EOF are numbers 260 | foreach (ref t; tokens[0 .. $ - 1]) { 261 | assert(t.type_ == tk!"number"); 262 | } 263 | assert(tokens.length == 10); 264 | } 265 | 266 | // Test identifiers 267 | unittest { 268 | string s = " 269 | x 270 | X 271 | xy 272 | Xy 273 | xY 274 | _x 275 | x_ 276 | x$ 277 | x_y 278 | x$y 279 | xy_ 280 | xy$ 281 | "; 282 | Token[] tokens = tokenize(s, "nofile.cpp"); 283 | // All except the ending TK_EOF are numbers 284 | foreach (ref t; tokens[0 .. $ - 1]) { 285 | assert(t.type_ == tk!"identifier"); 286 | } 287 | assert(tokens.length == 13); 288 | } 289 | 290 | //Test strings 291 | unittest { 292 | string s = q"["common\n\"string\"\tliteral\\" 293 | R"(raw"st"r\ni\tng)" 294 | R"())" 295 | ]"; 296 | Token[] tokens = tokenize(s, "nofile.cpp"); 297 | //All except the ending TK_EOF are string_literal 298 | foreach (ref t; tokens[0 .. $ - 1]) { 299 | assert(t.type_ == tk!"string_literal"); 300 | } 301 | assert(tokens.length == 4); 302 | } 303 | 304 | // Test sequences 305 | unittest { 306 | string s = " 307 | asm volatile('mov eax, 10'); 308 | volatile int foo; 309 | class Foo { 310 | operator+(); 311 | } 312 | "; 313 | Token[] tokens = tokenize(s, "nofile.cpp"); 314 | EXPECT_EQ(checkBlacklistedSequences("nofile.cpp", tokens), 1); 315 | } 316 | 317 | // Test c_mode=true for sequences 318 | unittest { 319 | string s = " 320 | asm volatile('mov eax, 10'); 321 | volatile int foo; 322 | "; 323 | Token[] tokens = tokenize(s, "nofile.cpp"); 324 | c_mode = true; 325 | assert(checkBlacklistedSequences("nofile.cpp", tokens) == 0); 326 | c_mode = false; 327 | } 328 | 329 | unittest { 330 | string filename = "nofile.cpp"; 331 | 332 | string s = "( 333 | int main(int argc, char** argv) { 334 | auto p = strtok(argv[0], ','); 335 | while ((p = strtok(nullptr, ','))) { 336 | } 337 | } 338 | )"; 339 | Token[] tokens = tokenize(s, filename); 340 | assert(checkBlacklistedIdentifiers(filename, tokens) == 2); 341 | 342 | string s1 = "( 343 | int main(int argc, char** argv) { 344 | char* state; 345 | auto p = strtok_r(argv[0], ',', &state); 346 | while ((p = strtok_r(nullptr, ',', &state))) { 347 | } 348 | } 349 | )"; 350 | tokens = tokenize(s1, filename); 351 | assert(checkBlacklistedIdentifiers(filename, tokens) == 0); 352 | } 353 | 354 | // Test catch clauses in exceptions 355 | unittest { 356 | string s = " 357 | try {} catch (Exception &) {} 358 | try {} catch (Exception & e) {} 359 | try {} catch (ns::Exception &) {} 360 | try {} catch (const ns::Exception & e) {} 361 | try {} catch (::ns::Exception &) {} 362 | try {} catch (const ::ns::Exception & e) {} 363 | try {} catch (typename ::ns::Exception &) {} 364 | try {} catch (const typename ns::Exception & e) {} 365 | try {} catch (Exception &) {} 366 | try {} catch (const Exception<(1)> & x) {} 367 | try {} catch (...) {} 368 | "; 369 | auto tokens = tokenize(s, "nofile.cpp"); 370 | assert(checkCatchByReference("nofile.cpp", tokens) == 0); 371 | } 372 | 373 | // testCheckIfEndifBalance 374 | unittest { 375 | string s = " 376 | #ifndef A 377 | #if B 378 | #endif 379 | #endif 380 | "; 381 | auto tokens = tokenize(s); 382 | assert(tokens.atSequence(tk!"#", tk!"identifier")); 383 | EXPECT_EQ(checkIfEndifBalance("nofile.cpp", tokens), 0); 384 | 385 | string s2 = " 386 | #ifndef A 387 | #if B 388 | #ifdef C 389 | #endif 390 | #else 391 | #endif 392 | "; 393 | tokens = tokenize(s2); 394 | assert(checkIfEndifBalance("nofile.cpp", tokens) == 1); 395 | 396 | string s3 = " 397 | #if B 398 | #ifdef C 399 | #endif 400 | #else 401 | #endif 402 | #endif 403 | "; 404 | tokens = tokenize(s3); 405 | assert(checkIfEndifBalance("nofile.cpp", tokens) == 1); 406 | 407 | string s4 = " 408 | #ifndef A 409 | #endif 410 | #else 411 | "; 412 | tokens = tokenize(s4); 413 | assert(checkIfEndifBalance("nofile.cpp", tokens) == 1); 414 | } 415 | 416 | unittest { 417 | string code; 418 | 419 | // Non-explicit single-arg constructors may inadvertently be used for 420 | // type conversion 421 | code = " 422 | class AA { 423 | AA(int bad); 424 | AA(AA *bad); 425 | }; 426 | struct BB { 427 | BB(int bad); 428 | }; 429 | "; 430 | Token[] tokens; 431 | tokens = tokenize(code); 432 | assert(checkConstructors("nofile.cpp", tokens) == 3); 433 | 434 | // Default arguments are a special case of the previous problem 435 | code = " 436 | class AA { 437 | AA(int bad = 42); 438 | AA(int bad, int j = 42); 439 | AA(int bad = 42, int j = 42); 440 | }; 441 | "; 442 | tokens = tokenize(code); 443 | EXPECT_EQ(checkConstructors("nofile.cpp", tokens), 3); 444 | 445 | // Constructors with zero or 2+ required arguments are safe 446 | code = " 447 | class AA { 448 | AA(); 449 | AA(void); 450 | AA(int safe, int safe2); 451 | AA(AA safe, AA safe2); 452 | void dosomething(const int & safe, vector & safe2); 453 | }; 454 | "; 455 | assert(checkConstructors("nofile.cpp", tokenize(code)) == 0); 456 | 457 | // Single-arg constructors marked "explicit" are safe 458 | code = " 459 | class AA { 460 | explicit AA(int safe); 461 | explicit AA(int safe, int j = 42); 462 | }; 463 | "; 464 | assert(checkConstructors("nofile.cpp", tokenize(code)) == 0); 465 | 466 | // Could be "explicit constexpr" or "constexpr explicit" too 467 | code = " 468 | class AA { 469 | explicit constexpr AA(int safe); 470 | constexpr explicit AA(int* safe); 471 | }; 472 | "; 473 | assert(checkConstructors("nofile.cpp", tokenize(code)) == 0); 474 | 475 | // Suppress warnings when single-arg constructors are marked as implicit 476 | code = " 477 | class CC { 478 | /* implicit */ CC(int acceptable); 479 | /* implicit */ CC(int acceptable, int j = 42); 480 | }; 481 | "; 482 | assert(checkConstructors("nofile.cpp", tokenize(code)) == 0); 483 | 484 | // These copy/move constructors are reasonable 485 | code = " 486 | class AA { 487 | AA(const AA& acceptable); 488 | AA(AA&& acceptable) noexcept; 489 | }; 490 | "; 491 | assert(checkConstructors("nofile.cpp", tokenize(code)) == 0); 492 | 493 | // Move constructors should be declared noexcept 494 | code = " 495 | class AA { 496 | AA(const AA& acceptable); 497 | AA(AA&& mightThrow); 498 | }; 499 | "; 500 | assert(checkConstructors("nofile.cpp", tokenize(code)) == 1); 501 | 502 | // Move constructors that are deleted doesn't have to be 503 | // declared noexcept 504 | code = " 505 | class AA { 506 | AA(const AA& acceptable); 507 | AA(AA&& mightThrow) = delete; 508 | }; 509 | "; 510 | assert(checkConstructors("nofile.cpp", tokenize(code)) == 0); 511 | 512 | // Move constructors that throw should be explicitly marked as such 513 | code = " 514 | class AA { 515 | AA(const AA& acceptable); 516 | AA(AA&& mightThrow) /* may throw */; 517 | }; 518 | "; 519 | assert(checkConstructors("nofile.cpp", tokenize(code)) == 0); 520 | 521 | // These move/copy constructors don't make sense 522 | code = " 523 | class AA { 524 | AA(AA& shouldBeConst); 525 | AA(const AA&& shouldNotBeConst); 526 | }; 527 | "; 528 | assert(checkConstructors("nofile.cpp", tokenize(code)) == 3); 529 | 530 | // Don't warn for single-argument std::initializer_list<...> 531 | code = " 532 | class AA { 533 | AA(std::initializer_list args); 534 | }; 535 | "; 536 | assert(checkConstructors("nofile.cpp", tokenize(code)) == 0); 537 | 538 | // Verify that the parser handles nested blocks correctly 539 | code = " 540 | namespace AA { namespace BB { 541 | class CC { 542 | class DD { 543 | DD(int bad); 544 | void CC(int safe); 545 | }; 546 | void DD(int safe); 547 | CC(int bad); 548 | }; 549 | void AA(int safe); 550 | } } 551 | void CC(int safe); 552 | void DD(int safe); 553 | "; 554 | assert(checkConstructors("nofile.cpp", tokenize(code)) == 2); 555 | 556 | // Some special cases that are tricky to parse 557 | code = " 558 | template < class T, class Allocator = allocator > 559 | class AA { 560 | using namespace std; 561 | static int i = 0; 562 | typedef AA this_type; 563 | friend class ::BB; 564 | struct { 565 | int x; 566 | struct DD { 567 | DD(int bad); 568 | }; 569 | } pt; 570 | struct foo foo_; 571 | AA(std::vector bad); 572 | AA(T bad, Allocator jj = NULL); 573 | AA* clone() const { return new AA(safe); } 574 | void foo(std::vector safe) { AA(this->i); } 575 | void foo(int safe); 576 | }; 577 | class CC : std::exception { 578 | CC(const string& bad) {} 579 | void foo() { 580 | CC(localizeString(MY_STRING)); 581 | CC(myString); 582 | CC(4); 583 | throw CC(\"ok\"); 584 | } 585 | }; 586 | "; 587 | assert(checkConstructors("nofile.cpp", tokenize(code)) == 4); 588 | } 589 | 590 | // testCheckImplicitCast 591 | unittest { 592 | Token[] tokens; 593 | string filename = "nofile.cpp"; 594 | string code; 595 | 596 | // Test if it captures implicit bool cast 597 | code = " 598 | class AA { 599 | operator bool(); 600 | }; 601 | struct BB { 602 | operator bool(); 603 | }; 604 | "; 605 | tokens = tokenize(code); 606 | assert(checkImplicitCast(filename, tokens) == 2); 607 | 608 | // Test if explicit and /* implicit */ whitelisting works 609 | code = " 610 | class AA { 611 | explicit operator bool(); 612 | }; 613 | struct BB { 614 | /* implicit */ operator bool(); 615 | }; 616 | "; 617 | tokens = tokenize(code); 618 | assert(checkImplicitCast(filename, tokens) == 0); 619 | 620 | // It's ok to delete an implicit bool operator. 621 | code = "( 622 | class AA { 623 | operator bool() = delete; 624 | operator bool() const = delete; 625 | }; 626 | )"; 627 | tokens = tokenize(code); 628 | assert(checkImplicitCast(filename, tokens) == 0); 629 | 630 | // Test if it captures implicit char cast 631 | code = " 632 | class AA { 633 | operator char(); 634 | }; 635 | struct BB { 636 | operator char(); 637 | }; 638 | "; 639 | tokens = tokenize(code); 640 | assert(checkImplicitCast(filename, tokens) == 2); 641 | 642 | // Test if explicit and /* implicit */ whitelisting works 643 | code = " 644 | struct Foo; 645 | class AA { 646 | explicit operator Foo(); 647 | }; 648 | struct BB { 649 | /* implicit */ operator Foo(); 650 | }; 651 | "; 652 | tokens = tokenize(code); 653 | assert(checkImplicitCast(filename, tokens) == 0); 654 | 655 | // Test if it captures implicit uint8_t cast 656 | code = " 657 | class AA { 658 | operator uint8_t(); 659 | }; 660 | struct BB { 661 | operator uint8_t(); 662 | }; 663 | "; 664 | tokens = tokenize(code); 665 | assert(checkImplicitCast(filename, tokens) == 2); 666 | 667 | // Test if it captures implicit uint8_t * cast 668 | code = " 669 | class AA { 670 | operator uint8_t *(); 671 | }; 672 | struct BB { 673 | operator uint8_t *(); 674 | }; 675 | "; 676 | tokens = tokenize(code); 677 | assert(checkImplicitCast(filename, tokens) == 2); 678 | 679 | // Test if it captures implicit void * cast 680 | code = " 681 | class AA { 682 | operator void *(); 683 | }; 684 | struct BB { 685 | operator void *(); 686 | }; 687 | "; 688 | tokens = tokenize(code); 689 | assert(checkImplicitCast(filename, tokens) == 2); 690 | 691 | // Test if it captures implicit class cast 692 | code = " 693 | class AA { 694 | }; 695 | class BB { 696 | operator AA(); 697 | }; 698 | "; 699 | tokens = tokenize(code); 700 | assert(checkImplicitCast(filename, tokens) == 1); 701 | 702 | // Test if it captures implicit class pointer cast 703 | code = " 704 | class AA { 705 | }; 706 | class BB { 707 | operator AA *(); 708 | }; 709 | "; 710 | tokens = tokenize(code); 711 | assert(checkImplicitCast(filename, tokens) == 1); 712 | 713 | // Test if it captures implicit class reference cast 714 | code = " 715 | class AA { 716 | }; 717 | class BB { 718 | operator AA&(); 719 | }; 720 | "; 721 | tokens = tokenize(code); 722 | assert(checkImplicitCast(filename, tokens) == 1); 723 | 724 | // Test if it captures template classes 725 | code = " 726 | template 727 | class AA { 728 | T bb; 729 | operator T(); 730 | }; 731 | "; 732 | tokens = tokenize(code); 733 | assert(checkImplicitCast(filename, tokens) == 1); 734 | 735 | // Test if it avoids operator overloading 736 | code = " 737 | class AA { 738 | int operator *() 739 | int operator+(int i); 740 | void foo(); 741 | }; 742 | "; 743 | tokens = tokenize(code); 744 | assert(checkImplicitCast(filename, tokens) == 0); 745 | 746 | // Test if it captures template casts 747 | code = " 748 | template 749 | class AA { 750 | operator std::unique_ptr(); 751 | }; 752 | "; 753 | tokens = tokenize(code); 754 | assert(checkImplicitCast(filename, tokens) == 1); 755 | 756 | // Test if it avoids definitions 757 | code = " 758 | class AA { 759 | int bb 760 | operator bool(); 761 | }; 762 | AA::operator bool() { 763 | return bb == 0; 764 | } 765 | "; 766 | tokens = tokenize(code); 767 | assert(checkImplicitCast(filename, tokens) == 1); 768 | 769 | // explicit constexpr is still explicit 770 | code = " 771 | class Foo { 772 | explicit constexpr operator int(); 773 | }; 774 | "; 775 | tokens = tokenize(code); 776 | assert(checkImplicitCast(filename, tokens) == 0); 777 | 778 | // operator in any unrelated context 779 | code = " 780 | #include 781 | #include \"operator.hpp\" 782 | "; 783 | tokens = tokenize(code); 784 | assert(checkImplicitCast(filename, tokens) == 0); 785 | } 786 | 787 | // Test non-virtual destructor detection 788 | unittest { 789 | string s =" 790 | class AA { 791 | public: 792 | ~AA(); 793 | virtual int foo(); 794 | void aa(); 795 | };"; 796 | Token[] tokens; 797 | string filename = "nofile.cpp"; 798 | tokens = tokenize(s); 799 | EXPECT_EQ(checkVirtualDestructors(filename, tokens), 1); 800 | 801 | s = " 802 | class AA { 803 | public: 804 | private: 805 | virtual void bar(); 806 | public: 807 | ~AA(); 808 | };"; 809 | tokens = tokenize(s); 810 | EXPECT_EQ(checkVirtualDestructors(filename, tokens), 1); 811 | 812 | s = " 813 | class AA { 814 | public: 815 | virtual void aa(); 816 | };"; 817 | tokens = tokenize(s); 818 | EXPECT_EQ(checkVirtualDestructors(filename, tokens), 1); 819 | 820 | // Namespace 821 | s = " 822 | class AA::BB { 823 | public: 824 | virtual void foo(); 825 | ~BB(); 826 | };"; 827 | tokens = tokenize(s); 828 | EXPECT_EQ(checkVirtualDestructors(filename, tokens), 1); 829 | 830 | // Struct 831 | s = " 832 | struct AA { 833 | virtual void foo() {} 834 | ~AA(); 835 | };"; 836 | tokens = tokenize(s); 837 | EXPECT_EQ(checkVirtualDestructors(filename, tokens), 1); 838 | 839 | // Nested class 840 | s = " 841 | class BB { 842 | public: 843 | ~BB(); 844 | class CC { 845 | virtual int bar(); 846 | }; 847 | virtual void foo(); 848 | };"; 849 | tokens = tokenize(s); 850 | EXPECT_EQ(checkVirtualDestructors(filename, tokens), 2); 851 | 852 | // Not a base class; don't know if AA has a virtual dtor 853 | s = " 854 | class BB : public AA{ 855 | virtual foo() {} 856 | };"; 857 | tokens = tokenize(s); 858 | EXPECT_EQ(checkVirtualDestructors(filename, tokens), 0); 859 | 860 | // Protected dtor 861 | s = " 862 | class BB { 863 | protected: 864 | ~BB(); 865 | public: 866 | virtual void foo(); 867 | };"; 868 | tokens = tokenize(s); 869 | EXPECT_EQ(checkVirtualDestructors(filename, tokens), 0); 870 | 871 | // Private dtor 872 | s = " 873 | class BB { 874 | ~BB(); 875 | public: 876 | virtual void foo(); 877 | };"; 878 | tokens = tokenize(s); 879 | EXPECT_EQ(checkVirtualDestructors(filename, tokens), 0); 880 | } 881 | 882 | void EXPECT_EQ(T, U, string f = __FILE__, size_t n = __LINE__) 883 | (auto ref T lhs, auto ref U rhs) { 884 | if (lhs == rhs) return; 885 | stderr.writeln(text(f, ":", n, ": ", lhs, " != ", rhs)); 886 | assert(0); 887 | } 888 | 889 | // Test include guard detection 890 | unittest { 891 | string s = " 892 | //comment 893 | /*yet\n anothern one\n*/ 894 | #ifndef TEST_H 895 | #define TEST_H 896 | class A { }; 897 | #if TRUE 898 | #ifdef TEST_H 899 | class B { }; 900 | #endif 901 | class C { }; 902 | #else 903 | #endif 904 | class D { }; 905 | #endif 906 | "; 907 | Token[] tokens; 908 | string filename = "nofile.h"; 909 | tokens = tokenize(s); 910 | EXPECT_EQ(checkIncludeGuard(filename, tokens), 0); 911 | 912 | string s2 = " 913 | #ifndef TEST_H 914 | #define TeST_h 915 | class A { }; 916 | #if TRUE 917 | #ifdef TEST_H 918 | class B { }; 919 | #endif 920 | class C { }; 921 | #else 922 | #endif 923 | class D { }; 924 | #endif 925 | #ifdef E_H 926 | class E { }; 927 | #endif 928 | "; 929 | tokens = tokenize(s2); 930 | EXPECT_EQ(checkIncludeGuard(filename, tokens), 2); 931 | 932 | string s3 = " 933 | class A { }; 934 | #if TRUE 935 | #ifdef TEST_H 936 | class B { }; 937 | #endif 938 | class C { }; 939 | #else 940 | #endif 941 | class D { }; 942 | "; 943 | tokens = tokenize(s3); 944 | EXPECT_EQ(checkIncludeGuard(filename, tokens), 1); 945 | filename = "nofile.cpp"; 946 | EXPECT_EQ(checkIncludeGuard(filename, tokens), 0); 947 | 948 | string s4 = " 949 | #pragma once 950 | class A { }; 951 | #if TRUE 952 | #ifdef TEST_H 953 | class B { }; 954 | #endif 955 | class C { }; 956 | #else 957 | #endif 958 | class D { }; 959 | "; 960 | tokens = tokenize(s4); 961 | filename = "nofile.h"; 962 | EXPECT_EQ(checkIncludeGuard(filename, tokens), 0); 963 | } 964 | 965 | // testCheckInitializeFromItself 966 | unittest { 967 | string s = " 968 | namespace whatever { 969 | ClassFoo::ClassFoo(int memberBar, int memberOk, int memberBaz) 970 | : memberBar_(memberBar_) 971 | , memberOk_(memberOk) 972 | , memberBaz_(memberBaz_) { 973 | } 974 | } 975 | "; 976 | auto tokens = tokenize(s); 977 | EXPECT_EQ(checkInitializeFromItself("nofile.cpp", tokens), 2); 978 | 979 | string s1 = " 980 | namespace whatever { 981 | ClassFooPOD::ClassFooPOD(int memberBaz) : 982 | memberBaz(memberBaz) { 983 | } 984 | } 985 | "; 986 | tokens = tokenize(s1); 987 | EXPECT_EQ(checkInitializeFromItself("nofile.cpp", tokens), 0); 988 | } 989 | 990 | // testCheckUsingDirectives 991 | unittest { 992 | string s = " 993 | namespace whatever { 994 | void foo() { 995 | using namespace ok; 996 | } 997 | } 998 | namespace facebook { 999 | namespace whatever { 1000 | class Bar { 1001 | void baz() { 1002 | using namespace ok; 1003 | }}; 1004 | }} 1005 | void qux() { 1006 | using namespace ok; 1007 | } 1008 | "; 1009 | string filename = "nofile.h"; 1010 | auto tokens = tokenize(s); 1011 | EXPECT_EQ(checkUsingDirectives(filename, tokens), 0); 1012 | 1013 | string s1 = " 1014 | namespace facebook { 1015 | 1016 | namespace { void unnamed(); } 1017 | namespace fs = boost::filesystem; 1018 | 1019 | void foo() { } 1020 | using namespace not_ok; 1021 | namespace whatever { 1022 | using namespace warn; 1023 | namespace facebook { 1024 | } 1025 | } 1026 | } 1027 | using namespace not_ok; 1028 | "; 1029 | tokens = tokenize(s1); 1030 | EXPECT_EQ(checkUsingDirectives(filename, tokens), 4); 1031 | 1032 | string s2 = " 1033 | void foo() { 1034 | namespace fs = boost::filesystem; 1035 | }"; 1036 | tokens = tokenize(s2); 1037 | EXPECT_EQ(checkUsingDirectives(filename, tokens), 0); 1038 | } 1039 | 1040 | // test that we allow 'using x=y::z; but not 'using abc::def' 1041 | unittest { 1042 | string s1 = " 1043 | using not_ok::nope; 1044 | using not_ok::nope::stillnotok; 1045 | using SomeAlias1 = is::ok; 1046 | using SomeAlias2 = is::ok::cool; 1047 | "; 1048 | string filename = "nofile.h"; 1049 | auto tokens = tokenize(s1); 1050 | EXPECT_EQ(checkUsingDirectives(filename, tokens), 2); 1051 | 1052 | string s2 = " 1053 | void foo() { using is::ok; } 1054 | namespace facebook { 1055 | using not_ok::nope; 1056 | using alias3 = is::ok; 1057 | }"; 1058 | auto tokens2 = tokenize(s2); 1059 | EXPECT_EQ(checkUsingDirectives(filename, tokens2), 1); 1060 | 1061 | string s3 = " 1062 | namespace notfacebook { 1063 | using is::warn; 1064 | using namespace warn; 1065 | }"; 1066 | auto tokens3 = tokenize(s3); 1067 | EXPECT_EQ(checkUsingDirectives(filename, tokens3), 2); 1068 | 1069 | string s4 = " 1070 | /* using override */ using ok::good; 1071 | /* using override */ using namespace ok; 1072 | namespace facebook { 1073 | /* using override */ using ok::good; 1074 | }"; 1075 | 1076 | auto tokens4 = tokenize(s4); 1077 | EXPECT_EQ(checkUsingDirectives(filename, tokens4), 0); 1078 | } 1079 | 1080 | // testCheckUsingNamespaceDirectives 1081 | unittest { 1082 | string filename = "nofile.cpp"; 1083 | Token[] tokens; 1084 | 1085 | void RUN_THIS_TEST(string code, uint expectedResult) { 1086 | auto tokens = tokenize(code); 1087 | EXPECT_EQ(checkUsingNamespaceDirectives(filename, tokens), expectedResult); 1088 | } 1089 | 1090 | string nothing = ""; 1091 | RUN_THIS_TEST(nothing, 0); 1092 | 1093 | string simple1 = "using namespace std;"; 1094 | RUN_THIS_TEST(simple1, 0); 1095 | 1096 | string simple2 = "using ok::good;"; 1097 | RUN_THIS_TEST(simple2, 0); 1098 | 1099 | string simple3 = "using alias = ok::good;"; 1100 | RUN_THIS_TEST(simple3, 0); 1101 | 1102 | string simpleFail = " 1103 | using namespace std; 1104 | using namespace boost; 1105 | "; 1106 | RUN_THIS_TEST(simpleFail, 1); 1107 | 1108 | // 2 directives in separate scopes should not conflict 1109 | string separateBlocks = " 1110 | { 1111 | using namespace std; 1112 | } 1113 | { 1114 | using namespace boost; 1115 | } 1116 | "; 1117 | RUN_THIS_TEST(separateBlocks, 0); 1118 | 1119 | // need to catch directives if they are nested in scope of other directive 1120 | string nested = " 1121 | { 1122 | using namespace std; 1123 | { 1124 | using namespace boost; 1125 | } 1126 | } 1127 | "; 1128 | RUN_THIS_TEST(nested, 1); 1129 | 1130 | // same as last case, but without outer braces 1131 | string nested2 = " 1132 | using namespace std; 1133 | { 1134 | using namespace boost; 1135 | } 1136 | "; 1137 | RUN_THIS_TEST(nested, 1); 1138 | 1139 | // make sure doubly nesting things is not a problem 1140 | string doubleNested = " 1141 | { 1142 | using namespace std; 1143 | { 1144 | { 1145 | using namespace boost; 1146 | } 1147 | } 1148 | } 1149 | "; 1150 | RUN_THIS_TEST(doubleNested, 1); 1151 | 1152 | // same as last case, but without outer braces 1153 | string doubleNested2 = " 1154 | using namespace std; 1155 | { 1156 | { 1157 | using namespace boost; 1158 | } 1159 | } 1160 | "; 1161 | RUN_THIS_TEST(doubleNested, 1); 1162 | 1163 | // duplicates using namespace declaratives 1164 | string dupe = " 1165 | using namespace std; 1166 | using namespace std; 1167 | "; 1168 | RUN_THIS_TEST(dupe, 1); 1169 | 1170 | // even though "using namespace std;" appears twice above "using namespace 1171 | // boost;", there should only be 1 error from that line (and 1 from the 1172 | // duplication) 1173 | string dupe2 = " 1174 | using namespace std; 1175 | using namespace std; 1176 | using namespace boost; 1177 | "; 1178 | RUN_THIS_TEST(dupe2, 2); 1179 | 1180 | // even though the third line has 2 errors (it's a duplicate and it 1181 | // conflicts with the 2nd line), it should only result in the duplicate 1182 | // errorand short circuit out of the other 1183 | string dupe3 = " 1184 | using namespace std; 1185 | using namespace boost; 1186 | using namespace std; 1187 | "; 1188 | RUN_THIS_TEST(dupe3, 2); 1189 | 1190 | // only 2 errors total should be generated even though the 3rd line 1191 | // conflicts with both of the first 2 1192 | string threeWay = " 1193 | using namespace std; 1194 | using namespace boost; 1195 | using namespace std; 1196 | "; 1197 | RUN_THIS_TEST(threeWay, 2); 1198 | 1199 | // make sure nesting algorithm realizes second using namespace std; is a 1200 | // dupe and doesn't remove it from the set of visible namespaces 1201 | string nestedDupe = " 1202 | using namespace std; 1203 | { 1204 | using namespace std; 1205 | } 1206 | using namespace std; 1207 | "; 1208 | RUN_THIS_TEST(nestedDupe, 2); 1209 | 1210 | string other1 = " 1211 | { 1212 | using namespace std; 1213 | using namespace std; 1214 | } 1215 | using namespace boost; 1216 | "; 1217 | RUN_THIS_TEST(other1, 1); 1218 | } 1219 | 1220 | // testThrowsSpecification 1221 | unittest { 1222 | string s1 = "struct foo { void function() throw(); };"; 1223 | Token[] tokens; 1224 | string filename = "nofile.cpp"; 1225 | tokens = tokenize(s1); 1226 | EXPECT_EQ(checkThrowSpecification(filename, tokens), 1); 1227 | 1228 | string s2 = " 1229 | void func() { 1230 | throw (std::runtime_error(\"asd\"); 1231 | } 1232 | "; 1233 | tokens = tokenize(s2); 1234 | EXPECT_EQ(checkThrowSpecification(filename, tokens), 0); 1235 | 1236 | string s3 = " 1237 | struct something { 1238 | void func() throw(); 1239 | void func2() noexcept(); 1240 | void func3() throw(char const* const*); 1241 | void wat() const { 1242 | throw(12); 1243 | } 1244 | }; 1245 | "; 1246 | tokens = tokenize(s3); 1247 | EXPECT_EQ(checkThrowSpecification(filename, tokens), 2); 1248 | 1249 | string s4 = "struct A { void f1() const throw(); 1250 | void f2() volatile throw(); 1251 | void f3() const volatile throw(); 1252 | void f4() volatile const throw(); 1253 | void f5() const; 1254 | void f6() volatile; 1255 | void f7() volatile const; 1256 | void f8() volatile const {} 1257 | };"; 1258 | tokens = tokenize(s4); 1259 | EXPECT_EQ(checkThrowSpecification(filename, tokens), 4); 1260 | 1261 | string s5 = "void f1();"; 1262 | tokens = tokenize(s5); 1263 | EXPECT_EQ(checkThrowSpecification(filename, tokens), 0); 1264 | 1265 | string s6 = "void f1(void(*)(int,char)) throw();"; 1266 | tokens = tokenize(s6); 1267 | EXPECT_EQ(checkThrowSpecification(filename, tokens), 1); 1268 | 1269 | string s7 = "void f() { if (!true) throw(12); }"; 1270 | tokens = tokenize(s7); 1271 | EXPECT_EQ(checkThrowSpecification(filename, tokens), 0); 1272 | 1273 | string s8 = "namespace foo { 1274 | struct bar { 1275 | struct baz { 1276 | void f() throw(std::logic_error); 1277 | }; 1278 | struct huh; 1279 | }; 1280 | using namespace std; 1281 | struct bar::huh : bar::baz { 1282 | void f2() const throw() { return f(); } 1283 | }; 1284 | namespace { 1285 | void func() throw() { 1286 | if (things_are_bad()) { 1287 | throw 12; 1288 | } 1289 | } 1290 | } 1291 | class one_more { void eh() throw(); }; 1292 | }"; 1293 | tokens = tokenize(s8); 1294 | EXPECT_EQ(checkThrowSpecification(filename, tokens), 4); 1295 | 1296 | string s9 = "struct foo : std::exception { 1297 | virtual void what() const throw() = 0; };"; 1298 | tokens = tokenize(s9); 1299 | EXPECT_EQ(checkThrowSpecification(filename, tokens), 0); 1300 | 1301 | string s10 = "template void f() { throw(12); } 1302 | template T, class Y> void g() 1303 | { throw(12); } 1304 | const int a = 2; const int b = 12; 1305 | template void h() 1306 | { throw(12); }"; 1307 | tokens = tokenize(s10); 1308 | EXPECT_EQ(checkThrowSpecification(filename, tokens), 0); 1309 | 1310 | string s11 = "const int a = 2; const int b = 12; 1311 | template void f() throw() {} 1312 | void g() throw();"; 1313 | tokens = tokenize(s11); 1314 | EXPECT_EQ(checkThrowSpecification(filename, tokens), 2); 1315 | 1316 | string s12 = "struct Foo : std::exception { ~Foo() throw(); };"; 1317 | tokens = tokenize(s12); 1318 | EXPECT_EQ(checkThrowSpecification(filename, tokens), 0); 1319 | 1320 | string s13 = "struct Foo : std::exception { ~Foo() throw() {} };"; 1321 | tokens = tokenize(s13); 1322 | EXPECT_EQ(checkThrowSpecification(filename, tokens), 0); 1323 | 1324 | string s14 = "struct Foo : std::exception { ~Foo() throw() {} " ~ 1325 | "virtual const char* what() const throw() {} };"; 1326 | tokens = tokenize(s14); 1327 | EXPECT_EQ(checkThrowSpecification(filename, tokens), 0); 1328 | 1329 | string s15 = "struct Foo { const char* what() const throw() {}" ~ 1330 | "~Foo() throw() {} };"; 1331 | tokens = tokenize(s15); 1332 | EXPECT_EQ(checkThrowSpecification(filename, tokens), 0); 1333 | } 1334 | 1335 | // testProtectedInheritance 1336 | unittest { 1337 | string s1 = "class foo { }"; 1338 | Token[] tokens; 1339 | string filename = "nofile.cpp"; 1340 | tokens = tokenize(s1); 1341 | EXPECT_EQ(checkProtectedInheritance(filename, tokens), 0); 1342 | 1343 | string s2 = "class foo : public bar { }"; 1344 | tokens = tokenize(s2); 1345 | EXPECT_EQ(checkProtectedInheritance(filename, tokens), 0); 1346 | 1347 | string s3 = "class foo : protected bar { }"; 1348 | tokens = tokenize(s3); 1349 | EXPECT_EQ(checkProtectedInheritance(filename, tokens), 1); 1350 | 1351 | string s4 = "class foo : public bar { class baz : protected bar { } }"; 1352 | tokens = tokenize(s4); 1353 | EXPECT_EQ(checkProtectedInheritance(filename, tokens), 1); 1354 | 1355 | string s5 = "class foo : protected bar { class baz : public bar { } }"; 1356 | tokens = tokenize(s5); 1357 | EXPECT_EQ(checkProtectedInheritance(filename, tokens), 1); 1358 | 1359 | string s6 = "class foo : protected bar { class baz : protected bar { } }"; 1360 | tokens = tokenize(s6); 1361 | EXPECT_EQ(checkProtectedInheritance(filename, tokens), 2); 1362 | 1363 | string s7 = "class foo; class bar : protected foo {};"; 1364 | tokens = tokenize(s7); 1365 | EXPECT_EQ(checkProtectedInheritance(filename, tokens), 1); 1366 | } 1367 | 1368 | // testExceptionInheritance 1369 | unittest { 1370 | Token[] tokens; 1371 | string filename = "nofile.cpp", s; 1372 | 1373 | s = "class foo { }"; 1374 | tokens = tokenize(s); 1375 | EXPECT_EQ(checkExceptionInheritance(filename, tokens), 0); 1376 | 1377 | s = "class foo: exception { }"; 1378 | tokens = tokenize(s); 1379 | EXPECT_EQ(checkExceptionInheritance(filename, tokens), 1); 1380 | 1381 | s = "class foo: std::exception { }"; 1382 | tokens = tokenize(s); 1383 | EXPECT_EQ(checkExceptionInheritance(filename, tokens), 1); 1384 | 1385 | s = "class foo: private exception { }"; 1386 | tokens = tokenize(s); 1387 | EXPECT_EQ(checkExceptionInheritance(filename, tokens), 1); 1388 | 1389 | s = "class foo: private std::exception { }"; 1390 | tokens = tokenize(s); 1391 | EXPECT_EQ(checkExceptionInheritance(filename, tokens), 1); 1392 | 1393 | s = "class foo: protected exception { }"; 1394 | tokens = tokenize(s); 1395 | EXPECT_EQ(checkExceptionInheritance(filename, tokens), 1); 1396 | 1397 | s = "class foo: protected std::exception { }"; 1398 | tokens = tokenize(s); 1399 | EXPECT_EQ(checkExceptionInheritance(filename, tokens), 1); 1400 | 1401 | s = "class foo: public exception { }"; 1402 | tokens = tokenize(s); 1403 | EXPECT_EQ(checkExceptionInheritance(filename, tokens), 0); 1404 | 1405 | s = "class foo: public std::exception { }"; 1406 | tokens = tokenize(s); 1407 | EXPECT_EQ(checkExceptionInheritance(filename, tokens), 0); 1408 | 1409 | s = "struct foo: exception { }"; 1410 | tokens = tokenize(s); 1411 | EXPECT_EQ(checkExceptionInheritance(filename, tokens), 0); 1412 | 1413 | s = "struct foo: std::exception { }"; 1414 | tokens = tokenize(s); 1415 | EXPECT_EQ(checkExceptionInheritance(filename, tokens), 0); 1416 | 1417 | s = "struct foo: private exception { }"; 1418 | tokens = tokenize(s); 1419 | EXPECT_EQ(checkExceptionInheritance(filename, tokens), 1); 1420 | 1421 | s = "struct foo: private std::exception { }"; 1422 | tokens = tokenize(s); 1423 | EXPECT_EQ(checkExceptionInheritance(filename, tokens), 1); 1424 | 1425 | s = "struct foo: protected exception { }"; 1426 | tokens = tokenize(s); 1427 | EXPECT_EQ(checkExceptionInheritance(filename, tokens), 0); 1428 | 1429 | s = "struct foo: protected std::exception { }"; 1430 | tokens = tokenize(s); 1431 | EXPECT_EQ(checkExceptionInheritance(filename, tokens), 0); 1432 | 1433 | s = "struct foo: public exception { }"; 1434 | tokens = tokenize(s); 1435 | EXPECT_EQ(checkExceptionInheritance(filename, tokens), 0); 1436 | 1437 | s = "struct foo: public std::exception { }"; 1438 | tokens = tokenize(s); 1439 | EXPECT_EQ(checkExceptionInheritance(filename, tokens), 0); 1440 | 1441 | s = "class bar: public std::exception {class foo: exception { } c;}"; 1442 | tokens = tokenize(s); 1443 | EXPECT_EQ(checkExceptionInheritance(filename, tokens), 1); 1444 | 1445 | s = "class bar: public std::exception {class foo: std::exception { } c;}"; 1446 | tokens = tokenize(s); 1447 | EXPECT_EQ(checkExceptionInheritance(filename, tokens), 1); 1448 | 1449 | s = "class bar: public std::exception {class foo: private exception { } c;}"; 1450 | tokens = tokenize(s); 1451 | EXPECT_EQ(checkExceptionInheritance(filename, tokens), 1); 1452 | 1453 | s = "class bar: public std::exception " ~ 1454 | "{class foo: private std::exception { } c;}"; 1455 | tokens = tokenize(s); 1456 | EXPECT_EQ(checkExceptionInheritance(filename, tokens), 1); 1457 | 1458 | s = "class bar: public std::exception " ~ 1459 | "{class foo: protected exception { } c;}"; 1460 | tokens = tokenize(s); 1461 | EXPECT_EQ(checkExceptionInheritance(filename, tokens), 1); 1462 | 1463 | s = "class bar: public std::exception " ~ 1464 | "{class foo: protected std::exception { } c;}"; 1465 | tokens = tokenize(s); 1466 | EXPECT_EQ(checkExceptionInheritance(filename, tokens), 1); 1467 | 1468 | s = "class bar: public std::exception {class foo: public exception { } c;}"; 1469 | tokens = tokenize(s); 1470 | EXPECT_EQ(checkExceptionInheritance(filename, tokens), 0); 1471 | 1472 | s = "class bar: std::exception {class foo { } c;}"; 1473 | tokens = tokenize(s); 1474 | EXPECT_EQ(checkExceptionInheritance(filename, tokens), 1); 1475 | 1476 | s = "class bar: std::exception {class foo: exception { } c;}"; 1477 | tokens = tokenize(s); 1478 | EXPECT_EQ(checkExceptionInheritance(filename, tokens), 2); 1479 | 1480 | s = "class bar: std::exception {class foo: std::exception { } c;}"; 1481 | tokens = tokenize(s); 1482 | EXPECT_EQ(checkExceptionInheritance(filename, tokens), 2); 1483 | 1484 | s = "class bar: std::exception {class foo: private exception { } c;}"; 1485 | tokens = tokenize(s); 1486 | EXPECT_EQ(checkExceptionInheritance(filename, tokens), 2); 1487 | 1488 | s = "class bar: std::exception {class foo: private std::exception { } c;}"; 1489 | tokens = tokenize(s); 1490 | EXPECT_EQ(checkExceptionInheritance(filename, tokens), 2); 1491 | 1492 | s = "class bar: std::exception {class foo: protected exception { } c;}"; 1493 | tokens = tokenize(s); 1494 | EXPECT_EQ(checkExceptionInheritance(filename, tokens), 2); 1495 | 1496 | s = "class bar: std::exception {class foo: protected std::exception { } c;}"; 1497 | tokens = tokenize(s); 1498 | EXPECT_EQ(checkExceptionInheritance(filename, tokens), 2); 1499 | 1500 | s = "class bar: std::exception {class foo: public exception { } c;}"; 1501 | tokens = tokenize(s); 1502 | EXPECT_EQ(checkExceptionInheritance(filename, tokens), 1); 1503 | 1504 | s = "class bar: std::exception {class foo: public std::exception { } c;}"; 1505 | tokens = tokenize(s); 1506 | EXPECT_EQ(checkExceptionInheritance(filename, tokens), 1); 1507 | 1508 | s = "class foo; class bar: std::exception {}"; 1509 | tokens = tokenize(s); 1510 | EXPECT_EQ(checkExceptionInheritance(filename, tokens), 1); 1511 | 1512 | s = "class foo: public bar, std::exception {}"; 1513 | tokens = tokenize(s); 1514 | EXPECT_EQ(checkExceptionInheritance(filename, tokens), 1); 1515 | 1516 | s = "class foo: public bar, private std::exception {}"; 1517 | tokens = tokenize(s); 1518 | EXPECT_EQ(checkExceptionInheritance(filename, tokens), 1); 1519 | 1520 | s = "class foo: private bar, std::exception {}"; 1521 | tokens = tokenize(s); 1522 | EXPECT_EQ(checkExceptionInheritance(filename, tokens), 1); 1523 | 1524 | s = "class foo: private bar, public baz, std::exception {}"; 1525 | tokens = tokenize(s); 1526 | EXPECT_EQ(checkExceptionInheritance(filename, tokens), 1); 1527 | 1528 | s = "class foo: private bar::exception {}"; 1529 | tokens = tokenize(s); 1530 | EXPECT_EQ(checkExceptionInheritance(filename, tokens), 0); 1531 | 1532 | s = "struct foo: public bar, std::exception {}"; 1533 | tokens = tokenize(s); 1534 | EXPECT_EQ(checkExceptionInheritance(filename, tokens), 0); 1535 | 1536 | s = "struct foo: public bar, private std::exception {}"; 1537 | tokens = tokenize(s); 1538 | EXPECT_EQ(checkExceptionInheritance(filename, tokens), 1); 1539 | } 1540 | 1541 | // testCxxReplace 1542 | unittest { 1543 | if (!std.file.exists("_bin/linters/flint/cxx_replace")) { 1544 | // No prejudice if the file is missing (this may happen e.g. if 1545 | // running fbmake runtests with -j) 1546 | stderr.writeln("_bin/linters/flint/cxx_replace is missing" ~ 1547 | " so it cannot be tested this time around. This may happen" ~ 1548 | " during a parallel build."); 1549 | return; 1550 | } 1551 | string s1 = " 1552 | tokenLen = 0; 1553 | pc = pc1; 1554 | pc.advance(strlen(\"line\")); 1555 | line = parse(pc); 1556 | munchSpaces(pc); 1557 | if (pc[0] != '\n') { 1558 | for (size_t i = 0; ; ++i) { 1559 | ENFORCE(pc[i]); 1560 | if (pc[i] == '\n') { 1561 | file = munchChars(pc, i); 1562 | ++line; 1563 | break; 1564 | } 1565 | } 1566 | } 1567 | "; 1568 | auto f = "/tmp/cxx_replace_testing" 1569 | ~ to!string(uniform(0,int.max)) ~ to!string(uniform(0,int.max)); 1570 | std.file.write(f, s1); 1571 | scope(exit) std.file.remove(f); 1572 | import std.process; 1573 | EXPECT_EQ(executeShell("_bin/linters/flint/cxx_replace 'munch' 'crunch' " 1574 | ~ f).status, 1575 | 0); 1576 | EXPECT_EQ(std.file.readText(f), s1); 1577 | EXPECT_EQ(executeShell("_bin/linters/flint/cxx_replace 'break' 'continue' " 1578 | ~ f).status, 1579 | 0); 1580 | string s2 = std.array.replace(s1, "break", "continue"); 1581 | EXPECT_EQ(std.file.readText(f), s2); 1582 | } 1583 | 1584 | // testThrowsHeapAllocException 1585 | unittest { 1586 | string s1 = "throw new MyException(\"error\");"; 1587 | string filename = "nofile.cpp"; 1588 | auto tokens = tokenize(s1); 1589 | EXPECT_EQ(checkThrowsHeapException(filename, tokens), 1); 1590 | 1591 | string s2 = "throw new (MyException)(\"error\");"; 1592 | tokens = tokenize(s2); 1593 | EXPECT_EQ(checkThrowsHeapException(filename, tokens), 1); 1594 | 1595 | string s3 = "throw new MyTemplatedException();"; 1596 | tokens = tokenize(s3); 1597 | EXPECT_EQ(checkThrowsHeapException(filename, tokens), 1); 1598 | 1599 | string s4 = "throw MyException(\"error\")"; 1600 | tokens = tokenize(s4); 1601 | EXPECT_EQ(checkThrowsHeapException(filename, tokens), 0); 1602 | } 1603 | 1604 | // testHPHPCalls 1605 | unittest { 1606 | Token[] tokens; 1607 | string filename = "nofile.cpp"; 1608 | 1609 | // basic pattern 1610 | string s1 = "using namespace HPHP; 1611 | void f() { 1612 | f_require_module(\"soup\"); 1613 | f_someother_func(1,2,3); 1614 | };"; 1615 | tokens = tokenize(s1); 1616 | EXPECT_EQ(checkHPHPNamespace(filename, tokens), 0); 1617 | 1618 | // alternative namespace syntax 1619 | string s2 = "using namespace ::HPHP; 1620 | void f() { 1621 | f_require_module(\"soup\"); 1622 | f_someother_func(1,2,3); 1623 | };"; 1624 | tokens = tokenize(s2); 1625 | EXPECT_EQ(checkHPHPNamespace(filename, tokens), 0); 1626 | 1627 | // reversed order of usage 1628 | string s3 = "using namespace ::HPHP; 1629 | void f() { 1630 | f_someother_func(1,2,3); 1631 | f_require_module(\"soup\"); 1632 | };"; 1633 | tokens = tokenize(s3); 1634 | EXPECT_EQ(checkHPHPNamespace(filename, tokens), 1); 1635 | 1636 | // all ways to fail without namespace open 1637 | string s4 = "HPHP::f_someother_func(1,2,3); 1638 | ::HPHP::f_someother_func(1,2); 1639 | HPHP::c_className::m_mfunc(1,2); 1640 | ::HPHP::c_className::m_mfunc(1,2); 1641 | HPHP::k_CONSTANT; 1642 | ::HPHP::k_CONSTANT; 1643 | HPHP::ft_sometyped_func(1,2); 1644 | ::HPHP::ft_sometyped_func(1,2);"; 1645 | tokens = tokenize(s4); 1646 | EXPECT_EQ(checkHPHPNamespace(filename, tokens), 8); 1647 | 1648 | // all ways to fail with namespace open 1649 | string s5 = "using namespace HPHP; 1650 | HPHP::f_someother_func(1,2,3); 1651 | ::HPHP::f_someother_func(1,2); 1652 | HPHP::c_className::m_mfunc(1,2); 1653 | ::HPHP::c_className::m_mfunc(1,2); 1654 | HPHP::k_CONSTANT; 1655 | ::HPHP::k_CONSTANT; 1656 | HPHP::ft_sometyped_func(1,2); 1657 | ::HPHP::ft_sometyped_func(1,2); 1658 | f_someother_func(1,2); 1659 | c_className::m_mfunc(1,2); 1660 | k_CONSTANT; 1661 | ft_sometyped_func(1,2);"; 1662 | tokens = tokenize(s5); 1663 | EXPECT_EQ(checkHPHPNamespace(filename, tokens), 12); 1664 | 1665 | // all checks pass after f_require_module 1666 | string s6 = "using namespace HPHP; 1667 | f_require_module(\"some module\"); 1668 | HPHP::f_someother_func(1,2,3); 1669 | ::HPHP::f_someother_func(1,2); 1670 | HPHP::c_className::m_mfunc(1,2); 1671 | ::HPHP::c_className::m_mfunc(1,2); 1672 | HPHP::k_CONSTANT; 1673 | ::HPHP::k_CONSTANT; 1674 | HPHP::ft_sometyped_func(1,2); 1675 | ::HPHP::ft_sometyped_func(1,2); 1676 | f_someother_func(1,2); 1677 | c_className::m_mfunc(1,2); 1678 | k_CONSTANT; 1679 | ft_sometyped_func(1,2);"; 1680 | tokens = tokenize(s6); 1681 | EXPECT_EQ(checkHPHPNamespace(filename, tokens), 0); 1682 | 1683 | // check that c_str is exempt 1684 | string s7 = "using namespace HPHP; 1685 | string c(\"hphp\"); c.c_str();"; 1686 | tokens = tokenize(s7); 1687 | EXPECT_EQ(checkHPHPNamespace(filename, tokens), 0); 1688 | 1689 | // check incorrect syntax 1690 | string s8 = "using namespace ? garbage"; 1691 | tokens = tokenize(s8); 1692 | EXPECT_EQ(checkHPHPNamespace(filename, tokens), 1); 1693 | 1694 | // check that nested namespace usage doesn't confuse us 1695 | string s9 = "using namespace ::HPHP; 1696 | { 1697 | { using namespace HPHP; } 1698 | f_someother_func(1,2,3); 1699 | };"; 1700 | tokens = tokenize(s9); 1701 | EXPECT_EQ(checkHPHPNamespace(filename, tokens), 1); 1702 | 1703 | // check that f_require_module is valid only in HPHP namespace 1704 | string s10 = "f_require_module(\"meaningless\"); 1705 | using namespace HPHP; 1706 | { 1707 | std::f_require_module(\"cake\"); 1708 | f_someother_func(1,2,3); 1709 | HPHP::c_classOops::mf_func(1); 1710 | };"; 1711 | tokens = tokenize(s10); 1712 | EXPECT_EQ(checkHPHPNamespace(filename, tokens), 2); 1713 | 1714 | // check that leaving HPHP scope re-requires module 1715 | // this is arguable as a point of correctness, but better to 1716 | // conservatively warn than silently fail 1717 | string s11 = "int f1() { using namespace HPHP; f_require_module(\"foo\");} 1718 | int second_entry_point() { using namespace HPHP; f_oops(); }"; 1719 | tokens = tokenize(s11); 1720 | EXPECT_EQ(checkHPHPNamespace(filename, tokens), 1); 1721 | } 1722 | 1723 | // testCheckDeprecatedIncludes 1724 | unittest { 1725 | Token[] tokens; 1726 | string filename = "dir/TestFile.cpp"; 1727 | // sanity 1728 | string s0 = "#include \"TestFile.h\" 1729 | #include \"foo.h\""; 1730 | tokens = tokenize(s0); 1731 | 1732 | EXPECT_EQ(checkQuestionableIncludes(filename, tokens), 0); 1733 | 1734 | // error case with one deprecated include 1735 | string s1 = "#include \"TestFile.h\" 1736 | #include \"common/base/Base.h\""; 1737 | tokens = tokenize(s1); 1738 | 1739 | EXPECT_EQ(checkQuestionableIncludes(filename, tokens), 1); 1740 | 1741 | // error case with two deprecated includes 1742 | string s2 = "#include \"TestFile.h\" 1743 | #include \"common/base/Base.h\" 1744 | #include \"common/base/StringUtil.h\""; 1745 | tokens = tokenize(s2); 1746 | 1747 | EXPECT_EQ(checkQuestionableIncludes(filename, tokens), 2); 1748 | 1749 | // No errors if expensive header in .cpp 1750 | string s3 = "#include \"TestFile.h\" 1751 | #include \"multifeed/aggregator/gen-cpp/aggregator_types.h\""; 1752 | tokens = tokenize(s3); 1753 | 1754 | EXPECT_EQ(checkQuestionableIncludes(filename, tokens), 0); 1755 | 1756 | // Two expensive header include from a header 1757 | string headerFile = "dir/TestFile.h"; 1758 | string s4 = "#include \"multifeed/aggregator/gen-cpp/aggregator_types.h\" 1759 | #include \"multifeed/shared/gen-cpp/multifeed_types.h\""; 1760 | tokens = tokenize(s4); 1761 | 1762 | EXPECT_EQ(checkQuestionableIncludes(headerFile, tokens), 2); 1763 | } 1764 | 1765 | 1766 | // testCheckIncludeAssociatedHeader 1767 | unittest { 1768 | Token[] tokens; 1769 | string filename = "dir/TestFile.cpp"; 1770 | // sanity 1771 | string s0 = "#include \"TestFile.h\" 1772 | #include \"SomeOtherFile.h\""; 1773 | tokens = tokenize(s0); 1774 | 1775 | EXPECT_EQ(checkIncludeAssociatedHeader(filename, tokens), 0); 1776 | 1777 | filename = "TestFile.cpp"; 1778 | // sanity hpp 1779 | string s1 = "#include \"TestFile.hpp\" 1780 | #include \"SomeOtherFile\""; 1781 | tokens = tokenize(s1); 1782 | 1783 | EXPECT_EQ(checkIncludeAssociatedHeader(filename, tokens), 0); 1784 | 1785 | // defines and pragmas before 1786 | string s2 = "#pragma option -O2 1787 | #define PI 3.14 1788 | #include \"TestFile.h\" 1789 | #include \"SomeOtherFile.h\""; 1790 | tokens = tokenize(s2); 1791 | 1792 | EXPECT_EQ(checkIncludeAssociatedHeader(filename, tokens), 0); 1793 | 1794 | // no associated header 1795 | string s3 = "#include \"\" 1796 | #include \"SomeOtherFile.h\""; 1797 | tokens = tokenize(s3); 1798 | 1799 | EXPECT_EQ(checkIncludeAssociatedHeader(filename, tokens), 0); 1800 | 1801 | // case sensitiveness 1802 | string s4 = "#include \"testfile.h\" 1803 | #include \"SomeOtherFile.h\""; 1804 | tokens = tokenize(s4); 1805 | 1806 | EXPECT_EQ(checkIncludeAssociatedHeader(filename, tokens), 0); 1807 | 1808 | // no good 1809 | string s5 = "#include \"\" 1810 | #include \"SomeOtherFile.h\" 1811 | #pragma option -O2 1812 | #include \"TestFile.h\""; 1813 | tokens = tokenize(s5, filename); 1814 | 1815 | EXPECT_EQ(checkIncludeAssociatedHeader(filename, tokens), 1); 1816 | 1817 | // no good 1818 | string s6 = "#include \"\" 1819 | #include \"SomeOtherFile.h\" 1820 | #pragma option -O2 1821 | #include \"TestFile.hpp\" 1822 | #include \"Dijkstra.h\""; 1823 | tokens = tokenize(s6); 1824 | 1825 | EXPECT_EQ(checkIncludeAssociatedHeader(filename, tokens), 1); 1826 | 1827 | // erroneous file 1828 | string s7 = "#include #include #include"; 1829 | tokens = tokenize(s7); 1830 | 1831 | EXPECT_EQ(checkIncludeAssociatedHeader(filename, tokens), 0); 1832 | 1833 | // no good using relative path 1834 | filename = "../TestFile.cpp"; 1835 | string s8 = "#include \"\" 1836 | #include \"SomeOtherFile.h\" 1837 | #pragma option -O2 1838 | #include \"TestFile.h\""; 1839 | tokens = tokenize(s8); 1840 | 1841 | EXPECT_EQ(checkIncludeAssociatedHeader(filename, tokens), 1); 1842 | 1843 | // good using relative path 1844 | string s9 = "#include 1845 | #include \"SomeOtherFile.h\" 1846 | #pragma option -O2 1847 | #include \"../TestFile.h\""; 1848 | tokens = tokenize(s9, filename); 1849 | 1850 | EXPECT_EQ(checkIncludeAssociatedHeader(filename, tokens), 0); 1851 | 1852 | // good when header having same name under other directory 1853 | filename = "dir/TestFile.cpp"; 1854 | string s10 = "#include \"SomeOtherDir/TestFile.h\""; 1855 | tokens = tokenize(s10); 1856 | 1857 | EXPECT_EQ(checkIncludeAssociatedHeader(filename, tokens), 0); 1858 | 1859 | // testing not breaking relative path 1860 | filename = "TestFile.cpp"; 1861 | string s11 = "#include 1862 | #include \"TestFile.h\""; 1863 | tokens = tokenize(s11); 1864 | 1865 | EXPECT_EQ(checkIncludeAssociatedHeader(filename, tokens), 1); 1866 | 1867 | // no good using absolute path for filename 1868 | filename = "/home/philipp/fbcode/test/testfile.cpp"; 1869 | string s12 = "#include 1870 | #include \"file.h\" 1871 | #include \"test/testfile.h\""; 1872 | tokens = tokenize(s12); 1873 | 1874 | EXPECT_EQ(checkIncludeAssociatedHeader(filename, tokens), 1); 1875 | 1876 | // good using absolute path for filename 1877 | string s13 = "#include 1878 | #include \"file.h\" 1879 | #include \"othertest/testfile.h\""; 1880 | tokens = tokenize(s13); 1881 | 1882 | EXPECT_EQ(checkIncludeAssociatedHeader(filename, tokens), 0); 1883 | 1884 | // good with 'nolint' comments and PRECOMPILED 1885 | filename = "A.cpp"; 1886 | string s14 = "#include // nolint 1887 | #include \"abracadabra\" // more than nolint comment 1888 | #include PRECOMPILED 1889 | #include \"A.h\""; 1890 | tokens = tokenize(s14); 1891 | 1892 | EXPECT_EQ(checkIncludeAssociatedHeader(filename, tokens), 0); 1893 | } 1894 | 1895 | 1896 | // testMemset 1897 | unittest { 1898 | string filename = "nofile.cpp"; 1899 | Token[] tokens; 1900 | 1901 | // good calls 1902 | string s1 = "memset(foo, 0, sizeof(foo)); 1903 | memset(foo, 1, sizeof(foo)); 1904 | memset(foo, 12, 1) 1905 | memset(T::bar(this, is, a pointers),(12+3)/5, 42); 1906 | memset(what(12,1), 0, sizeof(foo)); 1907 | memset(this->get(12,1), 0, sizeof(foo)); 1908 | memset(&foo, 0, sizeof(foo));"; 1909 | tokens = tokenize(s1); 1910 | EXPECT_EQ(checkMemset(filename, tokens), 0); 1911 | 1912 | // funky call that would terminate all subsequent check 1913 | string s1b = "memset(SC, a < b ? 0 : 1, sizeof(foo));\n"; 1914 | tokens = tokenize(s1b); 1915 | EXPECT_EQ(checkMemset(filename, tokens), 0); 1916 | 1917 | // bad call with 0 1918 | string s2 = "memset(foo, 12, 0);"; 1919 | tokens = tokenize(s2); 1920 | EXPECT_EQ(checkMemset(filename, tokens), 1); 1921 | 1922 | // bad call with 1 1923 | string s3 = "memset(foo, sizeof(bar), 1);"; 1924 | tokens = tokenize(s3); 1925 | EXPECT_EQ(checkMemset(filename, tokens), 1); 1926 | 1927 | // combination of bad calls 1928 | string s4 = s2 ~ s3; 1929 | s4 ~= "memset(T1::getPointer(), sizeof(T2), 0); 1930 | memset(&foo, sizeof(B), 1);"; 1931 | tokens = tokenize(s4); 1932 | EXPECT_EQ(checkMemset(filename, tokens), 4); 1933 | 1934 | // combination of good and bad calls 1935 | string s5 = s1 ~ s4; 1936 | tokens = tokenize(s5); 1937 | EXPECT_EQ(checkMemset(filename, tokens), 4); 1938 | 1939 | // combination of bad calls and funky calls 1940 | string s5b = s1b ~ s4; 1941 | tokens = tokenize(s5b); 1942 | EXPECT_EQ(checkMemset(filename, tokens), 0); 1943 | 1944 | // combination of bad calls and funky calls, different way 1945 | string s6 = s4 ~ s1b; 1946 | tokens = tokenize(s6); 1947 | EXPECT_EQ(checkMemset(filename, tokens), 4); 1948 | 1949 | // process only function calls 1950 | string s7 = "using std::memset;"; 1951 | tokens = tokenize(s7); 1952 | EXPECT_EQ(checkMemset(filename, tokens), 0); 1953 | } 1954 | 1955 | // testCheckInlHeaderInclusions 1956 | unittest { 1957 | Token[] tokens; 1958 | 1959 | string s1 = " 1960 | #include \"Foo.h\" 1961 | #include \"Bar-inl.h\" 1962 | #include \"foo/baz/Bar-inl.h\" 1963 | 1964 | int main() {} 1965 | "; 1966 | 1967 | auto filename = "..."; 1968 | tokens = tokenize(s1, filename); 1969 | EXPECT_EQ(checkInlHeaderInclusions("Foo.h", tokens), 2); 1970 | EXPECT_EQ(checkInlHeaderInclusions("Bar.h", tokens), 0); 1971 | 1972 | string s2 = " 1973 | #include \"Foo-inl.h\" 1974 | "; 1975 | auto filename2 = "FooBar.h"; 1976 | tokens = tokenize(s2, filename2); 1977 | EXPECT_EQ(checkInlHeaderInclusions(filename2, tokens), 1); 1978 | } 1979 | 1980 | // testUpcaseNull 1981 | unittest { 1982 | Token[] tokens; 1983 | 1984 | string s1 = " 1985 | #include 1986 | int main() { int x = NULL; } 1987 | "; 1988 | tokens = tokenize(s1); 1989 | EXPECT_EQ(checkUpcaseNull("filename", tokens), 1); 1990 | 1991 | string s2 = "int main() { int* x = nullptr; }\n"; 1992 | tokens = tokenize(s2); 1993 | EXPECT_EQ(checkUpcaseNull("filename", tokens), 0); 1994 | } 1995 | 1996 | // testSmartPtrUsage 1997 | unittest { 1998 | Token[] tokens; 1999 | string filename = "..."; 2000 | 2001 | // test basic pattern with known used namespaces 2002 | string s1 = " 2003 | std::shared_ptr p(new Foo(whatever)); 2004 | "; 2005 | tokenize(s1, filename, tokens); 2006 | EXPECT_EQ(checkSmartPtrUsage(filename, tokens), 1); 2007 | 2008 | string s2 = " 2009 | boost::shared_ptr p(new Foo(whatever)); 2010 | "; 2011 | tokenize(s2, filename, tokens); 2012 | EXPECT_EQ(checkSmartPtrUsage(filename, tokens), 1); 2013 | 2014 | string s3 = " 2015 | facebook::shared_ptr p(new Foo(whatever)); } 2016 | "; 2017 | tokenize(s3, filename, tokens); 2018 | EXPECT_EQ(checkSmartPtrUsage(filename, tokens), 1); 2019 | 2020 | string s4 = " 2021 | shared_ptr p(new Foo(whatever)); } 2022 | "; 2023 | tokenize(s4, filename, tokens); 2024 | EXPECT_EQ(checkSmartPtrUsage(filename, tokens), 1); 2025 | 2026 | // this matches and suggests using allocate_shared 2027 | string s10 = " 2028 | shared_ptr p(new Foo(whatever), d, a); } 2029 | "; 2030 | tokenize(s10, filename, tokens); 2031 | EXPECT_EQ(checkSmartPtrUsage(filename, tokens), 1); 2032 | 2033 | // this matches without any special suggestion but using 2034 | // make_shared, Deleter are not well supported in make_shared 2035 | string s11 = " 2036 | shared_ptr p(new Foo(whatever), d); } 2037 | "; 2038 | tokenize(s11, filename, tokens); 2039 | EXPECT_EQ(checkSmartPtrUsage(filename, tokens), 1); 2040 | 2041 | // test in a more "complex" code 2042 | string s12 = " 2043 | int main() { std::shared_ptr p(new Foo(whatever)); } 2044 | "; 2045 | tokenize(s12, filename, tokens); 2046 | EXPECT_EQ(checkSmartPtrUsage(filename, tokens), 1); 2047 | 2048 | // function declarations are ok 2049 | string s14 = " 2050 | std::shared_ptr foo(Foo foo); 2051 | "; 2052 | tokenize(s14, filename, tokens); 2053 | EXPECT_EQ(checkSmartPtrUsage(filename, tokens), 0); 2054 | 2055 | // assignments are fine 2056 | string s16 = " 2057 | std::shared_ptr foo = foo(); 2058 | "; 2059 | tokenize(s16, filename, tokens); 2060 | EXPECT_EQ(checkSmartPtrUsage(filename, tokens), 0); 2061 | } 2062 | 2063 | // testUniquePtrUsage 2064 | unittest { 2065 | Token[] tokens; 2066 | string filename = "..."; 2067 | 2068 | string s1 = " 2069 | unique_ptr p(new Foo(whatever)); 2070 | "; 2071 | tokenize(s1, filename, tokens); 2072 | EXPECT_EQ(checkUniquePtrUsage(filename, tokens), 0); 2073 | 2074 | //Should skip this, since its not of namespace std 2075 | string s2 = " 2076 | boost::unique_ptr p(new Foo[5]); 2077 | "; 2078 | tokenize(s2, filename, tokens); 2079 | EXPECT_EQ(checkUniquePtrUsage(filename, tokens), 0); 2080 | 2081 | // this matches and suggests using array in the template 2082 | string s3 = " 2083 | unique_ptr p(new Foo[5]); 2084 | "; 2085 | tokenize(s3, filename, tokens); 2086 | EXPECT_EQ(checkUniquePtrUsage(filename, tokens), 1); 2087 | 2088 | // this should not match 2089 | string s4 = " 2090 | shared_ptr p(new Foo(Bar[5])); 2091 | "; 2092 | tokenize(s4, filename, tokens); 2093 | EXPECT_EQ(checkUniquePtrUsage(filename, tokens), 0); 2094 | 2095 | // test in a more "complex" code 2096 | string s5 = " 2097 | int main() { unique_ptr p(new Foo[5]); 2098 | unique_ptr p(new Foo[5]); 2099 | unique_ptr q(new Bar[6]);}"; 2100 | tokenize(s5, filename, tokens); 2101 | EXPECT_EQ(checkUniquePtrUsage(filename, tokens), 2); 2102 | 2103 | string s6 = " 2104 | unique_ptr< unique_ptr > 2105 | p(new unique_ptr(new int[2])); 2106 | "; 2107 | tokenize(s6, filename, tokens); 2108 | EXPECT_EQ(checkUniquePtrUsage(filename, tokens), 0); 2109 | 2110 | string s7 = " 2111 | unique_ptr< unique_ptr > p(new unique_ptr[6]);"; 2112 | tokenize(s7, filename, tokens); 2113 | EXPECT_EQ(checkUniquePtrUsage(filename, tokens), 1); 2114 | 2115 | // temporaries 2116 | string s8 = " 2117 | unique_ptr(new int());"; 2118 | tokenize(s8, filename, tokens); 2119 | EXPECT_EQ(checkUniquePtrUsage(filename, tokens), 1); 2120 | 2121 | string s9 = " 2122 | unique_ptr(new int());"; 2123 | tokenize(s9, filename, tokens); 2124 | EXPECT_EQ(checkUniquePtrUsage(filename, tokens), 0); 2125 | 2126 | string s10 = " 2127 | unique_ptr(new int[5]);"; 2128 | tokenize(s10, filename, tokens); 2129 | EXPECT_EQ(checkUniquePtrUsage(filename, tokens), 1); 2130 | 2131 | string s11 = " 2132 | unique_ptr(new int[5]);"; 2133 | tokenize(s11, filename, tokens); 2134 | EXPECT_EQ(checkUniquePtrUsage(filename, tokens), 0); 2135 | 2136 | string s12 = " 2137 | unique_ptr function() { 2138 | unique_ptr ret; 2139 | return ret; 2140 | } 2141 | "; 2142 | tokenize(s12, filename, tokens); 2143 | EXPECT_EQ(checkUniquePtrUsage(filename, tokens), 0); 2144 | 2145 | string s13 = " 2146 | unique_ptr< 2147 | unique_ptr > foo(new unique_ptr[12]); 2148 | "; 2149 | tokenize(s13, filename, tokens); 2150 | EXPECT_EQ(checkUniquePtrUsage(filename, tokens), 1); 2151 | 2152 | string s14 = " 2153 | void function(unique_ptr a, 2154 | unique_ptr b = unique_ptr()) { 2155 | } 2156 | "; 2157 | tokenize(s14, filename, tokens); 2158 | EXPECT_EQ(checkUniquePtrUsage(filename, tokens), 0); 2159 | 2160 | string s15 = " 2161 | int main() { 2162 | vector args = something(); 2163 | unique_ptr p(new char*[args.size() + 1]); 2164 | } 2165 | "; 2166 | tokenize(s15, filename, tokens); 2167 | EXPECT_EQ(checkUniquePtrUsage(filename, tokens), 0); 2168 | 2169 | string s16 = " 2170 | int main() { 2171 | vector args = something(); 2172 | unique_ptr p( 2173 | new char const* volatile*[args.size() + 1] 2174 | ); 2175 | } 2176 | "; 2177 | tokenize(s16, filename, tokens); 2178 | EXPECT_EQ(checkUniquePtrUsage(filename, tokens), 0); 2179 | 2180 | string s17 = " 2181 | int main() { 2182 | unique_ptr p( 2183 | new const volatile char*[1] 2184 | ); 2185 | } 2186 | "; 2187 | tokenize(s17, filename, tokens); 2188 | EXPECT_EQ(checkUniquePtrUsage(filename, tokens), 0); 2189 | } 2190 | 2191 | // testThreadSpecificPtr 2192 | unittest { 2193 | Token[] tokens; 2194 | string filename = "..."; 2195 | 2196 | string s1 = " 2197 | int main() { 2198 | boost::thread_specific_ptr p; 2199 | } 2200 | "; 2201 | tokenize(s1, filename, tokens); 2202 | EXPECT_EQ(checkBannedIdentifiers(filename, tokens), 1); 2203 | 2204 | string s2 = " 2205 | int main() { 2206 | folly::ThreadLocalPtr p;y 2207 | } 2208 | }"; 2209 | tokenize(s2, filename, tokens); 2210 | EXPECT_EQ(checkBannedIdentifiers(filename, tokens), 0); 2211 | } 2212 | 2213 | // testNamespaceScopedStatics 2214 | unittest { 2215 | Token[] tokens; 2216 | string filename = "somefile.h"; 2217 | 2218 | string s1 = " 2219 | namespace bar { 2220 | static const int x = 42; 2221 | static inline getX() { return x; } 2222 | static void doFoo(); 2223 | int getStuff() { 2224 | static int s = 22; 2225 | return s; 2226 | } 2227 | }"; 2228 | tokenize(s1, filename, tokens); 2229 | EXPECT_EQ(checkNamespaceScopedStatics(filename, tokens), 3); 2230 | 2231 | string s2 = " 2232 | class bar; 2233 | namespace foo { 2234 | static const int x = 42; 2235 | }"; 2236 | tokenize(s2, filename, tokens); 2237 | EXPECT_EQ(checkNamespaceScopedStatics(filename, tokens), 1); 2238 | 2239 | string s3 = " 2240 | namespace bar { 2241 | class Status { 2242 | public: 2243 | static Status OK() {return 22;} 2244 | }; 2245 | static void doBar(); 2246 | } 2247 | static void doFoo();"; 2248 | tokenize(s3, filename, tokens); 2249 | EXPECT_EQ(checkNamespaceScopedStatics(filename, tokens), 2); 2250 | } 2251 | 2252 | // testCheckMutexHolderHasName 2253 | unittest { 2254 | string s = " 2255 | unique_lock foo() { 2256 | lock_guard (); 2257 | lock_guard(m_lock); 2258 | lock_guard(m_lock); 2259 | } 2260 | "; 2261 | Token[] tokens; 2262 | string filename = "nofile.cpp"; 2263 | tokenize(s, filename, tokens); 2264 | EXPECT_EQ(checkMutexHolderHasName(filename, tokens), 3); 2265 | 2266 | string s2 = " 2267 | unique_lock foo() { 2268 | vector (); 2269 | lock_guard s(thing); 2270 | unique_lock l(m_lock); 2271 | 2272 | bar(unique_lock(m_lock)); 2273 | return unique_lock(m_lock); 2274 | } 2275 | "; 2276 | tokens.destroy(); 2277 | tokenize(s2, filename, tokens); 2278 | EXPECT_EQ(checkMutexHolderHasName(filename, tokens), 0); 2279 | 2280 | string s3 = "y 2281 | void foo(lock_guard& m, lock_guard* m2) { 2282 | } 2283 | "; 2284 | tokens.destroy(); 2285 | tokenize(s3, filename, tokens); 2286 | EXPECT_EQ(checkMutexHolderHasName(filename, tokens), 0); 2287 | } 2288 | 2289 | // testOSSIncludes 2290 | unittest { 2291 | string s = 2292 | "#include \n" ~ 2293 | "#include \n" ~ 2294 | "#include \"no-slash-is-safe\"\n" ~ 2295 | "#include \"folly/is/safe\"\n" ~ 2296 | "#include \"hphp/is/safe/in/hphp\"\n" ~ 2297 | "#include \"hphp/facebook/hphp-facebook-is-safe-subdirectory\"\n" ~ 2298 | "#include \"random/unsafe/in/oss\"\n" ~ 2299 | "#include \"oss-is-safe\" // nolint\n" ~ 2300 | "#include \"oss/is/safe\" // nolint\n" ~ 2301 | "#include \"oss-at-eof-should-be-safe\" // nolint"; 2302 | 2303 | import std.typecons; 2304 | Tuple!(string, int)[] filenamesAndErrors = [ 2305 | tuple("anyfile.cpp", 0), 2306 | tuple("non-oss-project/anyfile.cpp", 0), 2307 | tuple("folly/anyfile.cpp", 3), 2308 | tuple("hphp/anyfile.cpp", 1), 2309 | tuple("hphp/facebook/anyfile.cpp", 0), 2310 | tuple("proxygen/lib/anyfile.cpp", 3), 2311 | ]; 2312 | 2313 | foreach (ref p; filenamesAndErrors) { 2314 | Token[] tokens; 2315 | tokenize(s, p[0], tokens); 2316 | EXPECT_EQ(p[1], checkOSSIncludes(p[0], tokens)); 2317 | } 2318 | } 2319 | 2320 | // testMultipleIncludes 2321 | 2322 | unittest { 2323 | import std.typecons; 2324 | Tuple!(string, int)[] includesAndErrors = [ 2325 | tuple("#include \n" ~ 2326 | "#include \n" ~ 2327 | "#include \n", 1), 2328 | tuple("#include \n" ~ 2329 | "#include \"includedTwice\"\n" ~ 2330 | "#include \"includedTwice\"\n", 1), 2331 | tuple("#include \n" ~ 2332 | "include \"includedTwice\"\n", 0), 2333 | tuple("#include \n" ~ 2334 | "#include \n", 0), 2335 | tuple("#include \n" ~ 2336 | "#include /* nolint */\n", 0) 2337 | ]; 2338 | 2339 | foreach (ref p; includesAndErrors) { 2340 | Token[] tokens; 2341 | tokenize(p[0], "testFile.cpp", tokens); 2342 | EXPECT_EQ(p[1], checkMultipleIncludes("testFile.cpp", tokens)); 2343 | tokenize(p[0], "testFile.c", tokens); 2344 | EXPECT_EQ(p[1], checkMultipleIncludes("testFile.c", tokens)); 2345 | } 2346 | } 2347 | 2348 | // testCheckBreakInSynchronized 2349 | unittest { 2350 | string s = " 2351 | int foo() {\n 2352 | int i = 1;\n 2353 | Synchronized> v;\n 2354 | while(i > 0) {\n 2355 | SYNCHRONIZED (v) {\n 2356 | if(v.size() > 0) break; \n 2357 | }\n 2358 | i--;\n 2359 | };\n 2360 | }\n 2361 | "; 2362 | Token[] tokens; 2363 | string filename = "nofile.cpp"; 2364 | tokenize(s, filename, tokens); 2365 | EXPECT_EQ(checkBreakInSynchronized(filename, tokens), 1); 2366 | 2367 | string s2 = " 2368 | void foo() {\n 2369 | Synchronized> v;\n 2370 | for(int i = 10; i < 0; i--) { \n 2371 | if(i < 1) break; \n 2372 | SYNCHRONIZED(v) { \n 2373 | if(v.size() > 5) break; \n 2374 | while(v.size() < 5) { \n 2375 | v.push(1); \n 2376 | if(v.size() > 3) break; \n 2377 | } \n 2378 | if(v.size() > 4) break; \n 2379 | } \n 2380 | i--; \n 2381 | if(i > 2) continue; \n 2382 | }\n 2383 | }\n 2384 | "; 2385 | tokens.destroy(); 2386 | tokenize(s2, filename, tokens); 2387 | EXPECT_EQ(checkBreakInSynchronized(filename, tokens), 2); 2388 | 2389 | string s3 = " 2390 | void foo() {\n 2391 | Synchronized> v;\n 2392 | SYNCHRONIZED_CONST(v) {\n 2393 | for(int i = 0; i < v.size(); i++) {\n 2394 | if(v[i] == 5) break;\n 2395 | }\n 2396 | }\n 2397 | }\n 2398 | "; 2399 | 2400 | tokens.destroy(); 2401 | tokenize(s3, filename, tokens); 2402 | EXPECT_EQ(checkBreakInSynchronized(filename, tokens), 0); 2403 | } 2404 | 2405 | // testFollyStringPieceByValue 2406 | unittest { 2407 | string filename = "nofile.cpp"; 2408 | Token[] tokens; 2409 | 2410 | // const & not ok 2411 | string s = "const folly::StringPiece& f(const StringPiece&)\n"; 2412 | tokenize(s, filename, tokens); 2413 | EXPECT_EQ(checkFollyStringPieceByValue(filename, tokens), 2); 2414 | 2415 | // non-const & is ok 2416 | string s2 = "folly::StringPiece& g(StringPiece&)\n"; 2417 | tokens.destroy(); 2418 | tokenize(s2, filename, tokens); 2419 | EXPECT_EQ(checkFollyStringPieceByValue(filename, tokens), 0); 2420 | 2421 | // by value is ok 2422 | string s3 = "folly::StringPiece g(StringPiece)\n"; 2423 | tokens.destroy(); 2424 | tokenize(s3, filename, tokens); 2425 | EXPECT_EQ(checkFollyStringPieceByValue(filename, tokens), 0); 2426 | } 2427 | 2428 | // testCheckRandomUsage 2429 | unittest { 2430 | string filename = "nofile.cpp"; 2431 | Token[] tokens; 2432 | 2433 | // random_device 2434 | string s = " 2435 | random_device rd; 2436 | std::uniform_int_distribution dist; 2437 | std::random_device rd2; 2438 | r = dist(rd); 2439 | "; 2440 | tokenize(s, filename, tokens); 2441 | EXPECT_EQ(checkRandomUsage(filename, tokens), 2); 2442 | 2443 | // rand() 2444 | string s1 = " 2445 | int randomInt = rand(); 2446 | tr_rand(); 2447 | "; 2448 | tokens.destroy(); 2449 | tokenize(s1, filename, tokens); 2450 | EXPECT_EQ(checkRandomUsage(filename, tokens), 1); 2451 | 2452 | // RandomInt32 2453 | string s2 = " 2454 | RandomInt32 r; 2455 | GetRandomInt32(); 2456 | uint32_t rand = r(); 2457 | "; 2458 | tokens.destroy(); 2459 | tokenize(s2, filename, tokens); 2460 | EXPECT_EQ(checkRandomUsage(filename, tokens), 1); 2461 | 2462 | // RandomInt64 2463 | string s3 = " 2464 | RandomInt64 r; 2465 | GetRandomInt64(); 2466 | uint64_t rand = r(); 2467 | "; 2468 | tokens.destroy(); 2469 | tokenize(s3, filename, tokens); 2470 | EXPECT_EQ(checkRandomUsage(filename, tokens), 1); 2471 | 2472 | // std::random_shuffle 2473 | string s4 = " 2474 | random_shuffle(x, y, z); 2475 | "; 2476 | tokens.destroy(); 2477 | tokenize(s4, filename, tokens); 2478 | EXPECT_EQ(checkRandomUsage(filename, tokens), 1); 2479 | } 2480 | 2481 | // testCheckSleepUsage 2482 | unittest { 2483 | string filename = "testFile.cpp"; 2484 | Token[] tokens; 2485 | 2486 | // sleep 2487 | string s = " 2488 | sleep(1); 2489 | //Nothing 2490 | sleep(5); 2491 | "; 2492 | tokenize(s, filename, tokens); 2493 | EXPECT_EQ(checkSleepUsage(filename, tokens), 2); 2494 | 2495 | // usleep() 2496 | string s1 = " 2497 | usleep(200); 2498 | "; 2499 | tokens.destroy(); 2500 | tokenize(s1, filename, tokens); 2501 | EXPECT_EQ(checkSleepUsage(filename, tokens), 1); 2502 | 2503 | // sleep_for 2504 | string s2 = " 2505 | std::this_thread::sleep_for(std::chrono::seconds(3)); 2506 | "; 2507 | tokens.destroy(); 2508 | tokenize(s2, filename, tokens); 2509 | EXPECT_EQ(checkSleepUsage(filename, tokens), 1); 2510 | 2511 | // sleep_until 2512 | string s3 = " 2513 | this_thread::sleep_until(chrono::system_clock::now()); 2514 | "; 2515 | tokens.destroy(); 2516 | tokenize(s3, filename, tokens); 2517 | EXPECT_EQ(checkSleepUsage(filename, tokens), 1); 2518 | 2519 | // No false positives 2520 | string s4 = ` 2521 | //sleep comment 2522 | sleepy_code(); 2523 | DEFINE_int32(sleep, 0, "trolololol"); 2524 | `; 2525 | tokens.destroy(); 2526 | tokenize(s4, filename, tokens); 2527 | EXPECT_EQ(checkSleepUsage(filename, tokens), 0); 2528 | 2529 | // Override lint rule mechanism 2530 | // Scope test: one lint error will apply for this test (the last line). 2531 | stderr.writeln("-------------------------------------TESTASFASDFASDASDASD"); 2532 | string s5 = " 2533 | /* sleep override */ sleep(); 2534 | /* sleep override */ this_thread::sleep_for(std::chrono::milliseconds(200)); 2535 | /* sleep override */ std::this_thread::sleep_for(std::chrono::milliseconds(200)); 2536 | /* sleep override */ appliesToThisInstead(); sleep(); 2537 | "; 2538 | tokens.destroy(); 2539 | tokenize(s5, filename, tokens); 2540 | EXPECT_EQ(checkSleepUsage(filename, tokens), 1); 2541 | } 2542 | 2543 | version(facebook) { 2544 | unittest { 2545 | Token[] tokens; 2546 | string filename = "somefileforAngleBrackets.cpp"; 2547 | 2548 | string s1 = " 2549 | #include PRECOMPILED 2550 | #include \"folly/Foo.h\" // not ok 2551 | #include \"folly/Bar.h\" // ok because nolint 2552 | #include // ok because folly likes angle brackets 2553 | "; 2554 | tokenize(s1, filename, tokens); 2555 | 2556 | // Only a warning if used outside of folly or thrift 2557 | EXPECT_EQ(checkAngleBracketIncludes(filename, tokens), 0); 2558 | 2559 | filename = "thrift/somefileForAngleBrackets.cpp"; 2560 | tokenize(s1, filename, tokens); 2561 | 2562 | EXPECT_EQ(checkAngleBracketIncludes(filename, tokens), 1); 2563 | } 2564 | } 2565 | 2566 | // testSkipTemplateSpec 2567 | unittest { 2568 | Token[] tokens; 2569 | string filename = "src.cpp"; 2570 | 2571 | string content = ">C"; 2572 | tokenize(content, filename, tokens); 2573 | 2574 | auto r = skipTemplateSpec(tokens); 2575 | EXPECT_EQ(r.length != 0, true); 2576 | if (r.length > 0) { 2577 | EXPECT_EQ(r.front.type_, tk!">>"); 2578 | } 2579 | } 2580 | 2581 | // allow /* may throw */ before constructor 2582 | unittest { 2583 | string code = " 2584 | struct Foo { 2585 | Foo() {} 2586 | /* may throw */ Foo(Foo &&) {} 2587 | }; 2588 | "; 2589 | string filename = "nofile.cpp"; 2590 | auto tokens = tokenize(code, filename); 2591 | EXPECT_EQ(checkConstructors(filename, tokens), 0); 2592 | } 2593 | 2594 | // testCheckExitStatus 2595 | unittest { 2596 | string code = " 2597 | exit(-1); 2598 | "; 2599 | string filename = "nofile.cpp"; 2600 | auto tokens = tokenize(code, filename); 2601 | EXPECT_EQ(checkExitStatus(filename, tokens), 1); 2602 | code = " 2603 | _exit(-1); 2604 | "; 2605 | tokens = tokenize(code, filename); 2606 | EXPECT_EQ(checkExitStatus(filename, tokens), 1); 2607 | code = " 2608 | ::exit(-1); 2609 | "; 2610 | tokens = tokenize(code, filename); 2611 | EXPECT_EQ(checkExitStatus(filename, tokens), 1); 2612 | code = " 2613 | _exit(EXIT_FAILURE); 2614 | "; 2615 | tokens = tokenize(code, filename); 2616 | EXPECT_EQ(checkExitStatus(filename, tokens), 0); 2617 | code = " 2618 | _exit(0); 2619 | "; 2620 | tokens = tokenize(code, filename); 2621 | EXPECT_EQ(checkExitStatus(filename, tokens), 0); 2622 | code = " 2623 | validFunction(-1); 2624 | "; 2625 | tokens = tokenize(code, filename); 2626 | EXPECT_EQ(checkExitStatus(filename, tokens), 0); 2627 | } 2628 | 2629 | void main(string[] args) { 2630 | enforce(c_mode == false); 2631 | } 2632 | --------------------------------------------------------------------------------