]> rtime.felk.cvut.cz Git - boost-statechart-viewer.git/blob - src/visualizer.cpp
Replace magic constants with something better - not finished
[boost-statechart-viewer.git] / src / visualizer.cpp
1 /** @file */
2 ////////////////////////////////////////////////////////////////////////////////////////
3 //
4 //    This file is part of Boost Statechart Viewer.
5 //
6 //    Boost Statechart Viewer is free software: you can redistribute it and/or modify
7 //    it under the terms of the GNU General Public License as published by
8 //    the Free Software Foundation, either version 3 of the License, or
9 //    (at your option) any later version.
10 //
11 //    Boost Statechart Viewer is distributed in the hope that it will be useful,
12 //    but WITHOUT ANY WARRANTY; without even the implied warranty of
13 //    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 //    GNU General Public License for more details.
15 //
16 //    You should have received a copy of the GNU General Public License
17 //    along with Boost Statechart Viewer.  If not, see <http://www.gnu.org/licenses/>.
18 //
19 ////////////////////////////////////////////////////////////////////////////////////////
20
21 //standard header files
22 #include <iostream>
23
24 //LLVM Header files
25 #include "llvm/Support/raw_ostream.h"
26 #include "llvm/Support/Host.h"
27 #include "llvm/Config/config.h"
28
29 //clang header files
30 #include "clang/Frontend/TextDiagnosticPrinter.h"
31 #include "clang/Lex/HeaderSearch.h"
32 #include "clang/Basic/FileManager.h"
33 #include "clang/Frontend/Utils.h"
34 #include "clang/Basic/TargetInfo.h"
35 #include "clang/Lex/Preprocessor.h"
36 #include "clang/Frontend/CompilerInstance.h"
37 #include "clang/AST/ASTConsumer.h"
38 #include "clang/Sema/Lookup.h"
39 #include "clang/Parse/ParseAST.h"
40 #include "clang/Basic/Version.h"
41 #include "clang/Driver/Driver.h"
42 #include "clang/Driver/Compilation.h"
43
44 #include "clang/Frontend/FrontendPluginRegistry.h"
45 #include "clang/AST/ASTConsumer.h"
46 #include "clang/AST/AST.h"
47 #include "clang/Frontend/CompilerInstance.h"
48 #include "llvm/Support/raw_ostream.h"
49
50 //my own header files
51 #include "iooper.h"
52
53 using namespace clang;
54 using namespace clang::driver;
55 using namespace std;
56
57 /**
58  * This class provides Simple diagnostic Client. It uses implementation in library for printing diagnostci information.
59  * Also it counts number of warnings, errors, ... When an error occurs the program is stopped.
60  */
61 class MyDiagnosticClient : public TextDiagnosticPrinter
62 {
63         int nwarnings; /** Save number of Warnings occured during diagnostic */
64         int nnotes;
65         int nignored;
66         int nerrors;
67         public:
68    /**
69     * Initialize number of warnings, errors, ...
70     */
71    MyDiagnosticClient(llvm::raw_ostream &os, const DiagnosticOptions &diags, bool OwnsOutputStream = false):TextDiagnosticPrinter(os, diags, OwnsOutputStream = false)
72         {
73                 nwarnings=0;
74                 nnotes=0;
75                 nignored=0;
76                 nerrors = 0;
77         }
78    /**
79      * This method prints diagnostic and counts diagnostic types.
80      */
81         virtual void HandleDiagnostic(DiagnosticsEngine::Level DiagLevel, const Diagnostic &Info)
82         {
83                 TextDiagnosticPrinter::HandleDiagnostic(DiagLevel, Info); // print diagnostic information using library implementation
84                 switch (DiagLevel) // count number of all diagnostic information
85                 {
86                         case 0 : nignored+=1; break;
87                         case 1 : nnotes+=1; break;
88                         case 2 : nwarnings+=1; break;
89                         default : nerrors+=1;
90                                                  print_stats();
91                                                  exit(1);
92                 }
93         }
94    /**
95      * Print statistics about diagnostic.
96      */
97         void print_stats()
98         {
99                 cout<<"\n--Diagnostic Info--\n";
100                 cout<<"Number of ignored: "<<nignored<<"\n";
101                 cout<<"Number of notes: "<<nnotes<<"\n";
102                 cout<<"Number of warnings: "<<nwarnings<<"\n";
103                 cout<<"Number of errors and fatal errors: "<<nerrors<<"\n";
104         }
105
106         int getNbrOfWarnings() /** Return number of warnings */
107         {
108                 return nwarnings;
109         }
110
111         int getNbrOfNotes() /** Return number of notes */
112         {
113                 return nnotes;
114         }
115
116         int getNbrOfIgnored() /** Return number of ignored */
117         {
118                 return nignored;
119         }
120 };
121
122 /**
123  * My ASTConsumer provides interface for traversing AST. It uses recursive traversing in namespaces.
124  */
125 class FindStates : public ASTConsumer
126 {
127         list<string> transitions;
128         list<string> cReactions; /** list of custom reactions. After all files are traversed this list should be empty. */
129         list<string> events;
130         list<string> states;
131         string name_of_machine;
132         string name_of_start;
133         string dest;
134         FullSourceLoc *fsloc; /** Full Source Location instance for holding Source Manager. */
135         public:
136
137         FindStates(string dest) : dest(dest) {}
138
139         list<string> getStates() /** Return list of states of the state machine. */
140         {
141                 return states;
142         }
143
144         list<string> getTransitions() /** Return list of transitions. */
145         {
146                 return transitions;
147         }
148
149         list<string> getEvents() /** Return list of events. */
150         {
151                 return events;
152         }
153
154         string getStateMachine() /** Return name of the state machine. */
155         {
156                 return name_of_machine;
157         }
158
159         string getNameOfFirstState() /** Return name of start state. */
160         {
161                 return name_of_start;
162         }
163
164         virtual void Initialize(ASTContext &ctx)/** Run after the AST is constructed before the consumer starts to work. So this function works like constructor. */
165         {
166                 fsloc = new FullSourceLoc(* new SourceLocation(), ctx.getSourceManager());
167                 name_of_start = "";
168                 name_of_machine = "";
169         }
170
171 /**
172 *   Traverse global decls using DeclGroupRef for handling all global decls. But only interesting decls are processed. Interesting decls are Struct, Class, C++ methods and Namespace.
173 *   When Namespace is found it recursively traverse all decls inside this Namespace using method recursive_visit.
174 */
175         virtual bool HandleTopLevelDecl(DeclGroupRef DGR)
176         {
177                 SourceLocation loc;
178                 string line, output, event;
179                 llvm::raw_string_ostream x(output);
180                 for (DeclGroupRef::iterator i = DGR.begin(), e = DGR.end(); i != e; ++i)
181                 {
182                         const Decl *decl = *i;
183                         loc = decl->getLocation();
184                         if(loc.isValid())
185                         {
186                                 if(dyn_cast<FunctionDecl>(decl))
187                                 {
188                                         method_decl(decl);
189                                 }
190                                 if (const TagDecl *tagDecl = dyn_cast<TagDecl>(decl))
191                                 {
192                                         if(tagDecl->isStruct() || tagDecl->isClass()) //is it a struct or class
193                                         {
194                                                 struct_class(decl);
195                                         }
196                                 }
197                                 if(const NamespaceDecl *namespaceDecl = dyn_cast<NamespaceDecl>(decl))
198                                 {
199
200                                         DeclContext *declCont = namespaceDecl->castToDeclContext(namespaceDecl);
201                                         recursive_visit(declCont);
202
203                                 }
204                         }
205                         output = "";
206                 }
207                 return true;
208         }
209
210 /**
211 *   It is used to recursive traverse decls in Namespaces. This method do the same as HandleTopLevelDecl.
212 */
213         void recursive_visit(const DeclContext *declCont)
214         {
215                 string line, output, event;
216                 llvm::raw_string_ostream x(output);
217                 SourceLocation loc;
218                 for (DeclContext::decl_iterator i = declCont->decls_begin(), e = declCont->decls_end(); i != e; ++i)
219                 {
220                         const Decl *decl = *i;
221                         loc = decl->getLocation();
222                         if(loc.isValid())
223                         {
224                                 if(dyn_cast<FunctionDecl>(decl))
225                                 {
226                                         method_decl(decl);
227                                 }
228                                 else if (const TagDecl *tagDecl = dyn_cast<TagDecl>(decl))
229                                 {
230                                         if(tagDecl->isStruct() || tagDecl->isClass()) //is it a structure or class
231                                         {
232                                                 struct_class(decl);
233                                         }
234                                 }
235                                 else if(const NamespaceDecl *namespaceDecl = dyn_cast<NamespaceDecl>(decl))
236                                 {
237                                         DeclContext *declCont = namespaceDecl->castToDeclContext(namespaceDecl);
238                                         recursive_visit(declCont);
239                                 }
240                         }
241                         output = "";
242                 }
243         }
244
245 /**
246 *       This function works with class or struct. It splits the decl into 3 interesting parts.
247 *       The state machine decl, state decl and event decl.
248 */
249         void struct_class(const Decl *decl)
250         {
251                 string output, line, ret, trans, event;
252                 llvm::raw_string_ostream x(output);
253                 decl->print(x);
254                 line = get_line_of_code(x.str());
255
256                 output = "";
257                 int pos;
258                 const NamedDecl *namedDecl = dyn_cast<NamedDecl>(decl);
259                 if(is_derived(line))
260                 {
261                         const CXXRecordDecl *cRecDecl = dyn_cast<CXXRecordDecl>(decl);
262
263                         if(find_events(cRecDecl, line))
264                         {
265                                 events.push_back(namedDecl->getNameAsString());
266                         }
267                         else if(name_of_machine == "")
268                         {
269                                 ret = find_name_of_machine(cRecDecl, line);
270                                 if(!ret.empty())
271                                 {
272                                         pos = ret.find(",");
273                                         name_of_machine = ret.substr(0,pos);
274                                         name_of_start = ret.substr(pos+1);
275                                 }
276                         }
277                         else
278                         {
279                                 ret = find_states(cRecDecl, line);
280                                 if(!ret.empty())
281                                 {
282                                         states.push_back(ret);
283                                         methods_in_class(decl,namedDecl->getNameAsString());
284                                 }
285                         }
286                 }
287         }
288
289 /**
290 *       This function provides traversing all methods and other context indide class. If
291 *       typedef or classic method decl is found. If typedef is found then it is being testted for transitions and custom reactions.
292 */
293         void methods_in_class(const Decl *decl, const string state)
294         {
295                 string output, line, ret, trans, event;
296                 llvm::raw_string_ostream x(output);
297                 int pos, num;
298                 const TagDecl *tagDecl = dyn_cast<TagDecl>(decl);
299                 const DeclContext *declCont = tagDecl->castToDeclContext(tagDecl);
300                 output="";
301                 std::cout<<"Found state: "<<state<<std::endl;
302                 for (DeclContext::decl_iterator i = declCont->decls_begin(), e = declCont->decls_end(); i != e; ++i)
303                 {
304                         if (i->getKind()==26) // typedefs
305                         {
306                                 i->print(x);
307                                 output = x.str();
308                                 line = clean_spaces(cut_type(output));
309                                 ret = find_transitions(state,line);
310                                 if(!ret.empty())
311                                 {
312                                         num = count(ret,';')+1;
313                                         for(int i = 0;i<num;i++)
314                                         {
315                                                 pos = ret.find(";");
316                                                 if(pos == 0)
317                                                 {
318                                                         ret = ret.substr(1);
319                                                         pos = ret.find(";");
320                                                         if(pos==-1) cReactions.push_back(ret);
321                                                         else cReactions.push_back(ret.substr(0,pos));
322                                                         num-=1;
323                                                 }
324                                                 else
325                                                 {
326                                                         if(pos==-1) transitions.push_back(ret);
327                                                         else transitions.push_back(ret.substr(0,pos));
328                                                 }
329                                                 if(i!=num-1) ret = ret.substr(pos+1);
330                                         }
331                                         output="";
332                                 }
333                         }
334                         if (dyn_cast<FunctionDecl>(*i)) method_decl(*i);// C++ method
335                 }
336
337         }
338
339  /**
340    * Traverse method declaration using classes with main class Stmt.
341    */
342         void method_decl(const Decl *decl)
343         {
344                 const FunctionDecl *fDecl = dyn_cast<FunctionDecl>(decl);
345                 string output, line, event;
346                 llvm::raw_string_ostream x(output);
347
348                 if(fDecl->hasBody() && fDecl->getNumParams() > 0)
349                 {
350                         decl->print(x);
351                         line = get_return(x.str());
352                         if(get_model(line)==5)
353                         {
354                                 //std::cout<<"metodass"<<std::endl;
355                                 const ParmVarDecl *pvd = fDecl->getParamDecl(0);
356                                 QualType qt = pvd->getOriginalType();
357                                 event = qt.getAsString();
358                                 if(event[event.length()-1]=='&') event = event.substr(0,event.length()-2);
359                                 event = event.substr(event.rfind(" ")+1);
360                                 line = dyn_cast<NamedDecl>(decl)->getQualifiedNameAsString();
361                                 line = cut_namespaces(line.substr(0,line.rfind("::")));
362                                 line.append(",");
363                                 line.append(event);
364                                 find_return_stmt(decl->getBody(),line);
365                                 for(list<string>::iterator i = cReactions.begin();i!=cReactions.end();i++) // erase info about it from list of custom reactions
366                                 {
367                                         event = *i;
368                                         if(line.compare(event)==0)
369                                         {
370                                                 cReactions.erase(i);
371                                                 break;
372                                         }
373                                 }
374                         }
375                 }
376         }
377
378         void find_return_stmt(Stmt *statemt,string event) /** Traverse all statements in function for finding all return Statements.*/
379         {
380                 if(statemt->getStmtClass() == 99) test_stmt(dyn_cast<CaseStmt>(statemt)->getSubStmt(), event);
381                 else
382                 {
383                         for (Stmt::child_range range = statemt->children(); range; ++range)
384                         {
385                                 test_stmt(*range, event);
386                         }
387                 }
388         }
389
390         void test_stmt(Stmt *stmt, string event) /** test statement for its kind Using number as identifier for all Statement Classes.*/
391         {
392                 const SourceManager &sman = fsloc->getManager();
393                 int type;
394                 string line, param;
395                 type = stmt->getStmtClass();
396                 switch(type)
397                 {
398                         case 8 :        find_return_stmt(dyn_cast<DoStmt>(stmt)->getBody(), event); // do
399                                                 break;
400                         case 86 :       find_return_stmt(dyn_cast<ForStmt>(stmt)->getBody(), event); // for
401                                                 break;
402                         case 88 :       find_return_stmt(dyn_cast<IfStmt>(stmt)->getThen(), event); //if then
403                                                 find_return_stmt(dyn_cast<IfStmt>(stmt)->getElse(), event); //if else
404                                                 break;
405                         case 90 :       find_return_stmt(dyn_cast<LabelStmt>(stmt)->getSubStmt(), event); //label
406                                                 break;
407                         case 98 :       line = sman.getCharacterData(dyn_cast<ReturnStmt>(stmt)->getReturnLoc());
408                                                 line = get_line_of_code(line).substr(6);
409                                                 line = line.substr(0,line.find("("));
410                                                 if(get_model(line)==6)
411                                                 {
412                                                         param = get_params(line);
413                                                         transitions.push_back(event.append(",").append(param));
414                                                 }
415                                                 if(get_model(line) == 7)
416                                                 {
417                                                         param = ",";
418                                                         transitions.push_back(param.append(event));
419                                                 }
420                                                 break;
421                         case 99 :       find_return_stmt(stmt, event);
422                                                 break;
423                         case 101 :      find_return_stmt(dyn_cast<SwitchStmt>(stmt)->getBody(), event); // switch
424                                                 break;
425                         case 102 :      find_return_stmt(dyn_cast<WhileStmt>(stmt)->getBody(), event); // while
426                                                 break;
427                 }
428         }
429
430   virtual void HandleTranslationUnit(clang::ASTContext &Context) {
431     IO_operations io(dest, getStateMachine(), getNameOfFirstState(), getTransitions(), getStates(), getEvents());
432     io.save_to_file();
433     io.print_stats();
434   }
435 };
436
437 class VisualizeStatechartAction : public PluginASTAction {
438 protected:
439   ASTConsumer *CreateASTConsumer(CompilerInstance &CI, llvm::StringRef) {
440     size_t dot = getCurrentFile().find_last_of('.');
441     std::string dest = getCurrentFile().substr(0, dot);
442     dest.append(".dot");
443     return new FindStates(dest);
444   }
445
446   bool ParseArgs(const CompilerInstance &CI,
447                  const std::vector<std::string>& args) {
448     for (unsigned i = 0, e = args.size(); i != e; ++i) {
449       llvm::errs() << "Visualizer arg = " << args[i] << "\n";
450
451       // Example error handling.
452       if (args[i] == "-an-error") {
453         DiagnosticsEngine &D = CI.getDiagnostics();
454         unsigned DiagID = D.getCustomDiagID(
455           DiagnosticsEngine::Error, "invalid argument '" + args[i] + "'");
456         D.Report(DiagID);
457         return false;
458       }
459     }
460     if (args.size() && args[0] == "help")
461       PrintHelp(llvm::errs());
462
463     return true;
464   }
465   void PrintHelp(llvm::raw_ostream& ros) {
466     ros << "Help for Visualize Statechart plugin goes here\n";
467   }
468
469 };
470
471 static FrontendPluginRegistry::Add<VisualizeStatechartAction> X("visualize-statechart", "visualize statechart");