 |
|
 |
|
| Files: |
1 |
|
Branches Taken: |
51.2% |
88 / 172 |
| Generated: |
2009-05-17 22:47 |
|
Branches Executed: |
61.6% |
106 / 172 |
| |
|
Line Coverage: |
53.3% |
122 / 229 |
| |
 |
|
 |
1 : /* -*- mode: c++; c-basic-offset: 2; -*- */
2 :
3 : #include "klee/ExecutionState.h"
4 :
5 : #include "klee/Internal/Module/Cell.h"
6 : #include "klee/Internal/Module/InstructionInfoTable.h"
7 : #include "klee/Internal/Module/KInstruction.h"
8 : #include "klee/Internal/Module/KModule.h"
9 :
10 : #include "klee/Expr.h"
11 : #include "klee/Memory.h"
12 :
13 : #include "llvm/Function.h"
14 : #include "llvm/Support/CommandLine.h"
15 :
16 : #include <iostream>
17 : #include <cassert>
18 : #include <map>
19 : #include <set>
20 : #include <stdarg.h>
21 :
22 : #include "klee/Internal/FIXME/sugar.h"
23 :
24 : using namespace llvm;
25 : using namespace klee;
26 :
27 : namespace {
28 : cl::opt<bool>
29 103: DebugLogStateMerge("debug-log-state-merge");
30 : }
31 :
32 : /***/
33 :
34 : // FIXME: Ugh. See comment in Searcher.cpp
35 : extern
36 : std::map< std::pair< std::pair<unsigned, bool>,
37 : std::pair<unsigned, bool> >,
38 : int > termStatesBranchPairTally;
39 :
40 : // only used if klee::userSearcherRequiresBranchSequences()
41 : // contains two-branch adjacent sequences that have been seen thus
42 : // during ANY state that has TERMINATED
43 : // (implemented as a multiset so can count the number of terminated
44 : // states that has seen a particular branch pair)
45 : extern
46 : std::multiset< std::pair< std::pair<unsigned, bool>,
47 : std::pair<unsigned, bool> > > seenBranchPairs;
48 :
49 : /***/
50 :
51 1056: StackFrame::StackFrame(KInstIterator _caller, KFunction *_kf)
52 : : caller(_caller), kf(_kf), callPathNode(0),
53 3168: minDistToUncoveredOnReturn(0), varargs(0) {
1742440: branch 1 taken
1056: branch 2 taken
0: branch 4 not taken
0: branch 5 not taken
54 1743496: locals = new Cell[kf->numRegisters];
55 1056: }
56 :
57 3619: StackFrame::StackFrame(const StackFrame &s)
58 : : caller(s.caller),
59 : kf(s.kf),
60 : callPathNode(s.callPathNode),
61 : allocas(s.allocas),
62 : minDistToUncoveredOnReturn(s.minDistToUncoveredOnReturn),
63 7238: varargs(s.varargs) {
5461222: branch 1 taken
3619: branch 2 taken
0: branch 4 not taken
0: branch 5 not taken
64 5464841: locals = new Cell[s.kf->numRegisters];
5461222: branch 0 taken
3619: branch 1 taken
3619: branch 2 taken
3619: branch 3 taken
65 5464841: for (unsigned i=0; i<s.kf->numRegisters; i++)
66 5461222: locals[i] = s.locals[i];
67 3619: }
68 :
69 4675: StackFrame::~StackFrame() {
4675: branch 0 taken
0: branch 1 not taken
7203662: branch 2 taken
4675: branch 3 taken
4675: branch 5 taken
4675: branch 6 taken
4675: branch 7 taken
4675: branch 8 taken
70 9350: delete[] locals;
71 4675: }
72 :
73 : /***/
74 :
75 103: ExecutionState::ExecutionState(KFunction *kf)
76 : : fakeState(false),
77 : underConstrained(false),
78 : depth(0),
79 : pc(kf->instructions),
80 : prevPC(pc),
81 : queryCost(0.),
82 : weight(1),
83 : instsSinceCovNew(0),
84 : coveredNew(false),
85 : forkDisabled(false),
86 1339: ptreeNode(0) {
87 103: pushFrame(0, kf);
88 103: }
89 :
90 0: ExecutionState::ExecutionState(const std::vector<ref<Expr> > &assumptions)
91 : : fakeState(true),
92 : underConstrained(false),
93 : constraints(assumptions),
94 : queryCost(0.),
95 0: ptreeNode(0) {
96 0: }
97 :
98 1453: ExecutionState::~ExecutionState() {
1551: branch 1 taken
1453: branch 2 taken
0: branch 4 not taken
0: branch 5 not taken
99 4457: while (!stack.empty()) popFrame();
100 13077: }
101 :
102 687: ExecutionState *ExecutionState::branch() {
103 687: depth++;
104 :
105 1374: ExecutionState *falseState = new ExecutionState(*this);
106 687: falseState->coveredNew = false;
107 687: falseState->coveredLines.clear();
108 :
109 687: weight *= .5;
110 687: falseState->weight -= weight;
111 :
112 687: return falseState;
113 : }
114 :
115 1056: void ExecutionState::pushFrame(KInstIterator caller, KFunction *kf) {
116 2112: stack.push_back(StackFrame(caller,kf));
117 1056: }
118 :
119 3063: void ExecutionState::popFrame() {
120 6126: StackFrame &sf = stack.back();
601283: branch 0 taken
3063: branch 1 taken
121 610472: foreach(it, sf.allocas.begin(), sf.allocas.end())
122 601283: addressSpace.unbindObject(*it);
123 3063: stack.pop_back();
124 3063: }
125 :
126 0: bool ExecutionState::isLastBranchPairSeen() {
127 0: unsigned s = branchDecisionsSequence.size();
0: branch 0 not taken
0: branch 1 not taken
128 0: if (s > 1) {
129 : std::pair< std::pair<unsigned, bool>, std::pair<unsigned, bool> > lastPair =
130 0: std::make_pair(branchDecisionsSequence[s-2], branchDecisionsSequence[s-1]);
131 0: return (seenBranchPairs.count(lastPair) > 0);
132 : }
133 : else {
134 0: return false;
135 : }
136 : }
137 :
138 : // Pre: branchDecisionsSequence.size() > 1
139 0: std::pair< std::pair<unsigned, bool>, std::pair<unsigned, bool> > ExecutionState::getLastBranchPair() {
140 0: unsigned s = branchDecisionsSequence.size();
0: branch 0 not taken
0: branch 1 not taken
141 0: assert(s > 1);
142 : std::pair< std::pair<unsigned, bool>, std::pair<unsigned, bool> > lastPair =
143 0: std::make_pair(branchDecisionsSequence[s-2], branchDecisionsSequence[s-1]);
144 : return lastPair;
145 : }
146 :
147 : // return total tally on all branch pairs seen thus far
148 : // in this state (no dups) by referring to
149 : // termStatesBranchPairTally:
150 0: int ExecutionState::getBranchPairsTally() {
151 0: int total = 0;
0: branch 0 not taken
0: branch 1 not taken
152 0: foreach(it, mySeenBranchPairs.begin(), mySeenBranchPairs.end()) {
153 0: total += termStatesBranchPairTally[*it];
154 : }
155 0: return total;
156 : }
157 :
158 0: void ExecutionState::branchSeqHandleStateBranch(unsigned branchID, bool trueTaken, bool isTwoWay) {
159 0: std::pair<unsigned, bool> curBranchDecision = std::make_pair(branchID, trueTaken);
160 :
161 : // add the most recent branch pair to state.mySeenBranchPairs:
0: branch 0 not taken
0: branch 1 not taken
162 0: if (branchDecisionsSequence.size() > 0) {
163 0: std::pair<unsigned, bool> lastBranchDecision = branchDecisionsSequence.back();
164 :
165 0: mySeenBranchPairs.insert(std::make_pair(lastBranchDecision, curBranchDecision));
166 :
167 : /*
168 : klee_message(" branchSeqHandleStateBranch: <%u, %u>, <%u, %u>",
169 : lastBranchDecision.first,
170 : lastBranchDecision.second,
171 : curBranchDecision.first,
172 : curBranchDecision.second);
173 : */
174 : }
175 :
176 0: branchDecisionsSequence.push_back(curBranchDecision);
177 :
178 : BranchInfo b;
179 0: b.isTwoWay = isTwoWay;
180 0: b.alreadyCoveredNew = coveredNew;
181 0: branchInfoSequence.push_back(b);
182 :
183 : //klee_message("branchSeqHandleStateBranch() - %u", branchDecisionsSequence.size());
184 0: }
185 :
186 : ///
187 :
188 3010735: std::string ExecutionState::getFnAlias(std::string fn) {
189 3010735: std::map < std::string, std::string >::iterator it = fnAliases.find(fn);
4: branch 0 taken
3010731: branch 1 taken
190 6021470: if (it != fnAliases.end())
191 8: return it->second;
192 6021462: else return "";
193 : }
194 :
195 2: void ExecutionState::addFnAlias(std::string old_fn, std::string new_fn) {
196 2: fnAliases[old_fn] = new_fn;
197 2: }
198 :
199 5: void ExecutionState::removeFnAlias(std::string fn) {
200 5: fnAliases.erase(fn);
201 5: }
202 :
203 : /**/
204 :
205 28: std::ostream &klee::operator<<(std::ostream &os, const MemoryMap &mm) {
206 28: os << "{";
207 : let(it, mm.begin());
208 : let(ie, mm.end());
28: branch 0 taken
0: branch 1 not taken
209 28: if (it!=ie) {
210 84: os << "MO" << it->first->id << ":" << it->second;
2268: branch 2 taken
28: branch 3 taken
211 4592: for (++it; it!=ie; ++it)
212 6804: os << ", MO" << it->first->id << ":" << it->second;
213 : }
214 28: os << "}";
215 28: return os;
216 : }
217 :
218 70: bool ExecutionState::merge(const ExecutionState &b) {
14: branch 0 taken
56: branch 1 taken
219 70: if (DebugLogStateMerge)
220 14: llvm::cerr << "-- attempting merge of A:" << this << " with B:" << &b << "--\n";
0: branch 0 not taken
70: branch 1 taken
221 140: if (pc != b.pc)
222 0: return false;
223 :
224 : #ifdef KLEE_TRACK_REFERENTS
225 : // DRE: haven't thought about merging base object information.
226 : return false;
227 : #endif
228 :
229 : // XXX is it even possible for these to differ? does it matter? probably
230 : // implies difference in object states?
0: branch 0 not taken
70: branch 1 taken
231 140: if (symbolics!=b.symbolics)
232 0: return false;
233 :
234 : {
235 70: let(itA, stack.begin());
236 70: let(itB, b.stack.begin());
140: branch 0 taken
70: branch 1 taken
140: branch 2 taken
0: branch 3 not taken
140: branch 4 taken
70: branch 5 taken
237 560: while (itA!=stack.end() && itB!=b.stack.end()) {
238 : // XXX vaargs?
140: branch 0 taken
0: branch 1 not taken
0: branch 2 not taken
140: branch 3 taken
0: branch 4 not taken
140: branch 5 taken
239 700: if (itA->caller!=itB->caller || itA->kf!=itB->kf)
240 0: return false;
241 : ++itA;
242 : ++itB;
243 : }
70: branch 0 taken
0: branch 1 not taken
0: branch 2 not taken
70: branch 3 taken
0: branch 4 not taken
70: branch 5 taken
244 210: if (itA!=stack.end() || itB!=b.stack.end())
245 0: return false;
246 : }
247 :
248 : std::set< ref<Expr> > aConstraints, bConstraints, commonConstraints, aSuffix, bSuffix;
70: branch 0 taken
70: branch 1 taken
249 350: foreach(it, constraints.begin(), constraints.end())
250 : aConstraints.insert(*it);
595: branch 0 taken
70: branch 1 taken
251 805: foreach(it, b.constraints.begin(), b.constraints.end())
252 : bConstraints.insert(*it);
253 : std::set_intersection(aConstraints.begin(), aConstraints.end(),
254 : bConstraints.begin(), bConstraints.end(),
255 140: std::inserter(commonConstraints, commonConstraints.begin()));
256 : std::set_difference(aConstraints.begin(), aConstraints.end(),
257 : commonConstraints.begin(), commonConstraints.end(),
258 140: std::inserter(aSuffix, aSuffix.end()));
259 : std::set_difference(bConstraints.begin(), bConstraints.end(),
260 : commonConstraints.begin(), commonConstraints.end(),
261 140: std::inserter(bSuffix, bSuffix.end()));
14: branch 0 taken
56: branch 1 taken
262 70: if (DebugLogStateMerge) {
263 : llvm::cerr << "\tconstraint prefix: [";
0: branch 1 not taken
14: branch 2 taken
264 28: foreach(it, commonConstraints.begin(), commonConstraints.end())
265 0: llvm::cerr << *it << ", ";
266 : llvm::cerr << "]\n";
267 : llvm::cerr << "\tA suffix: [";
14: branch 1 taken
14: branch 2 taken
268 56: foreach(it, aSuffix.begin(), aSuffix.end())
269 14: llvm::cerr << *it << ", ";
270 : llvm::cerr << "]\n";
271 : llvm::cerr << "\tB suffix: [";
119: branch 1 taken
14: branch 2 taken
272 266: foreach(it, bSuffix.begin(), bSuffix.end())
273 119: llvm::cerr << *it << ", ";
274 : llvm::cerr << "]\n";
275 : }
276 :
277 : // We cannot merge if addresses would resolve differently in the
278 : // states. This means:
279 : //
280 : // 1. Any objects created since the branch in either object must
281 : // have been free'd.
282 : //
283 : // 2. We cannot have free'd any pre-existing object in one state
284 : // and not the other
285 :
14: branch 0 taken
56: branch 1 taken
286 70: if (DebugLogStateMerge) {
287 : llvm::cerr << "\tchecking object states\n";
288 14: llvm::cerr << "A: " << addressSpace.objects << "\n";
289 14: llvm::cerr << "B: " << b.addressSpace.objects << "\n";
290 : }
291 :
292 : std::set<const MemoryObject*> mutated;
293 70: let(ai, addressSpace.objects.begin());
294 70: let(bi, b.addressSpace.objects.begin());
295 70: let(ae, addressSpace.objects.end());
296 70: let(be, b.addressSpace.objects.end());
5740: branch 2 taken
70: branch 3 taken
5740: branch 4 taken
0: branch 5 not taken
5740: branch 6 taken
70: branch 7 taken
297 17360: for (; ai!=ae && bi!=be; ++ai, ++bi) {
0: branch 0 not taken
5740: branch 1 taken
298 11480: if (ai->first != bi->first) {
0: branch 0 not taken
0: branch 1 not taken
299 0: if (DebugLogStateMerge) {
0: branch 0 not taken
0: branch 1 not taken
300 0: if (ai->first < bi->first) {
301 0: llvm::cerr << "\t\tB misses binding for: " << ai->first->id << "\n";
302 : } else {
303 0: llvm::cerr << "\t\tA misses binding for: " << bi->first->id << "\n";
304 : }
305 : }
306 0: return false;
307 : }
70: branch 0 taken
5670: branch 1 taken
308 17220: if (ai->second != bi->second) {
14: branch 0 taken
56: branch 1 taken
309 70: if (DebugLogStateMerge)
310 14: llvm::cerr << "\t\tmutated: " << ai->first->id << "\n";
311 70: mutated.insert(ai->first);
312 : }
313 : }
70: branch 0 taken
0: branch 1 not taken
0: branch 2 not taken
70: branch 3 taken
0: branch 4 not taken
70: branch 5 taken
314 140: if (ai!=ae || bi!=be) {
0: branch 0 not taken
0: branch 1 not taken
315 0: if (DebugLogStateMerge)
316 : llvm::cerr << "\t\tmappings differ\n";
317 0: return false;
318 : }
319 :
320 : // merge stack
321 :
322 : ref<Expr> inA(1, Expr::Bool), inB(1, Expr::Bool);
70: branch 1 taken
70: branch 2 taken
323 280: foreach(it, aSuffix.begin(), aSuffix.end())
324 70: inA = AndExpr::create(inA, *it);
595: branch 1 taken
70: branch 2 taken
325 1330: foreach(it, bSuffix.begin(), bSuffix.end())
326 595: inB = AndExpr::create(inB, *it);
327 :
328 : // XXX should we have a preference as to which predicate to use?
329 : // it seems like it can make a difference, even though logically
330 : // they must contradict each other and so inA => !inB
331 :
332 70: let(itA, stack.begin());
333 70: let(itB, b.stack.begin());
140: branch 0 taken
70: branch 1 taken
334 420: for (; itA!=stack.end(); ++itA, ++itB) {
335 140: StackFrame &af = *itA;
336 140: const StackFrame &bf = *itB;
5040: branch 0 taken
140: branch 1 taken
337 5180: for (unsigned i=0; i<af.kf->numRegisters; i++) {
338 5040: ref<Expr> &av = af.locals[i].value;
339 5040: const ref<Expr> &bv = bf.locals[i].value;
2240: branch 0 taken
2800: branch 1 taken
0: branch 2 not taken
2240: branch 3 taken
2240: branch 4 taken
2800: branch 5 taken
340 7280: if (av.isNull() || bv.isNull()) {
341 : // if one is null then by implication (we are at same pc)
342 : // we cannot reuse this local, so just ignore
343 : } else {
344 2240: av = SelectExpr::create(inA, av, bv);
345 : }
346 : }
347 : }
348 :
70: branch 0 taken
70: branch 1 taken
349 210: foreach(it, mutated.begin(), mutated.end()) {
350 70: const MemoryObject *mo = *it;
351 70: const ObjectState *os = addressSpace.findObject(mo);
352 70: const ObjectState *otherOS = b.addressSpace.findObject(mo);
70: branch 0 taken
0: branch 1 not taken
0: branch 2 not taken
70: branch 3 taken
353 70: assert(os && !os->readOnly && "objects mutated but not writable in merging state");
0: branch 0 not taken
70: branch 1 taken
354 70: assert(otherOS);
355 :
356 70: ObjectState *wos = addressSpace.getWriteable(mo, os);
280: branch 2 taken
70: branch 3 taken
357 350: for (unsigned i=0; i<mo->size; i++) {
358 280: ref<Expr> av = wos->read8(i);
359 280: ref<Expr> bv = otherOS->read8(i);
360 280: wos->write(i, SelectExpr::create(inA, av, bv));
361 : }
362 : }
363 :
364 140: constraints = ConstraintManager();
0: branch 1 not taken
70: branch 2 taken
365 140: foreach(it, commonConstraints.begin(), commonConstraints.end()) {
366 0: constraints.addConstraint(*it);
367 : }
368 70: constraints.addConstraint(OrExpr::create(inA, inB));
369 :
370 70: return true;
371 : }
372 :
373 : /**/
374 :
375 : /*
376 : Used for tainting: create a clone of os that we can revirt to with
377 : the behavior that all constraints are preserved, but writes are
378 : discarded. When we revirt it will be at the same address.
379 : */
380 : ObjectState *ExecutionState::cloneObject(ObjectState *os,
381 0: MemoryObject *mo) {
382 0: MemoryMap::iterator it = shadowObjects.find(mo);
0: branch 1 not taken
0: branch 2 not taken
383 0: if (it != shadowObjects.end())
384 0: assert(0 && "Cannot exist already!");
385 :
386 : llvm::cerr << "DRE: Inserting a cloned object: " << mo << "\n";
387 0: shadowObjects = shadowObjects.replace(std::make_pair(mo, os));
388 0: os = new ObjectState(*os);
389 0: addressSpace.bindObject(mo, os);
390 0: return os;
391 : }
392 :
393 : void ExecutionState::revirtClonedObject(const MemoryObject *mo,
394 0: ObjectState *os) {
395 : // XXX DWD this may be busted if os has already been bound.
396 : // probably fixed by just making a copy of the os, but I'm
397 : // not sure what is going on here so skipping. bug me if
398 : // you have problems DRE.
399 0: addressSpace.bindObject(mo, os);
400 : llvm::cerr << "Revirting object: " << mo << "\n";
401 0: }
402 :
403 : // Or should this be a one at a time thing?
404 0: void ExecutionState::revirtClonedObjects(void) {
405 : llvm::cerr << "Revirting objects\n";
406 0: unsigned n = 0;
407 :
0: branch 1 not taken
0: branch 2 not taken
408 0: foreach(it_orig, shadowObjects.begin(), shadowObjects.end()) {
409 0: revirtClonedObject(it_orig->first, it_orig->second);
410 0: n++;
411 0: }
412 : llvm::cerr << "Revirted: " << n << " objects\n";
413 0: }
414 :
415 : /***/
416 :
417 :
418 : ExecutionTraceEvent::ExecutionTraceEvent(ExecutionState& state,
419 0: KInstruction* ki)
420 0: : consecutiveCount(1)
421 : {
422 0: file = ki->info->file;
423 0: line = ki->info->line;
424 0: funcName = state.stack.back().kf->function->getName();
425 0: stackDepth = state.stack.size();
426 0: }
427 :
428 0: bool ExecutionTraceEvent::ignoreMe() const {
429 : // ignore all events occurring in certain pesky uclibc files:
0: branch 1 not taken
0: branch 2 not taken
430 0: if (file.find("libc/stdio/") != std::string::npos) {
431 0: return true;
432 : }
433 :
434 0: return false;
435 : }
436 :
437 0: void ExecutionTraceEvent::print(std::ostream &os) const {
438 0: os.width(stackDepth);
439 0: os << ' ';
440 0: printDetails(os);
441 0: os << ' ' << file << ':' << line << ':' << funcName;
0: branch 0 not taken
0: branch 1 not taken
442 0: if (consecutiveCount > 1)
443 0: os << " (" << consecutiveCount << "x)\n";
444 : else
445 0: os << '\n';
446 0: }
447 :
448 :
449 0: bool ExecutionTraceEventEquals(ExecutionTraceEvent* e1, ExecutionTraceEvent* e2) {
450 : // first see if their base class members are identical:
0: branch 0 not taken
0: branch 1 not taken
0: branch 2 not taken
0: branch 3 not taken
0: branch 4 not taken
0: branch 5 not taken
0: branch 6 not taken
0: branch 7 not taken
451 0: if (!((e1->file == e2->file) &&
452 : (e1->line == e2->line) &&
453 : (e1->funcName == e2->funcName)))
454 0: return false;
455 :
456 : // fairly ugly, but i'm no OOP master, so this is the way i'm
457 : // doing it for now ... lemme know if there's a cleaner way:
0: branch 0 not taken
0: branch 1 not taken
458 0: BranchTraceEvent* be1 = dynamic_cast<BranchTraceEvent*>(e1);
0: branch 0 not taken
0: branch 1 not taken
459 0: BranchTraceEvent* be2 = dynamic_cast<BranchTraceEvent*>(e2);
0: branch 0 not taken
0: branch 1 not taken
460 0: if (be1 && be2) {
461 : return ((be1->trueTaken == be2->trueTaken) &&
462 0: (be1->canForkGoBothWays == be2->canForkGoBothWays));
463 : }
464 :
465 : // don't tolerate duplicates in anything else:
466 0: return false;
467 : }
468 :
469 :
470 0: void BranchTraceEvent::printDetails(std::ostream &os) const {
471 : os << "BRANCH " << (trueTaken ? "T" : "F") << ' ' <<
0: branch 0 not taken
0: branch 1 not taken
0: branch 2 not taken
0: branch 3 not taken
472 0: (canForkGoBothWays ? "2-way" : "1-way");
473 0: }
474 :
475 0: void ExecutionTraceManager::addEvent(ExecutionTraceEvent* evt) {
476 : // don't trace anything before __user_main, except for global events
0: branch 0 not taken
0: branch 1 not taken
477 0: if (!hasSeenUserMain) {
0: branch 0 not taken
0: branch 1 not taken
478 0: if (evt->funcName == "__user_main") {
479 0: hasSeenUserMain = true;
480 : }
0: branch 0 not taken
0: branch 1 not taken
481 0: else if (evt->funcName != "global_def") {
482 0: return;
483 : }
484 : }
485 :
486 : // custom ignore events:
0: branch 1 not taken
0: branch 2 not taken
487 0: if (evt->ignoreMe())
488 0: return;
489 :
0: branch 0 not taken
0: branch 1 not taken
490 0: if (events.size() > 0) {
491 : // compress consecutive duplicates:
492 0: ExecutionTraceEvent* last = events.back();
0: branch 1 not taken
0: branch 2 not taken
493 0: if (ExecutionTraceEventEquals(last, evt)) {
494 0: last->consecutiveCount++;
495 0: return;
496 : }
497 : }
498 :
499 0: events.push_back(evt);
500 : }
501 :
502 0: void ExecutionTraceManager::printAllEvents(std::ostream &os) const {
0: branch 0 not taken
0: branch 1 not taken
503 0: foreach(it, events.begin(), events.end()) {
504 0: (*it)->print(os);
505 : }
103: branch 0 taken
0: branch 1 not taken
103: branch 2 taken
0: branch 3 not taken
506 309: }
507 :
508 : /***/
Generated: 2009-05-17 22:47 by zcov