Skip to content

Commit

Permalink
Merge pull request #59 from dbeer1/fix_macro_args
Browse files Browse the repository at this point in the history
Fix source range for macro arguments
  • Loading branch information
dbeer1 authored Dec 29, 2020
2 parents 6a8ee1f + 52af390 commit 7420df6
Show file tree
Hide file tree
Showing 4 changed files with 203 additions and 95 deletions.
132 changes: 37 additions & 95 deletions src/source_util.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -74,68 +74,34 @@ 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<clang::Token>::const_iterator
skipToBalancedRParenTok(llvm::ArrayRef<clang::Token>::const_iterator begin,
llvm::ArrayRef<clang::Token>::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
// location. The end location of a statement points to the beginning of the
// 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());
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -284,64 +256,34 @@ 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<clang::Token> currentMacroDefTokens =
currentMacro->tokens();
if (!currentMacroDefTokens.empty()) {
clang::SourceLocation macroEnd =
currentMacroDefTokens.back().getEndLoc();
llvm::ArrayRef<clang::Token> 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;
}
}
}

// 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;
Expand Down
128 changes: 128 additions & 0 deletions t/035-range-for-statement.t.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
#include "clangmetatool-testconfig.h"

#include <functional>
#include <sstream>
#include <string>
#include <vector>
#include <utility>

#include <clang/ASTMatchers/ASTMatchers.h>
#include <clang/ASTMatchers/ASTMatchFinder.h>
#include <clang/Frontend/FrontendAction.h>
#include <clang/Tooling/Core/Replacement.h>
#include <clang/Tooling/CommonOptionsParser.h>
#include <clang/Tooling/Tooling.h>
#include <clang/Tooling/Refactoring.h>
#include <llvm/Support/CommandLine.h>
#include <clang/Basic/SourceManager.h>

#include <clangmetatool/match_forwarder.h>
#include <clangmetatool/meta_tool_factory.h>
#include <clangmetatool/meta_tool.h>
#include <clangmetatool/source_util.h>

#include <gtest/gtest.h>

namespace {

using namespace clang::ast_matchers;

class MyTool {
private:
clang::CompilerInstance* ci;
clangmetatool::MatchForwarder mf;
std::vector<const clang::DeclRefExpr*> data;

void handleDeclRefExpr(const MatchFinder::MatchResult& r) {
data.push_back(r.Nodes.getNodeAs<clang::DeclRefExpr>("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<std::string, clang::tooling::Replacements> &replacementsMap) {
const clang::SourceManager& sourceManager = ci->getSourceManager();

struct Location {
int line;
int col;
};

std::vector<std::pair<Location, Location> > 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<clangmetatool::MetaTool<MyTool>>
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 ----------------------------------
1 change: 1 addition & 0 deletions t/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
37 changes: 37 additions & 0 deletions t/data/035-range-for-statement/foo.cpp
Original file line number Diff line number Diff line change
@@ -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 ----------------------------------

0 comments on commit 7420df6

Please sign in to comment.