1 /*
2  * Copyright 2006-2008 The FLWOR Foundation.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 #include "stdafx.h"
17 
18 #include "debugger_commons.h"
19 
20 #include "system/globalenv.h"
21 
22 #include "compiler/api/compilercb.h"
23 #include "context/static_context.h"
24 #include "context/dynamic_context.h"
25 #include "diagnostics/assert.h"
26 
27 #include "runtime/debug/debug_iterator.h"
28 #include "runtime/visitors/planiter_visitor.h"
29 
30 #include "store/api/item_factory.h"
31 
32 #include "zorbatypes/URI.h"
33 
34 #include "types/typeimpl.h"
35 
36 #include "zorba/util/uri.h"
37 
38 #include "debugger_runtime.h"
39 
40 namespace zorba {
41 
42 // ****************************************************************************
43 
SERIALIZABLE_CLASS_VERSIONS(Breakable)44 SERIALIZABLE_CLASS_VERSIONS(Breakable)
45 
46 void
47 Breakable::serialize(serialization::Archiver& ar)
48 {
49   ar & theLocation;
50   ar & theEnabled;
51 }
52 
53 // ****************************************************************************
54 
55 bool
operator ()(const QueryLoc & a,const QueryLoc & b) const56 QueryLocComparator::operator()(const QueryLoc& a, const QueryLoc& b) const
57 {
58   // check the file names
59   // smaller has priority
60   int c;
61   if ((c = a.getFilename().compare(b.getFilename())) != 0) {
62     return c < 0;
63   }
64 
65   int ai, bi;
66 
67   // check line numbers
68   // smaller has priority
69   ai = a.getLineBegin();
70   bi = b.getLineBegin();
71   if (ai != bi) {
72     return ai < bi;
73   }
74 
75   // check column begin numbers
76   // smaller has priority
77   ai = a.getColumnBegin();
78   bi = b.getColumnBegin();
79   if (ai == 0 || bi == 0) {
80     return false;
81   }
82   if (ai != bi) {
83     return ai < bi;
84   }
85 
86   // check line end numbers
87   // larger has priority
88   ai = a.getLineEnd();
89   bi = b.getLineEnd();
90   if (ai != bi) {
91     return ai > bi;
92   }
93 
94   // check column end numbers
95   // larger has priority
96   ai = a.getColumnEnd();
97   bi = b.getColumnEnd();
98   if (ai == 0 || bi == 0) {
99     return false;
100   }
101 
102   return a.getColumnEnd() > b.getColumnEnd();
103 }
104 
105 // ****************************************************************************
106 
SERIALIZABLE_CLASS_VERSIONS(DebuggerSingletonIterator)107 SERIALIZABLE_CLASS_VERSIONS(DebuggerSingletonIterator)
108 
109 
110 DebuggerSingletonIterator::DebuggerSingletonIterator(
111   static_context* sctx, QueryLoc loc, DebuggerCommons* lCommons)
112   : NoaryBaseIterator<DebuggerSingletonIterator, PlanIteratorState>(sctx, loc),
113     theCommons(lCommons)
114 {
115 }
116 
117 void
serialize(::zorba::serialization::Archiver & ar)118 DebuggerSingletonIterator::serialize(::zorba::serialization::Archiver& ar)
119 {
120   serialize_baseclass(ar, (NoaryBaseIterator<DebuggerSingletonIterator,PlanIteratorState>*)this);
121   ar & theCommons;
122 }
123 
124 NOARY_ACCEPT(DebuggerSingletonIterator);
125 
126 bool
nextImpl(store::Item_t & result,PlanState & planState) const127 DebuggerSingletonIterator::nextImpl(store::Item_t& result, PlanState& planState) const
128 {
129   PlanIteratorState* state;
130   DEFAULT_STACK_INIT ( PlanIteratorState, state, planState );
131   result = theCommons->getEvalItem();
132   STACK_PUSH ( result != NULL, state );
133   STACK_END (state);
134 }
135 
136 
137 // ****************************************************************************
138 
SERIALIZABLE_CLASS_VERSIONS(DebuggerCommons)139 SERIALIZABLE_CLASS_VERSIONS(DebuggerCommons)
140 
141 
142 DebuggerCommons::DebuggerCommons(static_context* sctx)
143   : theBreak(false),
144     theCause(0),
145     theBreakCondition(0),
146     theExecEval(false),
147     theStepping(false)
148 {
149   theRuntime = NULL;
150   theCurrentStaticContext = NULL;
151   theCurrentDynamicContext = NULL;
152   thePlanState = NULL;
153 }
154 
155 
~DebuggerCommons()156 DebuggerCommons::~DebuggerCommons()
157 {
158 }
159 
160 
161 void
serialize(::zorba::serialization::Archiver & ar)162 DebuggerCommons::serialize(::zorba::serialization::Archiver& ar)
163 {
164   ar & theBreakables;
165 
166   if (ar.is_serializing_out())
167   {
168     ar.set_is_temp_field(true);
169     csize s = theBreakableIDs.size();
170     ar & s;
171     ar.set_is_temp_field(false);
172 
173     BreakableIdMap::iterator it = theBreakableIDs.begin();
174     BreakableIdMap::iterator end = theBreakableIDs.end();
175 
176     for (; it != end; ++it)
177     {
178       QueryLoc loc = (*it).first;
179       ar & loc;
180       ar & (*it).second;
181     }
182   }
183   else
184   {
185     ar.set_is_temp_field(true);
186     csize s;
187     ar & s;
188     ar.set_is_temp_field(false);
189 
190     std::pair<QueryLoc, unsigned int> p;
191 
192     for (csize i = 0; i < s; ++i)
193     {
194       ar & p.first;
195       ar & p.second;
196 
197       theBreakableIDs.insert(p);
198     }
199   }
200 
201   ar & theStackTrace;
202   ar & theUriFileMappingMap;
203 
204   if (ar.is_serializing_out())
205     theRuntime = NULL;
206 
207   ar & theCurrentStaticContext;
208 
209   if (ar.is_serializing_out())
210     theCurrentDynamicContext = NULL;
211 
212   ar & theBreak;
213   ar & theCause;
214   ar & theIteratorStack;
215   ar & theBreakCondition;
216 
217   if (ar.is_serializing_out())
218     thePlanState = NULL;
219 
220   ar & theEvalItem;
221   ar & theExecEval;
222   ar & theStepping;
223 }
224 
225 
226 void
setRuntime(DebuggerRuntime * aRuntime)227 DebuggerCommons::setRuntime(DebuggerRuntime* aRuntime)
228 {
229   theRuntime = aRuntime;
230 }
231 
232 DebuggerRuntime*
getRuntime()233 DebuggerCommons::getRuntime()
234 {
235   return theRuntime;
236 }
237 
238 void
setCurrentStaticContext(static_context * aStaticContext)239 DebuggerCommons::setCurrentStaticContext(static_context* aStaticContext)
240 {
241   theCurrentStaticContext = aStaticContext;
242 }
243 
244 void
setCurrentDynamicContext(dynamic_context * aDynamicContext)245 DebuggerCommons::setCurrentDynamicContext(dynamic_context* aDynamicContext)
246 {
247  theCurrentDynamicContext = aDynamicContext;
248 }
249 
250 dynamic_context*
getCurrentDynamicContext() const251 DebuggerCommons::getCurrentDynamicContext() const
252 {
253   return theCurrentDynamicContext;
254 }
255 
256 static_context*
getCurrentStaticContext() const257 DebuggerCommons::getCurrentStaticContext() const
258 {
259   return theCurrentStaticContext;
260 }
261 
262 unsigned int
addBreakpoint(String & aFileName,unsigned int aLine,bool aEnabled)263 DebuggerCommons::addBreakpoint(String& aFileName, unsigned int aLine, bool aEnabled)
264 {
265   QueryLoc lLocation;
266   lLocation.setLineBegin(aLine);
267   lLocation.setLineEnd(aLine);
268   lLocation.setFilename(aFileName.c_str());
269 
270   // we make sure that the this location file name is aligned with the internal ones
271   // it must have a valid URI and a scheme (file://, http://, or https://)
272   adjustLocationFilePath(lLocation);
273 
274   BreakableIdMap::iterator lIter = theBreakableIDs.find(lLocation);
275   unsigned int lId;
276 
277   if (lIter == theBreakableIDs.end()) {
278     String lFileName = aFileName;
279     // be prepared to throw an exception
280     std::stringstream lSs;
281     lSs << "The breakpoint could not be set at line " << aLine
282       << " in file \"" << lFileName << "\"";
283 
284     // let us then try some search before we fail, be good to the user and help him
285     // 1. first we check if he sent a file URI; in this case, sorry!
286     if (lFileName.find("file://") == 0) {
287       throw lSs.str();
288     }
289 
290     // now we have to normalize if we hope to find something
291     lFileName = URIHelper::encodeFileURI(lFileName).str();
292     // remove the added file schema prefix
293     // TODO: maybe there is a better way to do this encoding
294     lFileName = lFileName.substr(8);
295 
296     // 2. secondly we hope he gave us part of a path of a file
297     lIter = theBreakableIDs.begin();
298     std::vector<std::pair<QueryLoc, int> > lFoundBreakables;
299     zorba::String lFirstBreakablePath;
300     while (lIter != theBreakableIDs.end()) {
301       // for now, only valid if on the breakable is on the same line as requested
302       // TODO: this could be improved if the user wants to add a breakpoint to a line
303       //       INSIDE a breakable that spans over multiple lines
304       if (lIter->second != theMainModuleBreakableId && lIter->first.getLineBegin() == aLine) {
305         zorba::String lBreakablePath = lIter->first.getFilename().str();
306 
307         // dies the given string matche any part in the breakable file name?
308         if (lBreakablePath.find(lFileName) != String::npos) {
309           // we found the fist candidate path
310           if (lFirstBreakablePath == "") {
311             lFirstBreakablePath = lBreakablePath;
312           }
313           // but stop as soon as we are reaching a second different path (report ambiguity)
314           else if (lFirstBreakablePath != lBreakablePath){
315             lSs.str("");
316             lSs << "The file name \"" << aFileName << "\" is ambiguous. "
317               << "I already found two potential files to set a breakpoint in line " << aLine
318               << ":" << std::endl << "  " << lFirstBreakablePath << std::endl << "  " << lBreakablePath;
319             throw lSs.str();
320           }
321 
322           // Yes! We found one!
323           lFoundBreakables.push_back(std::pair<QueryLoc, int>(lIter->first, lIter->second));
324         }
325       }
326       lIter++;
327     }
328 
329     // what should I say, not a very successful search :(
330     if (lFoundBreakables.size() == 0) {
331       throw lSs.str();
332     }
333 
334     // TODO: The best solution would be for the debugger to enable all the
335     // matched breakables but the protocol can send back only one ID of the
336     // breakpoint set.
337 
338     // so we have multiple breakables, get the first in line
339     // TODO: this does not catch multiple breakables starting in the same line
340     // so only one will be picked (depending how the translator generated them)
341     unsigned int lMinCol = lFoundBreakables.at(0).first.getColumnBegin();
342     lId = lFoundBreakables.at(0).second;
343     for (std::size_t i = 1; i < lFoundBreakables.size(); i++) {
344       if (lMinCol > lFoundBreakables.at(i).first.getColumnBegin()) {
345         lId = lFoundBreakables.at(i).second;
346       }
347     }
348   }
349   else {
350     lId = lIter->second;
351   }
352 
353   // now we have a breakable, so set it accordingly
354   theBreakables[lId].setSet(true);
355   theBreakables[lId].setEnabled(aEnabled);
356   return lId;
357 }
358 
359 Breakable
getBreakpoint(unsigned int aId)360 DebuggerCommons::getBreakpoint(unsigned int aId)
361 {
362   checkBreakpoint(aId);
363   return theBreakables[aId];
364 }
365 
366 BreakableVector
getBreakpoints()367 DebuggerCommons::getBreakpoints()
368 {
369   return theBreakables;
370 }
371 
372 void
checkBreakpoint(unsigned int aId)373 DebuggerCommons::checkBreakpoint(unsigned int aId)
374 {
375   if (aId >= theBreakables.size() || !theBreakables[aId].isSet()) {
376     std::stringstream lSs;
377     lSs << "No such breakpoint: " << aId;
378     throw lSs.str();
379   }
380 }
381 
382 void
updateBreakpoint(unsigned int aId,bool aEnabled)383 DebuggerCommons::updateBreakpoint(
384   unsigned int aId,
385   bool aEnabled)
386 {
387   checkBreakpoint(aId);
388   theBreakables[aId].setEnabled(aEnabled);
389 }
390 
391 void
updateBreakpoint(unsigned int aId,bool aEnabled,std::string aCondition,unsigned int aHitValue)392 DebuggerCommons::updateBreakpoint(
393   unsigned int aId,
394   bool aEnabled,
395   std::string aCondition,
396   unsigned int aHitValue)
397 {
398   checkBreakpoint(aId);
399   theBreakables[aId].setEnabled(aEnabled);
400   // TODO: set condition
401 }
402 
403 void
removeBreakpoint(unsigned int aId)404 DebuggerCommons::removeBreakpoint(unsigned int aId)
405 {
406   checkBreakpoint(aId);
407   theBreakables[aId].setSet(false);
408 }
409 
410 bool
canBreak()411 DebuggerCommons::canBreak()
412 {
413   return !theExecEval;
414 }
415 
416 bool
mustBreak(SuspensionCause & aCause)417 DebuggerCommons::mustBreak(SuspensionCause& aCause)
418 {
419   if (theRuntime->getAndClearInterruptBreak()) {
420     aCause = CAUSE_USER;
421     return true;
422   }
423   if (theBreak) {
424     aCause = theCause;
425     return true;
426   } else if (theStepping) {
427     std::size_t lSize = theIteratorStack.size();
428 
429     // either we meet the step condition
430     if (lSize <= theBreakCondition) {
431       // reset the break conditions
432       theBreakCondition = 0;
433       theStepping = false;
434       return true;
435     }
436     // or we have stepped over from a function declaration breakpoint
437     // we have for sure at least the following stack of debug iterators:
438     // MainModule DI -> FunctionCall DI -> FunctionDecl DI -> current DI
439     if (lSize > 3 && lSize == theBreakCondition + 1 && theIteratorStack.at(lSize - 2)->getDebuggerParent() == NULL) {
440       // reset the break conditions
441       theBreakCondition = 0;
442       theStepping = false;
443       return true;
444     }
445     // or we have stepped over and we reached another variable declaration
446     // TODO: still to check and stop only at declarations on the same level
447     if (lSize <= theBreakCondition && theIteratorStack.back()->isVarDeclaration()) {
448       // reset the break conditions
449       theBreakCondition = 0;
450       theStepping = false;
451       return true;
452     }
453   }
454 
455   return false;
456 }
457 
458 bool
hasToBreakAt(QueryLoc aLocation)459 DebuggerCommons::hasToBreakAt(QueryLoc aLocation)
460 {
461   // we make sure that this location file name is a valid URI and has a scheme (file://, http://, or https://)
462   adjustLocationFilePath(aLocation);
463 
464   BreakableIdMap::const_iterator lIter = theBreakableIDs.find(aLocation);
465   if (lIter == theBreakableIDs.end()) {
466     return false;
467   }
468 
469   Breakable lBreakable = theBreakables[lIter->second];
470   return lBreakable.isSet() && lBreakable.isEnabled();
471 }
472 
473 bool
hasToBreakAt(const DebugIterator * aIter)474 DebuggerCommons::hasToBreakAt(const DebugIterator* aIter)
475 {
476   return false;
477 }
478 
479 void
setBreak(bool lBreak,SuspensionCause aCause)480 DebuggerCommons::setBreak(bool lBreak, SuspensionCause aCause)
481 {
482 #ifndef NDEBUG
483   //Check preconditions
484   ZORBA_ASSERT(lBreak ? aCause != 0 : true);
485 #endif // NDEBUG
486   theBreak = lBreak;
487   theCause = aCause;
488 #ifndef NDEBUG
489   // Check post conditions
490   ZORBA_ASSERT(theBreak == lBreak);
491   ZORBA_ASSERT(theCause == aCause);
492 #endif //NDEBUG
493 }
494 
495 void
setCurrentIterator(const DebugIterator * aIterator)496 DebuggerCommons::setCurrentIterator(const DebugIterator* aIterator)
497 {
498   // don't modify the iterator stack during expression evaluation
499   if (theExecEval) {
500     return;
501   }
502 
503   const DebugIterator* lParent = aIterator->getDebuggerParent();
504 
505   // when the stack is empty (main module debug iterator) or
506   // when the parent is NULL but not a variable declaration (function declaration iterator) or
507   // when the parent is the last in the stack
508   if (theIteratorStack.empty() ||
509       (lParent == NULL && !aIterator->isVarDeclaration()) ||
510       theIteratorStack.back() == lParent) {
511     theIteratorStack.push_back((DebugIterator*)aIterator);
512     return;
513   }
514 
515   // avoid multiple calls to this function
516   // recursive functions always insert at least 2 iterators
517   if (theIteratorStack.back() == aIterator) {
518     return;
519   }
520 
521   // now we remove the iterators from the stack until we find the parent
522   theIteratorStack.pop_back();
523   while (!theIteratorStack.empty()) {
524     if (theIteratorStack.back() == aIterator->getDebuggerParent()) {
525       theIteratorStack.push_back((DebugIterator*)aIterator);
526       return;
527     }
528     theIteratorStack.pop_back();
529   }
530 
531   // noe the stack is empty, so push this iterator in the stack
532   theIteratorStack.push_back((DebugIterator*)aIterator);
533 }
534 
535 const DebugIterator*
getCurrentIterator() const536 DebuggerCommons::getCurrentIterator() const
537 {
538   if (theIteratorStack.empty()) {
539     return NULL;
540   }
541   return theIteratorStack.back();
542 }
543 
544 void
makeStepOver()545 DebuggerCommons::makeStepOver()
546 {
547   theStepping = true;
548   if (!theIteratorStack.empty()) {
549     theBreakCondition = theIteratorStack.size();
550   } else {
551     theBreakCondition = 0;
552   }
553 }
554 
makeStepOut()555 void DebuggerCommons::makeStepOut()
556 {
557   theStepping = true;
558   if (!theIteratorStack.empty()) {
559     theBreakCondition = theIteratorStack.size() - 1;
560   } else {
561     theBreakCondition = 0;
562   }
563 }
564 
565 void
setPlanState(PlanState * aPlanState)566 DebuggerCommons::setPlanState(PlanState* aPlanState)
567 {
568   thePlanState = aPlanState;
569   //Check postconditions
570   ZORBA_ASSERT(thePlanState == aPlanState);
571 }
572 
573 std::list<std::pair<zstring, zstring> >
eval(const zstring & aExpr,Zorba_SerializerOptions & aSerOpts)574 DebuggerCommons::eval(const zstring& aExpr, Zorba_SerializerOptions& aSerOpts)
575 {
576   theExecEval = true;
577   zstring lStore = aExpr;
578   GlobalEnvironment::getInstance().getItemFactory()->createString(theEvalItem,
579                                                                   lStore);
580   std::list<std::pair<zstring, zstring> > lRes;
581   try {
582     lRes = theIteratorStack.back()->eval(thePlanState, &aSerOpts);
583     theExecEval = false;
584   } catch (...) {
585     theExecEval = false;
586     throw;
587   }
588 
589   return lRes;
590 }
591 
592 store::Item_t
getEvalItem()593 DebuggerCommons::getEvalItem()
594 {
595   return theEvalItem;
596 }
597 
598 void
addModuleUriMapping(std::string aUri,std::string aFileUri)599 DebuggerCommons::addModuleUriMapping(std::string aUri, std::string aFileUri)
600 {
601   ZORBA_ASSERT(theUriFileMappingMap.find(aUri) == theUriFileMappingMap.end());
602   theUriFileMappingMap.insert(
603     std::pair<std::string, std::string>(aUri, aFileUri));
604   ZORBA_ASSERT(theUriFileMappingMap.find(aUri) != theUriFileMappingMap.end());
605 }
606 
607 std::string
getFilepathOfURI(const std::string & aUri) const608 DebuggerCommons::getFilepathOfURI(const std::string& aUri) const
609 {
610   std::map<std::string, std::string>::const_iterator lIter;
611   lIter = theUriFileMappingMap.find(aUri);
612   zstring lString;
613   if (lIter == theUriFileMappingMap.end())
614   {
615     lString = aUri;
616   }
617   else
618   {
619     lString = lIter->second;
620   }
621   zstring lRes;
622   URI::decode_file_URI(lString, lRes);
623   return lRes.str();
624 }
625 
626 void
addBreakable(Breakable & aBreakable,bool aIsMainModuleBreakable)627 DebuggerCommons::addBreakable(
628   Breakable& aBreakable,
629   bool aIsMainModuleBreakable)
630 {
631   // we make sure that this breakable file name is a valid URI and has a scheme (file://, http://, or https://)
632   adjustLocationFilePath(aBreakable.getLocation());
633 
634   unsigned int lId = theBreakables.size();
635   if (aIsMainModuleBreakable) {
636     theMainModuleBreakableId = lId;
637   }
638   theBreakables.push_back(aBreakable);
639   theBreakableIDs[aBreakable.getLocation()] = lId;
640 }
641 
642 void
pushStackFrame(QueryLoc aLocation,std::string & aFunctionName)643 DebuggerCommons::pushStackFrame(QueryLoc aLocation, std::string& aFunctionName)
644 {
645   // we make sure that the stack frame locations always have valid URIs and a scheme (file://, http://, or https://)
646   adjustLocationFilePath(aLocation);
647 
648   theStackTrace.push_back(std::pair<QueryLoc, std::string>(aLocation, aFunctionName));
649 }
650 
651 void
popStackFrame()652 DebuggerCommons::popStackFrame()
653 {
654   theStackTrace.pop_back();
655 }
656 
657 std::vector<std::pair<QueryLoc, std::string> >
getStackFrames() const658 DebuggerCommons::getStackFrames() const
659 {
660   return theStackTrace;
661 }
662 
663 void
adjustLocationFilePath(QueryLoc & aLocation)664 DebuggerCommons::adjustLocationFilePath(QueryLoc& aLocation)
665 {
666   zstring lOldFilename(aLocation.getFilename());
667   zstring lPrefix = lOldFilename.substr(0, 7);
668 
669   if (lPrefix == "file://") {
670 #ifdef WIN32
671     // decode and encode back to solve the driver column encoding: C:, D:, etc.
672     const String lNewFilename = URIHelper::decodeFileURI(lOldFilename.str());
673     const String lNewURI = URIHelper::encodeFileURI(lNewFilename);
674     aLocation.setFilename(lNewURI.str());
675 #endif
676     return;
677   }
678 
679   // just encode and assume file for non-URI locations
680   if (lPrefix != "http://" && lPrefix != "https:/") {
681     const String lNewURI = URIHelper::encodeFileURI(lOldFilename.str());
682     aLocation.setFilename(lNewURI.str());
683     return;
684   }
685 }
686 
687 } // namespace zorba
688