diff --git a/src/source_util.cpp b/src/source_util.cpp index 38477b6..ce2c57f 100644 --- a/src/source_util.cpp +++ b/src/source_util.cpp @@ -74,57 +74,21 @@ bool sourceRangeContainsOnly(clang::SourceLocation beginLocation, return true; } -// Find the first r-paren token that balances the l-paren at the beginning of -// the list. If no such token exists, return a past the end iterator -llvm::ArrayRef::const_iterator -skipToBalancedRParenTok(llvm::ArrayRef::const_iterator begin, - llvm::ArrayRef::const_iterator end, - const clang::SourceManager &SM) { - if (begin == end) { - return end; - } - - assert(begin->is(clang::tok::l_paren) && - "First iterator must point to an lparen token! "); - - int leftBalance = 0; - clang::LangOptions langOpts; - - // If there is an l_paren token after 'start': - // find the r_paren that balances it within that - // subexpression. - for (auto it = begin; it != end; ++it) { - if (it->is(clang::tok::l_paren)) { - ++leftBalance; - } else if (it->is(clang::tok::r_paren)) { - --leftBalance; - } - - // If balanced, return the iterator pointing to the r-paren - if (leftBalance == 0) { - return it; - } - } - - // Default to returning the end iterator in case we never hit 0 balance in - // the preceeding loop - return end; -} - } // namespace clang::CharSourceRange SourceUtil::expandRange(const clang::SourceRange &range, const clang::SourceManager &sourceManager) { // Get the start location, resolving from macro definition to macro call - // location. Special handling is needed for a statement in a macro body, we - // want to resolve to the entire macro call. + // location. The loop is adapted from 'clang::SourceManager::getFileLoc'. clang::SourceLocation begin = range.getBegin(); - if (sourceManager.isMacroBodyExpansion(begin)) { - begin = sourceManager.getExpansionRange(begin).getBegin(); - } else { - begin = sourceManager.getFileLoc(begin); + while (!begin.isFileID()) { + if (sourceManager.isMacroArgExpansion(begin)) { + begin = sourceManager.getImmediateSpellingLoc(begin); + } else { + begin = sourceManager.getImmediateExpansionRange(begin).getBegin(); + } } // Get the end location, resolving from macro definition to macro call @@ -132,10 +96,12 @@ SourceUtil::expandRange(const clang::SourceRange &range, // last token in it, so we must use the lexer to traverse the token too. clang::SourceLocation end = range.getEnd(); - if (sourceManager.isMacroBodyExpansion(end)) { - end = sourceManager.getExpansionRange(end).getEnd(); - } else { - end = sourceManager.getFileLoc(end); + while (!end.isFileID()) { + if (sourceManager.isMacroArgExpansion(end)) { + end = sourceManager.getImmediateSpellingLoc(end); + } else { + end = sourceManager.getImmediateExpansionRange(end).getEnd(); + } } end = clang::Lexer::getLocForEndOfToken(end, 0, sourceManager, clang::LangOptions()); @@ -168,9 +134,9 @@ std::string SourceUtil::getMacroNameForStatement( while (loc.isMacroID()) { if (sourceManager.isMacroBodyExpansion(loc)) { - return clang::Lexer::getImmediateMacroName( - loc, sourceManager, clang::LangOptions()) - .str(); + return clang::Lexer::getImmediateMacroName(loc, sourceManager, + clang::LangOptions()) + .str(); } loc = sourceManager.getImmediateMacroCallerLoc(loc); @@ -249,6 +215,7 @@ bool SourceUtil::isPartialMacro(const clang::SourceRange &sourceRange, getMacroTokens(begin, sourceManager, preprocessor); if (!tokens.empty()) { clang::SourceLocation macroStart = tokens.front().getLocation(); + // FIXME // There is potentially a bug here, this is unable to deal with macros // that expand to more than one access expression @@ -262,9 +229,14 @@ bool SourceUtil::isPartialMacro(const clang::SourceRange &sourceRange, } } - // Move up one level closer to the expansion point + // Move up one level closer to the expansion point. This code is adapted + // from 'clang::SourceManager::getImmediateMacroCallerLoc'. - begin = sourceManager.getImmediateMacroCallerLoc(begin); + if (sourceManager.isMacroArgExpansion(begin)) { + begin = sourceManager.getImmediateSpellingLoc(begin); + } else { + begin = sourceManager.getImmediateExpansionRange(begin).getBegin(); + } } // Trace through levels of macros that are expanded by the end of the @@ -284,53 +256,19 @@ bool SourceUtil::isPartialMacro(const clang::SourceRange &sourceRange, // Only process macros where the statement is in the body, not ones where // it is an argument. - // Get information about the caller macro that was last expanded - - const clang::MacroInfo *currentMacro = - getMacroInfo(end, sourceManager, preprocessor); - if (sourceManager.isMacroBodyExpansion(end)) { + // Check that there are only spaces or '(' between the beginning of the + // macro and part corresponding to the beginning of the statement. - // Tokens for the current macro's spelling, this only contains the tokens - // for its definition, included unexpanded identifiers - llvm::ArrayRef currentMacroDefTokens = - currentMacro->tokens(); - if (!currentMacroDefTokens.empty()) { - clang::SourceLocation macroEnd = - currentMacroDefTokens.back().getEndLoc(); + llvm::ArrayRef tokens = + getMacroTokens(end, sourceManager, preprocessor); + if (!tokens.empty()) { + clang::SourceLocation macroEnd = tokens.back().getEndLoc(); clang::SourceLocation statementEnd = sourceManager.getSpellingLoc(end); statementEnd = clang::Lexer::getLocForEndOfToken( statementEnd, 0, sourceManager, clang::LangOptions()); - // If we are expanding a function like macro, move statementEnd to - // match the clang::tok::TokenKind::r_paren corresponding to the - // opening clang::tok::TokenKind::l_paren - - if (prevMacro && prevMacro->isFunctionLike()) { - // Find the first, if any l_paren token - auto *lparenIt = std::find_if( - currentMacroDefTokens.begin(), currentMacroDefTokens.end(), - [&sourceManager, &statementEnd](auto token) { - return sourceManager.isBeforeInTranslationUnit( - statementEnd, - clang::Lexer::getLocForEndOfToken( - token.getEndLoc(), 0, sourceManager, - clang::LangOptions())) && - token.is(clang::tok::l_paren); - }); - - auto *rparenIt = skipToBalancedRParenTok( - lparenIt, currentMacroDefTokens.end(), sourceManager); - if (rparenIt != lparenIt) { - statementEnd = clang::Lexer::getLocForEndOfToken( - rparenIt->getEndLoc(), 0, sourceManager, clang::LangOptions()); - } - } - - // Check that there are only spaces or ')' between the part - // corresponding to the end of the statement and the end of the macro. - if (!sourceRangeContainsOnly(statementEnd, macroEnd, " \t)", sourceManager)) { return true; @@ -338,10 +276,14 @@ bool SourceUtil::isPartialMacro(const clang::SourceRange &sourceRange, } } - // Move up one level closer to the expansion point + // Move up one level closer to the expansion point. This code is adapted + // from 'clang::SourceManager::getImmediateMacroCallerLoc'. - end = sourceManager.getImmediateMacroCallerLoc(end); - prevMacro = currentMacro; + if (sourceManager.isMacroArgExpansion(end)) { + end = sourceManager.getImmediateSpellingLoc(end); + } else { + end = sourceManager.getImmediateExpansionRange(end).getEnd(); + } } return false; diff --git a/t/035-range-for-statement.t.cpp b/t/035-range-for-statement.t.cpp new file mode 100644 index 0000000..85a6c40 --- /dev/null +++ b/t/035-range-for-statement.t.cpp @@ -0,0 +1,128 @@ +#include "clangmetatool-testconfig.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +namespace { + +using namespace clang::ast_matchers; + +class MyTool { +private: + clang::CompilerInstance* ci; + clangmetatool::MatchForwarder mf; + std::vector data; + + void handleDeclRefExpr(const MatchFinder::MatchResult& r) { + data.push_back(r.Nodes.getNodeAs("ref")); + } + +public: + MyTool(clang::CompilerInstance* ci, MatchFinder *f) + : ci(ci), mf(f) { + using namespace std::placeholders; + StatementMatcher matcher = + declRefExpr(hasDeclaration(namedDecl(hasName("var")))).bind("ref"); + mf.addMatcher(matcher, std::bind(&MyTool::handleDeclRefExpr, this, _1)); + } + + void postProcessing + (std::map &replacementsMap) { + const clang::SourceManager& sourceManager = ci->getSourceManager(); + + struct Location { + int line; + int col; + }; + + std::vector > expected = { + { {11, 3}, {11, 6} }, + { {12, 3}, {12, 6} }, + { {13, 8}, {13, 11} }, + { {14, 8}, {14, 11} }, + { {15, 3}, {15, 11} }, + { {16, 3}, {16, 11} }, + { {17, 14}, {17, 17} }, + { {18, 14}, {18, 17} }, + { {19, 9}, {19, 23} }, + { {20, 9}, {20, 23} } + }; + + ASSERT_EQ(data.size(), expected.size()); + for (int i = 0; i < data.size(); ++i) { + clang::CharSourceRange range = + clangmetatool::SourceUtil::getRangeForStatement(*data[i], + sourceManager); + EXPECT_EQ(expected[i].first.line, + sourceManager.getSpellingLineNumber(range.getBegin())) + << "i: " << i; + EXPECT_EQ(expected[i].first.col, + sourceManager.getSpellingColumnNumber(range.getBegin())) + << "i: " << i; + EXPECT_EQ(expected[i].second.line, + sourceManager.getSpellingLineNumber(range.getEnd())) + << "i: " << i; + EXPECT_EQ(expected[i].second.col, + sourceManager.getSpellingColumnNumber(range.getEnd())) + << "i: " << i; + } + } +}; + +} // namespace anonymous + +TEST(propagation_MacroConstantPropagation, basic) { + llvm::cl::OptionCategory MyToolCategory("my-tool options"); + int argc = 4; + const char* argv[] = { + "foo", + CMAKE_SOURCE_DIR "/t/data/035-range-for-statement/foo.cpp", + "--", + "-xc", + "-Wno-unused-value" + }; + clang::tooling::CommonOptionsParser optionsParser + (argc, argv, MyToolCategory); + clang::tooling::RefactoringTool tool + (optionsParser.getCompilations(), optionsParser.getSourcePathList()); + clangmetatool::MetaToolFactory> + raf(tool.getReplacements()); + int r = tool.runAndSave(&raf); + ASSERT_EQ(0, r); +} + +// ---------------------------------------------------------------------------- +// Copyright 2018 Bloomberg Finance L.P. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ----------------------------- END-OF-FILE ---------------------------------- diff --git a/t/CMakeLists.txt b/t/CMakeLists.txt index d2384d7..f325f15 100644 --- a/t/CMakeLists.txt +++ b/t/CMakeLists.txt @@ -51,6 +51,7 @@ foreach( 032-find-functions 033-detect-partial-macro-expansion 034-macro-name-for-statement + 035-range-for-statement ) add_executable(${TEST}.t ${TEST}.t.cpp) diff --git a/t/data/035-range-for-statement/foo.cpp b/t/data/035-range-for-statement/foo.cpp new file mode 100644 index 0000000..0010569 --- /dev/null +++ b/t/data/035-range-for-statement/foo.cpp @@ -0,0 +1,37 @@ +int var; + +#define VAR var + +#define FUNC(x) x +#define FUNC2(x) var + +#define OTHER(x) (x + 1) + +int main(){ + var; + VAR; + FUNC(var); + FUNC(VAR); + FUNC2(0); + FUNC2(1); + OTHER(FUNC(var)); + OTHER(FUNC(VAR)); + OTHER(FUNC2(FUNC(0))); + OTHER(FUNC2(FUNC(1))); +} + +// ---------------------------------------------------------------------------- +// Copyright 2020 Bloomberg Finance L.P. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ----------------------------- END-OF-FILE ----------------------------------