| // Copyright (c) 2010 Google Inc. | 
 | // All rights reserved. | 
 | // | 
 | // Redistribution and use in source and binary forms, with or without | 
 | // modification, are permitted provided that the following conditions are | 
 | // met: | 
 | // | 
 | //     * Redistributions of source code must retain the above copyright | 
 | // notice, this list of conditions and the following disclaimer. | 
 | //     * Redistributions in binary form must reproduce the above | 
 | // copyright notice, this list of conditions and the following disclaimer | 
 | // in the documentation and/or other materials provided with the | 
 | // distribution. | 
 | //     * Neither the name of Google Inc. nor the names of its | 
 | // contributors may be used to endorse or promote products derived from | 
 | // this software without specific prior written permission. | 
 | // | 
 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | 
 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | 
 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | 
 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | 
 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | 
 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | 
 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | 
 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | 
 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | 
 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | 
 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | 
 |  | 
 | // postfix_evaluator_unittest.cc: Unit tests for PostfixEvaluator. | 
 | // | 
 | // Author: Mark Mentovai | 
 |  | 
 | #include <assert.h> | 
 | #include <stdio.h> | 
 |  | 
 | #include <map> | 
 | #include <string> | 
 |  | 
 | #include "processor/postfix_evaluator-inl.h" | 
 |  | 
 | #include "common/using_std_string.h" | 
 | #include "google_breakpad/common/breakpad_types.h" | 
 | #include "google_breakpad/processor/memory_region.h" | 
 | #include "processor/logging.h" | 
 |  | 
 |  | 
 | namespace { | 
 |  | 
 |  | 
 | using std::map; | 
 | using google_breakpad::MemoryRegion; | 
 | using google_breakpad::PostfixEvaluator; | 
 |  | 
 |  | 
 | // FakeMemoryRegion is used to test PostfixEvaluator's dereference (^) | 
 | // operator.  The result of dereferencing a value is one greater than | 
 | // the value. | 
 | class FakeMemoryRegion : public MemoryRegion { | 
 |  public: | 
 |   virtual uint64_t GetBase() const { return 0; } | 
 |   virtual uint32_t GetSize() const { return 0; } | 
 |   virtual bool GetMemoryAtAddress(uint64_t address, uint8_t  *value) const { | 
 |     *value = address + 1; | 
 |     return true; | 
 |   } | 
 |   virtual bool GetMemoryAtAddress(uint64_t address, uint16_t *value) const { | 
 |     *value = address + 1; | 
 |     return true; | 
 |   } | 
 |   virtual bool GetMemoryAtAddress(uint64_t address, uint32_t *value) const { | 
 |     *value = address + 1; | 
 |     return true; | 
 |   } | 
 |   virtual bool GetMemoryAtAddress(uint64_t address, uint64_t *value) const { | 
 |     *value = address + 1; | 
 |     return true; | 
 |   } | 
 |   virtual void Print() const { | 
 |     assert(false); | 
 |   } | 
 | }; | 
 |  | 
 |  | 
 | struct EvaluateTest { | 
 |   // Expression passed to PostfixEvaluator::Evaluate. | 
 |   const string expression; | 
 |  | 
 |   // True if the expression is expected to be evaluable, false if evaluation | 
 |   // is expected to fail. | 
 |   bool evaluable; | 
 | }; | 
 |  | 
 |  | 
 | struct EvaluateTestSet { | 
 |   // The dictionary used for all tests in the set. | 
 |   PostfixEvaluator<unsigned int>::DictionaryType *dictionary; | 
 |  | 
 |   // The list of tests. | 
 |   const EvaluateTest *evaluate_tests; | 
 |  | 
 |   // The number of tests. | 
 |   unsigned int evaluate_test_count; | 
 |  | 
 |   // Identifiers and their expected values upon completion of the Evaluate | 
 |   // tests in the set. | 
 |   map<string, unsigned int> *validate_data; | 
 | }; | 
 |  | 
 |  | 
 | struct EvaluateForValueTest { | 
 |   // Expression passed to PostfixEvaluator::Evaluate. | 
 |   const string expression; | 
 |    | 
 |   // True if the expression is expected to be evaluable, false if evaluation | 
 |   // is expected to fail. | 
 |   bool evaluable; | 
 |  | 
 |   // If evaluable, the value we expect it to yield. | 
 |   unsigned int value; | 
 | }; | 
 |  | 
 | static bool RunTests() { | 
 |   // The first test set checks the basic operations and failure modes. | 
 |   PostfixEvaluator<unsigned int>::DictionaryType dictionary_0; | 
 |   const EvaluateTest evaluate_tests_0[] = { | 
 |     { "$rAdd 2 2 + =",     true },   // $rAdd = 2 + 2 = 4 | 
 |     { "$rAdd $rAdd 2 + =", true },   // $rAdd = $rAdd + 2 = 6 | 
 |     { "$rAdd 2 $rAdd + =", true },   // $rAdd = 2 + $rAdd = 8 | 
 |     { "99",                false },  // put some junk on the stack... | 
 |     { "$rAdd2 2 2 + =",    true },   // ...and make sure things still work | 
 |     { "$rAdd2\t2\n2 + =",  true },   // same but with different whitespace | 
 |     { "$rAdd2 2 2 + = ",   true },   // trailing whitespace | 
 |     { " $rAdd2 2 2 + =",   true },   // leading whitespace | 
 |     { "$rAdd2  2 2 +   =", true },   // extra whitespace | 
 |     { "$T0 2 = +",         false },  // too few operands for add | 
 |     { "2 + =",             false },  // too few operands for add | 
 |     { "2 +",               false },  // too few operands for add | 
 |     { "+",                 false },  // too few operands for add | 
 |     { "^",                 false },  // too few operands for dereference | 
 |     { "=",                 false },  // too few operands for assignment | 
 |     { "2 =",               false },  // too few operands for assignment | 
 |     { "2 2 + =",           false },  // too few operands for assignment | 
 |     { "2 2 =",             false },  // can't assign into a literal | 
 |     { "k 2 =",             false },  // can't assign into a constant | 
 |     { "2",                 false },  // leftover data on stack | 
 |     { "2 2 +",             false },  // leftover data on stack | 
 |     { "$rAdd",             false },  // leftover data on stack | 
 |     { "0 $T1 0 0 + =",     false },  // leftover data on stack | 
 |     { "$T2 $T2 2 + =",     false },  // can't operate on an undefined value | 
 |     { "$rMul 9 6 * =",     true },   // $rMul = 9 * 6 = 54 | 
 |     { "$rSub 9 6 - =",     true },   // $rSub = 9 - 6 = 3 | 
 |     { "$rDivQ 9 6 / =",    true },   // $rDivQ = 9 / 6 = 1 | 
 |     { "$rDivM 9 6 % =",    true },   // $rDivM = 9 % 6 = 3 | 
 |     { "$rDeref 9 ^ =",     true },   // $rDeref = ^9 = 10 (FakeMemoryRegion) | 
 |     { "$rAlign 36 8 @ =",  true },   // $rAlign = 36 @ 8 | 
 |     { "$rAdd3 2 2 + =$rMul2 9 6 * =", true } // smashed-equals tokenization | 
 |   }; | 
 |   map<string, unsigned int> validate_data_0; | 
 |   validate_data_0["$rAdd"]   = 8; | 
 |   validate_data_0["$rAdd2"]  = 4; | 
 |   validate_data_0["$rSub"]   = 3; | 
 |   validate_data_0["$rMul"]   = 54; | 
 |   validate_data_0["$rDivQ"]  = 1; | 
 |   validate_data_0["$rDivM"]  = 3; | 
 |   validate_data_0["$rDeref"] = 10; | 
 |   validate_data_0["$rAlign"] = 32; | 
 |   validate_data_0["$rAdd3"]  = 4; | 
 |   validate_data_0["$rMul2"]  = 54; | 
 |  | 
 |   // The second test set simulates a couple of MSVC program strings. | 
 |   // The data is fudged a little bit because the tests use FakeMemoryRegion | 
 |   // instead of a real stack snapshot, but the program strings are real and | 
 |   // the implementation doesn't know or care that the data is not real. | 
 |   PostfixEvaluator<unsigned int>::DictionaryType dictionary_1; | 
 |   dictionary_1["$ebp"] = 0xbfff0010; | 
 |   dictionary_1["$eip"] = 0x10000000; | 
 |   dictionary_1["$esp"] = 0xbfff0000; | 
 |   dictionary_1[".cbSavedRegs"] = 4; | 
 |   dictionary_1[".cbParams"] = 4; | 
 |   dictionary_1[".raSearchStart"] = 0xbfff0020; | 
 |   const EvaluateTest evaluate_tests_1[] = { | 
 |     { "$T0 $ebp = $eip $T0 4 + ^ = $ebp $T0 ^ = $esp $T0 8 + = " | 
 |       "$L $T0 .cbSavedRegs - = $P $T0 8 + .cbParams + =", true }, | 
 |     // Intermediate state: $T0  = 0xbfff0010, $eip = 0xbfff0015, | 
 |     //                     $ebp = 0xbfff0011, $esp = 0xbfff0018, | 
 |     //                     $L   = 0xbfff000c, $P   = 0xbfff001c | 
 |     { "$T0 $ebp = $eip $T0 4 + ^ = $ebp $T0 ^ = $esp $T0 8 + = " | 
 |       "$L $T0 .cbSavedRegs - = $P $T0 8 + .cbParams + = $ebx $T0 28 - ^ =", | 
 |       true }, | 
 |     // Intermediate state: $T0  = 0xbfff0011, $eip = 0xbfff0016, | 
 |     //                     $ebp = 0xbfff0012, $esp = 0xbfff0019, | 
 |     //                     $L   = 0xbfff000d, $P   = 0xbfff001d, | 
 |     //                     $ebx = 0xbffefff6 | 
 |     { "$T0 $ebp = $T2 $esp = $T1 .raSearchStart = $eip $T1 ^ = $ebp $T0 = " | 
 |       "$esp $T1 4 + = $L $T0 .cbSavedRegs - = $P $T1 4 + .cbParams + = " | 
 |       "$ebx $T0 28 - ^ =", | 
 |       true } | 
 |   }; | 
 |   map<string, unsigned int> validate_data_1; | 
 |   validate_data_1["$T0"]  = 0xbfff0012; | 
 |   validate_data_1["$T1"]  = 0xbfff0020; | 
 |   validate_data_1["$T2"]  = 0xbfff0019; | 
 |   validate_data_1["$eip"] = 0xbfff0021; | 
 |   validate_data_1["$ebp"] = 0xbfff0012; | 
 |   validate_data_1["$esp"] = 0xbfff0024; | 
 |   validate_data_1["$L"]   = 0xbfff000e; | 
 |   validate_data_1["$P"]   = 0xbfff0028; | 
 |   validate_data_1["$ebx"] = 0xbffefff7; | 
 |   validate_data_1[".cbSavedRegs"] = 4; | 
 |   validate_data_1[".cbParams"] = 4; | 
 |  | 
 |   EvaluateTestSet evaluate_test_sets[] = { | 
 |     { &dictionary_0, evaluate_tests_0, | 
 |           sizeof(evaluate_tests_0) / sizeof(EvaluateTest), &validate_data_0 }, | 
 |     { &dictionary_1, evaluate_tests_1, | 
 |           sizeof(evaluate_tests_1) / sizeof(EvaluateTest), &validate_data_1 }, | 
 |   }; | 
 |  | 
 |   unsigned int evaluate_test_set_count = sizeof(evaluate_test_sets) / | 
 |                                          sizeof(EvaluateTestSet); | 
 |  | 
 |   FakeMemoryRegion fake_memory; | 
 |   PostfixEvaluator<unsigned int> postfix_evaluator = | 
 |       PostfixEvaluator<unsigned int>(NULL, &fake_memory); | 
 |  | 
 |   for (unsigned int evaluate_test_set_index = 0; | 
 |        evaluate_test_set_index < evaluate_test_set_count; | 
 |        ++evaluate_test_set_index) { | 
 |     EvaluateTestSet *evaluate_test_set = | 
 |         &evaluate_test_sets[evaluate_test_set_index]; | 
 |     const EvaluateTest *evaluate_tests = evaluate_test_set->evaluate_tests; | 
 |     unsigned int evaluate_test_count = evaluate_test_set->evaluate_test_count; | 
 |  | 
 |     // The same dictionary will be used for each test in the set.  Earlier | 
 |     // tests can affect the state of the dictionary for later tests. | 
 |     postfix_evaluator.set_dictionary(evaluate_test_set->dictionary); | 
 |  | 
 |     // Use a new validity dictionary for each test set. | 
 |     PostfixEvaluator<unsigned int>::DictionaryValidityType assigned; | 
 |  | 
 |     for (unsigned int evaluate_test_index = 0; | 
 |          evaluate_test_index < evaluate_test_count; | 
 |          ++evaluate_test_index) { | 
 |       const EvaluateTest *evaluate_test = &evaluate_tests[evaluate_test_index]; | 
 |  | 
 |       // Do the test. | 
 |       bool result = postfix_evaluator.Evaluate(evaluate_test->expression, | 
 |                                                &assigned); | 
 |       if (result != evaluate_test->evaluable) { | 
 |         fprintf(stderr, "FAIL: evaluate set %d/%d, test %d/%d, " | 
 |                         "expression \"%s\", expected %s, observed %s\n", | 
 |                 evaluate_test_set_index, evaluate_test_set_count, | 
 |                 evaluate_test_index, evaluate_test_count, | 
 |                 evaluate_test->expression.c_str(), | 
 |                 evaluate_test->evaluable ? "evaluable" : "not evaluable", | 
 |                 result ? "evaluted" : "not evaluated"); | 
 |         return false; | 
 |       } | 
 |     } | 
 |  | 
 |     // Validate the results. | 
 |     for (map<string, unsigned int>::const_iterator validate_iterator = | 
 |             evaluate_test_set->validate_data->begin(); | 
 |         validate_iterator != evaluate_test_set->validate_data->end(); | 
 |         ++validate_iterator) { | 
 |       const string identifier = validate_iterator->first; | 
 |       unsigned int expected_value = validate_iterator->second; | 
 |  | 
 |       map<string, unsigned int>::const_iterator dictionary_iterator = | 
 |           evaluate_test_set->dictionary->find(identifier); | 
 |  | 
 |       // The identifier must exist in the dictionary. | 
 |       if (dictionary_iterator == evaluate_test_set->dictionary->end()) { | 
 |         fprintf(stderr, "FAIL: evaluate test set %d/%d, " | 
 |                         "validate identifier \"%s\", " | 
 |                         "expected %d, observed not found\n", | 
 |                 evaluate_test_set_index, evaluate_test_set_count, | 
 |                 identifier.c_str(), expected_value); | 
 |         return false; | 
 |       } | 
 |  | 
 |       // The value in the dictionary must be the same as the expected value. | 
 |       unsigned int observed_value = dictionary_iterator->second; | 
 |       if (expected_value != observed_value) { | 
 |         fprintf(stderr, "FAIL: evaluate test set %d/%d, " | 
 |                         "validate identifier \"%s\", " | 
 |                         "expected %d, observed %d\n", | 
 |                 evaluate_test_set_index, evaluate_test_set_count, | 
 |                 identifier.c_str(), expected_value, observed_value); | 
 |         return false; | 
 |       } | 
 |  | 
 |       // The value must be set in the "assigned" dictionary if it was a | 
 |       // variable.  It must not have been assigned if it was a constant. | 
 |       bool expected_assigned = identifier[0] == '$'; | 
 |       bool observed_assigned = false; | 
 |       PostfixEvaluator<unsigned int>::DictionaryValidityType::const_iterator | 
 |           iterator_assigned = assigned.find(identifier); | 
 |       if (iterator_assigned != assigned.end()) { | 
 |         observed_assigned = iterator_assigned->second; | 
 |       } | 
 |       if (expected_assigned != observed_assigned) { | 
 |         fprintf(stderr, "FAIL: evaluate test set %d/%d, " | 
 |                         "validate assignment of \"%s\", " | 
 |                         "expected %d, observed %d\n", | 
 |                 evaluate_test_set_index, evaluate_test_set_count, | 
 |                 identifier.c_str(), expected_assigned, observed_assigned); | 
 |         return false; | 
 |       } | 
 |     } | 
 |   } | 
 |  | 
 |   // EvaluateForValue tests. | 
 |   PostfixEvaluator<unsigned int>::DictionaryType dictionary_2; | 
 |   dictionary_2["$ebp"] = 0xbfff0010; | 
 |   dictionary_2["$eip"] = 0x10000000; | 
 |   dictionary_2["$esp"] = 0xbfff0000; | 
 |   dictionary_2[".cbSavedRegs"] = 4; | 
 |   dictionary_2[".cbParams"] = 4; | 
 |   dictionary_2[".raSearchStart"] = 0xbfff0020; | 
 |   const EvaluateForValueTest evaluate_for_value_tests_2[] = { | 
 |     { "28907223",               true,  28907223 },      // simple constant | 
 |     { "89854293 40010015 +",    true,  89854293 + 40010015 }, // arithmetic | 
 |     { "-870245 8769343 +",      true,  7899098 },       // negative constants | 
 |     { "$ebp $esp - $eip +",     true,  0x10000010 },    // variable references | 
 |     { "18929794 34015074",      false, 0 },             // too many values | 
 |     { "$ebp $ebp 4 - =",        false, 0 },             // too few values | 
 |     { "$new $eip = $new",       true,  0x10000000 },    // make new variable | 
 |     { "$new 4 +",               true,  0x10000004 },    // see prior assignments | 
 |     { ".cfa 42 = 10",           false, 0 }              // can't set constants | 
 |   }; | 
 |   const int evaluate_for_value_tests_2_size | 
 |       = (sizeof (evaluate_for_value_tests_2) | 
 |          / sizeof (evaluate_for_value_tests_2[0])); | 
 |   map<string, unsigned int> validate_data_2; | 
 |   validate_data_2["$eip"] = 0x10000000; | 
 |   validate_data_2["$ebp"] = 0xbfff000c; | 
 |   validate_data_2["$esp"] = 0xbfff0000; | 
 |   validate_data_2["$new"] = 0x10000000; | 
 |   validate_data_2[".cbSavedRegs"] = 4; | 
 |   validate_data_2[".cbParams"] = 4; | 
 |   validate_data_2[".raSearchStart"] = 0xbfff0020; | 
 |  | 
 |   postfix_evaluator.set_dictionary(&dictionary_2); | 
 |   for (int i = 0; i < evaluate_for_value_tests_2_size; i++) { | 
 |     const EvaluateForValueTest *test = &evaluate_for_value_tests_2[i]; | 
 |     unsigned int result; | 
 |     if (postfix_evaluator.EvaluateForValue(test->expression, &result) | 
 |         != test->evaluable) { | 
 |       fprintf(stderr, "FAIL: evaluate for value test %d, " | 
 |               "expected evaluation to %s, but it %s\n", | 
 |               i, test->evaluable ? "succeed" : "fail", | 
 |               test->evaluable ? "failed" : "succeeded"); | 
 |       return false; | 
 |     } | 
 |     if (test->evaluable && result != test->value) { | 
 |       fprintf(stderr, "FAIL: evaluate for value test %d, " | 
 |               "expected value to be 0x%x, but it was 0x%x\n", | 
 |               i, test->value, result); | 
 |       return false; | 
 |     } | 
 |   } | 
 |  | 
 |   for (map<string, unsigned int>::iterator v = validate_data_2.begin(); | 
 |        v != validate_data_2.end(); v++) { | 
 |     map<string, unsigned int>::iterator a = dictionary_2.find(v->first); | 
 |     if (a == dictionary_2.end()) { | 
 |       fprintf(stderr, "FAIL: evaluate for value dictionary check: " | 
 |               "expected dict[\"%s\"] to be 0x%x, but it was unset\n", | 
 |               v->first.c_str(), v->second); | 
 |       return false; | 
 |     } else if (a->second != v->second) { | 
 |       fprintf(stderr, "FAIL: evaluate for value dictionary check: " | 
 |               "expected dict[\"%s\"] to be 0x%x, but it was 0x%x\n", | 
 |               v->first.c_str(), v->second, a->second); | 
 |       return false; | 
 |     }  | 
 |     dictionary_2.erase(a); | 
 |   } | 
 |    | 
 |   map<string, unsigned int>::iterator remaining = dictionary_2.begin(); | 
 |   if (remaining != dictionary_2.end()) { | 
 |     fprintf(stderr, "FAIL: evaluation of test expressions put unexpected " | 
 |             "values in dictionary:\n"); | 
 |     for (; remaining != dictionary_2.end(); remaining++) | 
 |       fprintf(stderr, "    dict[\"%s\"] == 0x%x\n", | 
 |               remaining->first.c_str(), remaining->second); | 
 |     return false; | 
 |   } | 
 |  | 
 |   return true; | 
 | } | 
 |  | 
 |  | 
 | }  // namespace | 
 |  | 
 |  | 
 | int main(int argc, char **argv) { | 
 |   BPLOG_INIT(&argc, &argv); | 
 |  | 
 |   return RunTests() ? 0 : 1; | 
 | } |