Clang Tool: rewrite ObjCMessageExpr
c++, clang, llvm-c++-api, objective-c, selector
Solution
You can rewrite selector name by replacing only continuous parts of selector name. For example, replace only underlined parts
[object someMessage:[object2 someMessage:obj3 calculate:obj4]];
^~~~~~~~~~~ ^~~~~~~~~~~ ^~~~~~~~~
To achieve this you require only
- number of selector parts - `ObjCMessageExpr::getNumSelectorLocs()`
- their locations - `ObjCMessageExpr::getSelectorLoc(index)`
- their lengths - `ObjCMessageExpr::getSelector().getNameForSlot(index).size()`.
Overall, you can rewrite ObjCMessageExpr with the following RecursiveASTVisitor:
#include "clang/AST/ASTConsumer.h"
#include "clang/AST/ASTContext.h"
#include "clang/AST/RecursiveASTVisitor.h"
#include "clang/Rewrite/Core/Rewriter.h"
namespace clang_tooling
{
using clang::SourceLocation;
class RewritingVisitor : public clang::ASTConsumer,
public clang::RecursiveASTVisitor<RewritingVisitor>
{
public:
// You can obtain SourceManager and LangOptions from CompilerInstance when
// you are creating visitor (which is also ASTConsumer) in
// clang::ASTFrontendAction::CreateASTConsumer.
RewritingVisitor(clang::SourceManager &sourceManager,
const clang::LangOptions &langOptions)
: _sourceManager(sourceManager), _rewriter(sourceManager, langOptions)
{}
virtual void HandleTranslationUnit(clang::ASTContext &context)
{
TraverseDecl(context.getTranslationUnitDecl());
_rewriter.overwriteChangedFiles();
}
bool VisitObjCMessageExpr(clang::ObjCMessageExpr *messageExpr)
{
if (_sourceManager.isInMainFile(messageExpr->getLocStart()))
{
clang::Selector selector = messageExpr->getSelector();
for (unsigned i = 0, end = messageExpr->getNumSelectorLocs();
i < end; ++i)
{
SourceLocation selectorLoc = messageExpr->getSelectorLoc(i);
_rewriter.ReplaceText(selectorLoc,
selector.getNameForSlot(i).size(),
"newSelector");
}
}
return Base::VisitObjCMessageExpr(messageExpr);
}
private:
typedef clang::RecursiveASTVisitor<RewritingVisitor> Base;
clang::SourceManager &_sourceManager;
clang::Rewriter _rewriter;
};
} // end namespace clang_tooling
Problem
I want to rewrite all messages in my code, I need replace only selectors, but I need be able to replace nested expressions f. e. : ``` [super foo:[someInstance someMessage:@""] foo2:[someInstance someMessage2]]; ``` I tried do it with `clang::Rewriter replaceText` and just generate new string, but there is a problem: It would not be work if I change selectors length, because I replace nested messages with those old positions. So, I assumed that I need to use `clang::Rewriter ReplaceStmt(originalStatement, newStatement);` I am using `RecursiveASTVisitor` to visit all messages, and I want to copy those messages objects, and replace selectors: How can I do that? I tried use `ObjCMessageExpr::Create` but there is so meny args, I don't know how to get `ASTContext &Context` `and ArrayRef<SourceLocation> SeLocs` and `Expr *Receiver` parameters from the original message. What is the proper way to replace selectors in nested messages using clang tool (clang tooling interface)? Update: Should I use `ReplaceStmtWithStmt` callback and `ASTMatchFinder` ? Update: I am using following function to rewrite text in file: ``` void ReplaceText(SourceLocation start, unsigned originalLength, StringRef string) { m_rewriter.ReplaceText(start, originalLength, string); m_rewriter.overwriteChangedFiles(); } ``` And I want to replace all messageExpr in code with new selector f.e: how it was: ``` [object someMessage:[object2 someMessage:obj3 calculate:obj4]]; ``` how it should be: ``` [object newSelector:[object2 newSelector:obj3 newSelector:obj4]]; ``` I am using ReqoursiveASTVisitor: ``` bool VisitStmt(Stmt *statement) { if (ObjCMessageExpr *messageExpr = dyn_cast<ObjCMessageExpr>(statement)) { ReplaceMessage(*messageExpr) } return true; } ``` I created method for generating new message expr string: ``` string StringFromObjCMessageExpr(ObjCMessageExpr& messageExpression) { std::ostringstream stringStream; const string selectorString = messageExpression.getSelector().getAsString(); cout << selectorString << endl; vector<string> methodParts; split(selectorString, ParametersDelimiter, methodParts); stringStream << "[" ; const string receiver = GetStringFromLocations(m_compiler, messageExpression.getReceiverRange().getBegin(), messageExpression.getSelectorStartLoc()); stringStream << receiver; clang::ObjCMessageExpr::arg_iterator argIterator = messageExpression.arg_begin(); for (vector<string>::const_iterator partsIterator = methodParts.begin(); partsIterator != methodParts.end(); ++partsIterator) { stringStream << "newSelector"; if (messageExpression.getNumArgs() != 0) { const clang::Stmt *argument = *argIterator; stringStream << ":" << GetStatementString(*argument) << " "; ++argIterator; } } stringStream << "]"; return stringStream.str(); } void ReplaceMessage(ObjCMessageExpr& messageExpression) { SourceLocation locStart = messageExpression.getLocStart(); SourceLocation locEnd = messageExpression.getLocEnd(); string newExpr = StringFromObjCMessageExpr(messageExpression); const int exprStringLegth = m_rewriter.getRangeSize(SourceRange(locStart, locEnd)); ReplaceText(locStart, exprStringLegth, newExpr); } ``` The problem occurs when I try to replace nested messages, like that: ``` [simpleClass doSomeActionWithString:string3 andAnotherString:string4]; [simpleClass doSomeActionWithString:str andAnotherString:str2]; [simpleClass doSomeActionWithString:@"" andAnotherString:@"asdasdsad"]; [simpleClass setSimpleClassZAZAZAZAZAZAZAZA:[simpleClass getSimpleClassZAZAZAZAZAZAZAZA]]; ``` the result is: ``` [simpleClass newSelector:string3 newSelector:string4 ]; [simpleClass newSelector:str newSelector:str2 ]; [simpleClass newSelector:@"" newSelector:@"asdasdsad" ]; [simpleClass newSelector:[simpleClass getSimp[simpleClass newSelector]]; ``` because messageExpression has "old" value of `getLocStart();` and `getLocEnd();` How can I fix it?