Back to index

lightning-sunbird  0.9+nobinonly
leaky.cpp
Go to the documentation of this file.
00001 /* ***** BEGIN LICENSE BLOCK *****
00002  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
00003  *
00004  * The contents of this file are subject to the Mozilla Public License Version
00005  * 1.1 (the "License"); you may not use this file except in compliance with
00006  * the License. You may obtain a copy of the License at
00007  * http://www.mozilla.org/MPL/
00008  *
00009  * Software distributed under the License is distributed on an "AS IS" basis,
00010  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
00011  * for the specific language governing rights and limitations under the
00012  * License.
00013  *
00014  * The Original Code is mozilla.org code.
00015  *
00016  * The Initial Developer of the Original Code is Kipp E.B. Hickman.
00017  * Portions created by the Initial Developer are Copyright (C) 1999
00018  * the Initial Developer. All Rights Reserved.
00019  *
00020  * Contributor(s):
00021  *
00022  * Alternatively, the contents of this file may be used under the terms of
00023  * either the GNU General Public License Version 2 or later (the "GPL"), or
00024  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00025  * in which case the provisions of the GPL or the LGPL are applicable instead
00026  * of those above. If you wish to allow use of your version of this file only
00027  * under the terms of either the GPL or the LGPL, and not to allow others to
00028  * use your version of this file under the terms of the MPL, indicate your
00029  * decision by deleting the provisions above and replace them with the notice
00030  * and other provisions required by the GPL or the LGPL. If you do not delete
00031  * the provisions above, a recipient may use your version of this file under
00032  * the terms of any one of the MPL, the GPL or the LGPL.
00033  *
00034  * ***** END LICENSE BLOCK ***** */
00035 
00036 #include "leaky.h"
00037 #include "dict.h"
00038 
00039 #include <sys/types.h>
00040 #include <sys/mman.h>
00041 #include <sys/stat.h>
00042 #include <fcntl.h>
00043 #include <unistd.h>
00044 #include <string.h>
00045 #ifndef NTO
00046 #include <getopt.h>
00047 #endif
00048 #include <assert.h>
00049 #include <stdlib.h>
00050 #include <stdio.h>
00051 
00052 #ifdef NTO
00053 #include <mem.h>
00054 #endif
00055 
00056 #ifndef FALSE
00057 #define FALSE 0
00058 #endif
00059 #ifndef TRUE
00060 #define TRUE 1
00061 #endif
00062 
00063 static const u_int DefaultBuckets = 10007;       // arbitrary, but prime
00064 static const u_int MaxBuckets = 1000003;  // arbitrary, but prime
00065 
00066 //----------------------------------------------------------------------
00067 
00068 int main(int argc, char** argv)
00069 {
00070   leaky* l = new leaky;
00071 
00072   l->initialize(argc, argv);
00073   l->open();
00074   return 0;
00075 }
00076 
00077 leaky::leaky()
00078 {
00079   applicationName = NULL;
00080   logFile = NULL;
00081   progFile = NULL;
00082 
00083   dumpLeaks = FALSE;
00084   dumpGraph = FALSE;
00085   dumpHTML = FALSE;
00086   quiet = FALSE;
00087   dumpEntireLog = FALSE;
00088   showAddress = FALSE;
00089   stackDepth = 100000;
00090   dumpRefcnts = false;
00091 
00092   mappedLogFile = -1;
00093   firstLogEntry = lastLogEntry = 0;
00094   buckets = DefaultBuckets;
00095   dict = NULL;
00096   refcntDict = NULL;
00097 
00098   mallocs = 0;
00099   reallocs = 0;
00100   frees = 0;
00101   totalMalloced = 0;
00102   errors = 0;
00103   totalLeaked = 0;
00104 
00105   sfd = -1;
00106   externalSymbols = 0;
00107   usefulSymbols = 0;
00108   numExternalSymbols = 0;
00109   lowestSymbolAddr = 0;
00110   highestSymbolAddr = 0;
00111 
00112   loadMap = NULL;
00113 }
00114 
00115 leaky::~leaky()
00116 {
00117   delete dict;
00118 }
00119 
00120 void leaky::usageError()
00121 {
00122   fprintf(stderr,
00123          "Usage: %s [-aAEdfgqxR] [-e name] [-s depth] [-h hash-buckets] [-r root|-i symbol] prog log\n",
00124          (char*) applicationName);
00125   exit(-1);
00126 }
00127 
00128 void leaky::initialize(int argc, char** argv)
00129 {
00130   applicationName = argv[0];
00131   applicationName = strrchr(applicationName, '/');
00132   if (!applicationName) {
00133     applicationName = argv[0];
00134   } else {
00135     applicationName++;
00136   }
00137 
00138   int arg;
00139   int errflg = 0;
00140   while ((arg = getopt(argc, argv, "adEe:gh:i:r:Rs:tqx")) != -1) {
00141     switch (arg) {
00142       case '?':
00143        errflg++;
00144        break;
00145       case 'a':
00146        dumpEntireLog = TRUE;
00147        break;
00148       case 'A':
00149        showAddress = TRUE;
00150        break;
00151       case 'd':
00152        dumpLeaks = TRUE;
00153        if (dumpGraph) errflg++;
00154        break;
00155       case 'R':
00156        dumpRefcnts = true;
00157        break;
00158       case 'e':
00159        exclusions.add(optarg);
00160        break;
00161       case 'g':
00162        dumpGraph = TRUE;
00163        if (dumpLeaks) errflg++;
00164        break;
00165       case 'r':
00166        roots.add(optarg);
00167        if (!includes.IsEmpty()) {
00168          errflg++;
00169        }
00170        break;
00171       case 'i':
00172        includes.add(optarg);
00173        if (!roots.IsEmpty()) {
00174          errflg++;
00175        }
00176        break;
00177       case 'h':
00178        buckets = atoi(optarg);
00179        if ((buckets < 0) || (buckets > MaxBuckets)) {
00180          buckets = MaxBuckets;
00181          fprintf(stderr, "%s: buckets is invalid, using %d\n",
00182                 (char*) applicationName,
00183                 buckets);
00184        }
00185        break;
00186       case 's':
00187        stackDepth = atoi(optarg);
00188        if (stackDepth < 2) {
00189          stackDepth = 2;
00190        }
00191        break;
00192       case 'x':
00193        dumpHTML = TRUE;
00194        break;
00195       case 'q':
00196        quiet = TRUE;
00197        break;
00198     }
00199   }
00200   if (errflg || ((argc - optind) < 2)) {
00201     usageError();
00202   }
00203   progFile = argv[optind++];
00204   logFile = argv[optind];
00205 
00206   dict = new MallocDict(buckets);
00207   if (dumpRefcnts) {
00208     refcntDict = new MallocDict(buckets);
00209   }
00210 }
00211 
00212 static void* mapFile(int fd, u_int flags, off_t* sz)
00213 {
00214   struct stat sb;
00215   if (fstat(fd, &sb) < 0) {
00216     perror("fstat");
00217     exit(-1);
00218   }
00219   void* base = mmap(0, (int)sb.st_size, flags, MAP_PRIVATE, fd, 0);
00220   if (!base) {
00221     perror("mmap");
00222     exit(-1);
00223   }
00224   *sz = sb.st_size;
00225   return base;
00226 }
00227 
00228 void leaky::LoadMap()
00229 {
00230   malloc_map_entry mme;
00231   char name[1000];
00232 
00233   int fd = ::open("malloc-map", O_RDONLY);
00234   if (fd < 0) {
00235     perror("open: malloc-map");
00236     exit(-1);
00237   }
00238   for (;;) {
00239     int nb = read(fd, &mme, sizeof(mme));
00240     if (nb != sizeof(mme)) break;
00241     nb = read(fd, name, mme.nameLen);
00242     if (nb != (int)mme.nameLen) break;
00243     name[mme.nameLen] = 0;
00244     if (!quiet) {
00245       printf("%s @ %lx\n", name, mme.address);
00246     }
00247 
00248     LoadMapEntry* lme = new LoadMapEntry;
00249     lme->address = mme.address;
00250     lme->name = strdup(name);
00251     lme->next = loadMap;
00252     loadMap = lme;
00253   }
00254   close(fd);
00255 }
00256 
00257 void leaky::open()
00258 {
00259   LoadMap();
00260 
00261   setupSymbols(progFile);
00262 
00263   // open up the log file
00264   mappedLogFile = ::open(logFile, O_RDONLY);
00265   if (mappedLogFile < 0) {
00266     perror("open");
00267     exit(-1);
00268   }
00269   off_t size;
00270   firstLogEntry = (malloc_log_entry*) mapFile(mappedLogFile, PROT_READ, &size);
00271   lastLogEntry = (malloc_log_entry*)((char*)firstLogEntry + size);
00272 
00273   analyze();
00274 
00275   if (dumpLeaks || dumpEntireLog || dumpRefcnts) {
00276     dumpLog();
00277   }
00278   else if (dumpGraph) {
00279     buildLeakGraph();
00280     dumpLeakGraph();
00281   }
00282   exit(0);
00283 }
00284 
00285 //----------------------------------------------------------------------
00286 
00287 
00288 static ptrdiff_t symbolOrder(void const* a, void const* b)
00289 {
00290   Symbol const* ap = (Symbol const *)a;
00291   Symbol const* bp = (Symbol const *)b;
00292   return ap->address - bp->address;
00293 }
00294 
00295 void leaky::ReadSharedLibrarySymbols()
00296 {
00297   LoadMapEntry* lme = loadMap;
00298   while (NULL != lme) {
00299     ReadSymbols(lme->name, lme->address);
00300     lme = lme->next;
00301   }
00302 }
00303 
00304 void leaky::setupSymbols(const char *fileName)
00305 {
00306   // Read in symbols from the program
00307   ReadSymbols(fileName, 0);
00308 
00309   // Read in symbols from the .so's
00310   ReadSharedLibrarySymbols();
00311 
00312   if (!quiet) {
00313     printf("A total of %d symbols were loaded\n", usefulSymbols);
00314   }
00315 
00316   // Now sort them
00317   qsort(externalSymbols, usefulSymbols, sizeof(Symbol), symbolOrder);
00318   lowestSymbolAddr = externalSymbols[0].address;
00319   highestSymbolAddr = externalSymbols[usefulSymbols-1].address;
00320 }
00321 
00322 // Binary search the table, looking for a symbol that covers this
00323 // address.
00324 Symbol* leaky::findSymbol(u_long addr)
00325 {
00326   u_int base = 0;
00327   u_int limit = usefulSymbols - 1;
00328   Symbol* end = &externalSymbols[limit];
00329   while (base <= limit) {
00330     u_int midPoint = (base + limit)>>1;
00331     Symbol* sp = &externalSymbols[midPoint];
00332     if (addr < sp->address) {
00333       if (midPoint == 0) {
00334        return NULL;
00335       }
00336       limit = midPoint - 1;
00337     } else {
00338       if (sp+1 < end) {
00339        if (addr < (sp+1)->address) {
00340          return sp;
00341        }
00342       } else {
00343        return sp;
00344       }
00345       base = midPoint + 1;
00346     }
00347   }
00348   return NULL;
00349 }
00350 
00351 //----------------------------------------------------------------------
00352 
00353 bool leaky::excluded(malloc_log_entry* lep)
00354 {
00355   if (exclusions.IsEmpty()) {
00356     return false;
00357   }
00358 
00359   char** pcp = &lep->pcs[0];
00360   u_int n = lep->numpcs;
00361   for (u_int i = 0; i < n; i++, pcp++) {
00362     Symbol* sp = findSymbol((u_long) *pcp);
00363     if (sp && exclusions.contains(sp->name)) {
00364       return true;
00365     }
00366   }
00367   return false;
00368 }
00369 
00370 bool leaky::included(malloc_log_entry* lep)
00371 {
00372   if (includes.IsEmpty()) {
00373     return true;
00374   }
00375 
00376   char** pcp = &lep->pcs[0];
00377   u_int n = lep->numpcs;
00378   for (u_int i = 0; i < n; i++, pcp++) {
00379     Symbol* sp = findSymbol((u_long) *pcp);
00380     if (sp && includes.contains(sp->name)) {
00381       return true;
00382     }
00383   }
00384   return false;
00385 }
00386 
00387 //----------------------------------------------------------------------
00388 
00389 void leaky::displayStackTrace(FILE* out, malloc_log_entry* lep)
00390 {
00391   char** pcp = &lep->pcs[0];
00392   u_int n = (lep->numpcs < stackDepth) ? lep->numpcs : stackDepth;
00393   for (u_int i = 0; i < n; i++, pcp++) {
00394     u_long addr = (u_long) *pcp;
00395     Symbol* sp = findSymbol(addr);
00396     if (sp) {
00397       fputs(sp->name, out);
00398       if (showAddress) {
00399        fprintf(out, "[%p]", (char*)addr);
00400       }
00401     }
00402     else {
00403       fprintf(out, "<%p>", (char*)addr);
00404     }
00405     fputc(' ', out);
00406   }
00407   fputc('\n', out);
00408 }
00409 
00410 char* typeFromLog[] = {
00411     "malloc",
00412     "realloc",
00413     "free",
00414     "new",
00415     "delete",
00416     "addref",
00417     "release"
00418 };
00419 
00420 void leaky::dumpEntryToLog(malloc_log_entry* lep)
00421 {
00422   printf("%-10s %08lx %5ld ",
00423         typeFromLog[lep->type],
00424         lep->address, lep->size);
00425   if (IsRefcnt(lep)) {
00426     printf("%08ld", lep->oldaddress);
00427   }
00428   else {
00429     printf("%08lx", lep->oldaddress);
00430   }
00431   printf(" --> ");
00432   displayStackTrace(stdout, lep);
00433 }
00434 
00435 bool leaky::ShowThisEntry(malloc_log_entry* lep)
00436 {
00437   if ((!dumpRefcnts || IsRefcnt(lep)) && !excluded(lep) && included(lep)) {
00438     return true;
00439   }
00440   return false;
00441 }
00442 
00443 void leaky::dumpLog()
00444 {
00445   if (dumpRefcnts) {
00446       malloc_log_entry* lep;
00447       refcntDict->rewind();
00448       while (NULL != (lep = refcntDict->next())) {
00449        if (ShowThisEntry(lep)) {
00450          // Now we get slow...
00451          u_long addr = lep->address;
00452          malloc_log_entry* lep2 = firstLogEntry;
00453          while (lep2 < lastLogEntry) {
00454            if (lep2->address == addr) {
00455              dumpEntryToLog(lep2);
00456            }
00457            lep2 = (malloc_log_entry*) &lep2->pcs[lep2->numpcs];
00458          }
00459        }
00460       }
00461   }
00462   else {
00463     if (dumpEntireLog) {
00464       malloc_log_entry* lep = firstLogEntry;
00465       while (lep < lastLogEntry) {
00466        if (ShowThisEntry(lep)) {
00467          dumpEntryToLog(lep);
00468        }
00469        lep = (malloc_log_entry*) &lep->pcs[lep->numpcs];
00470       }
00471     } else {
00472       malloc_log_entry* lep;
00473       dict->rewind();
00474       while (NULL != (lep = dict->next())) {
00475        if (ShowThisEntry(lep)) {
00476          dumpEntryToLog(lep);
00477        }
00478       }
00479     }
00480   }
00481 }
00482 
00483 //----------------------------------------------------------------------
00484 
00485 void leaky::insertAddress(u_long address, malloc_log_entry* lep)
00486 {
00487   malloc_log_entry** lepp = dict->find(address);
00488   if (lepp) {
00489     assert(*lepp);
00490     if (!quiet) {
00491       printf("Address %lx allocated twice\n", address);
00492       displayStackTrace(stdout, lep);
00493     }
00494     errors++;
00495   } else {
00496     dict->add(address, lep);
00497   }
00498 }
00499 
00500 void leaky::removeAddress(u_long address, malloc_log_entry* lep)
00501 {
00502   malloc_log_entry** lepp = dict->find(address);
00503   if (!lepp) {
00504     if (!quiet) {
00505       printf("Free of unallocated %lx\n", address);
00506       displayStackTrace(stdout, lep);
00507     }
00508     errors++;
00509   } else {
00510     dict->remove(address);
00511   }
00512 }
00513 
00514 void leaky::analyze()
00515 {
00516   malloc_log_entry* lep = firstLogEntry;
00517   while (lep < lastLogEntry) {
00518     switch (lep->type) {
00519       case malloc_log_malloc:
00520       case malloc_log_new:
00521        mallocs++;
00522        if (lep->address) {
00523          totalMalloced += lep->size;
00524          insertAddress((u_long) lep->address, lep);
00525        }
00526        break;
00527 
00528       case malloc_log_realloc:
00529        if (lep->oldaddress) {
00530          removeAddress((u_long) lep->oldaddress, lep);
00531        }
00532        if (lep->address) {
00533          insertAddress((u_long) lep->address, lep);
00534        }
00535        reallocs++;
00536        break;
00537 
00538       case malloc_log_free:
00539       case malloc_log_delete:
00540        if (lep->address) {
00541          removeAddress((u_long) lep->address, lep);
00542        }
00543        frees++;
00544        break;
00545       case malloc_log_addref:
00546        if (dumpRefcnts) {
00547          if (lep->size == 0) {
00548            // Initial addref
00549            u_long addr = (u_long) lep->address;
00550            malloc_log_entry** lepp = refcntDict->find(addr);
00551            if (!lepp) {
00552              refcntDict->add(addr, lep);
00553            }
00554          }
00555        }
00556        break;
00557       case malloc_log_release:
00558        if (dumpRefcnts) {
00559          if (lep->oldaddress == 0) {
00560            // Final release
00561            u_long addr = (u_long) lep->address;
00562            malloc_log_entry** lepp = refcntDict->find(addr);
00563            if (lepp) {
00564              refcntDict->remove(addr);
00565            }
00566          }
00567        }
00568        break;
00569     }
00570     lep = (malloc_log_entry*) &lep->pcs[lep->numpcs];
00571   }
00572 
00573   dict->rewind();
00574   while (NULL != (lep = dict->next())) {
00575     totalLeaked += lep->size;
00576   }
00577 
00578   if (!quiet) {
00579     printf("# of mallocs = %ld\n", mallocs);
00580     printf("# of reallocs = %ld\n", reallocs);
00581     printf("# of frees = %ld\n", frees);
00582     printf("# of errors = %ld\n", errors);
00583     printf("Total bytes allocated = %ld\n", totalMalloced);
00584     printf("Total bytes leaked = %ld\n", totalLeaked);
00585     printf("Average bytes per malloc = %g\n",
00586           float(totalMalloced)/mallocs);
00587   }
00588 }
00589 
00590 void leaky::buildLeakGraph()
00591 {
00592   // For each leak
00593   malloc_log_entry* lep;
00594   dict->rewind();
00595   while (NULL != (lep = dict->next())) {
00596     if (ShowThisEntry(lep)) {
00597       char** basepcp = &lep->pcs[0];
00598       char** pcp = &lep->pcs[lep->numpcs - 1];
00599 
00600       // Find root for this allocation
00601       Symbol* sym = findSymbol((u_long) *pcp);
00602       TreeNode* node = sym->root;
00603       if (!node) {
00604        sym->root = node = new TreeNode(sym);
00605 
00606        // Add root to list of roots
00607        if (roots.IsEmpty()) {
00608          node->nextRoot = rootList;
00609          rootList = node;
00610        }
00611       }
00612       pcp--;
00613 
00614       // Build tree underneath the root
00615       for (; pcp >= basepcp; pcp--) {
00616        // Share nodes in the tree until there is a divergence
00617        sym = findSymbol((u_long) *pcp);
00618        if (!sym) {
00619          break;
00620        }
00621        TreeNode* nextNode = node->GetDirectDescendant(sym);
00622        if (!nextNode) {
00623          // Make a new node at the point of divergence
00624          nextNode = node->AddDescendant(sym);
00625        }
00626 
00627        // See if the symbol is to be a user specified root. If it is,
00628        // and we haven't already stuck it on the root-list do so now.
00629        if (!sym->root && !roots.IsEmpty() && roots.contains(sym->name)) {
00630          sym->root = nextNode;
00631          nextNode->nextRoot = rootList;
00632          rootList = nextNode;
00633        }
00634 
00635        if (pcp == basepcp) {
00636          nextNode->bytesLeaked += lep->size;
00637        }
00638        else {
00639          node->descendantBytesLeaked += lep->size;
00640        }
00641 
00642        node = nextNode;
00643       }
00644     }
00645   }
00646 }
00647 
00648 Symbol* leaky::findLeakGraphRoot(Symbol* aStart, Symbol* aEnd)
00649 {
00650   while (aStart < aEnd) {
00651     if (aStart->root) {
00652       return aStart;
00653     }
00654     aStart++;
00655   }
00656   return NULL;
00657 }
00658 
00659 void leaky::dumpLeakGraph()
00660 { 
00661   if (dumpHTML) {
00662     printf("<html><head><title>Leaky Graph</title>\n");
00663     printf("<style src=\"resource:/res/leaky/leaky.css\"></style>\n");
00664     printf("<script src=\"resource:/res/leaky/leaky.js\"></script>\n");
00665     printf("</head><body><div class=\"key\">\n");
00666     printf("Key:<br>\n");
00667     printf("<span class=b>Bytes directly leaked</span><br>\n");
00668     printf("<span class=d>Bytes leaked by descendants</span></div>\n");
00669   }
00670 
00671 #if 0
00672   Symbol* base = externalSymbols;
00673   Symbol* end = externalSymbols + usefulSymbols;
00674   while (base < end) {
00675     Symbol* sym = findLeakGraphRoot(base, end);
00676     if (!sym) break;
00677     dumpLeakTree(sym->root, 0);
00678     base = sym + 1;
00679   }
00680 #else
00681   TreeNode* root = rootList;
00682   while (root) {
00683     dumpLeakTree(root, 0);
00684     root = root->nextRoot;
00685   }
00686 #endif
00687   if (dumpHTML) {
00688     printf("</body></html>\n");
00689   }
00690 }
00691 
00692 void leaky::dumpLeakTree(TreeNode* aNode, int aIndent)
00693 {
00694   Symbol* sym = aNode->symbol;
00695   if (dumpHTML) {
00696     printf("<div class=\"n\">\n");
00697     if (aNode->HasDescendants()) {
00698       printf("<img onmouseout=\"O(event);\" onmouseover=\"I(event);\" ");
00699       printf("onclick=\"C(event);\" src=\"resource:/res/leaky/%s.gif\">",
00700             aIndent > 1 ? "close" : "open");
00701     }
00702     printf("<span class=s>%s</span><span class=b>%ld</span>",
00703           sym->name,
00704           aNode->bytesLeaked);
00705     printf("<span class=d>%ld</span>\n",
00706           aNode->descendantBytesLeaked);
00707   }
00708   else {
00709     indentBy(aIndent);
00710     printf("%s bytesLeaked=%ld (%ld from kids)\n", 
00711           sym->name,
00712           aNode->bytesLeaked,
00713           aNode->descendantBytesLeaked);
00714   }
00715 
00716   TreeNode* node = aNode->descendants;
00717   int kidNum = 0;
00718   while (node) {
00719     sym = node->symbol;
00720     dumpLeakTree(node, aIndent + 1);
00721     kidNum++;
00722     node = node->nextSibling;
00723   }
00724 
00725   if (dumpHTML) {
00726     printf("</div>");
00727   }
00728 }
00729 
00730 //----------------------------------------------------------------------
00731 
00732 TreeNode* TreeNode::freeList;
00733 
00734 void* TreeNode::operator new(size_t size) CPP_THROW_NEW
00735 {
00736   if (!freeList) {
00737     TreeNode* newNodes = (TreeNode*) new char[sizeof(TreeNode) * 5000];
00738     if (!newNodes) {
00739       return NULL;
00740     }
00741     TreeNode* n = newNodes;
00742     TreeNode* end = newNodes + 5000 - 1;
00743     while (n < end) {
00744       n->nextSibling = n + 1;
00745       n++;
00746     }
00747     n->nextSibling = NULL;
00748     freeList = newNodes;
00749   }
00750 
00751   TreeNode* rv = freeList;
00752   freeList = rv->nextSibling;
00753 
00754   return (void*) rv;
00755 }
00756 
00757 void TreeNode::operator delete(void* ptr)
00758 {
00759   TreeNode* node = (TreeNode*) ptr;
00760   if (node) {
00761     node->nextSibling = freeList;
00762     freeList = node;
00763   }
00764 }
00765 
00766 TreeNode* TreeNode::GetDirectDescendant(Symbol* aSymbol)
00767 {
00768   TreeNode* node = descendants;
00769   while (node) {
00770     if (node->symbol == aSymbol) {
00771       return node;
00772     }
00773     node = node->nextSibling;
00774   }
00775   return NULL;
00776 }
00777 
00778 TreeNode* TreeNode::AddDescendant(Symbol* aSymbol)
00779 {
00780   TreeNode* node = new TreeNode(aSymbol);
00781   node->nextSibling = descendants;
00782   descendants = node;
00783   return node;
00784 }