New combined decompiler testing framework

This commit is contained in:
caheckman 2021-01-27 14:55:00 -05:00
parent 5d7a7c5291
commit ab76cc6095
32 changed files with 1788 additions and 791 deletions

View file

@ -56,6 +56,9 @@ LNK=
# Source files
ALL_SOURCE= $(wildcard *.cc)
ALL_NAMES=$(subst .cc,,$(ALL_SOURCE))
UNITTEST_SOURCE= $(wildcard ../unittests/*.cc)
UNITTEST_NAMES=$(subst .cc,,$(UNITTEST_SOURCE))
UNITTEST_STRIP=$(subst ../unittests/,,$(UNITTEST_NAMES))
COREEXT_SOURCE= $(wildcard coreext_*.cc)
COREEXT_NAMES=$(subst .cc,,$(COREEXT_SOURCE))
@ -91,7 +94,7 @@ GHIDRA= ghidra_arch inject_ghidra ghidra_translate loadimage_ghidra \
# Additional files specific to the sleigh compiler
SLACOMP=slgh_compile slghparse slghscan
# Additional special files that should not be considered part of the library
SPECIAL=consolemain sleighexample test
SPECIAL=consolemain sleighexample test testfunction
# Any additional modules for the command line decompiler
EXTRA= $(filter-out $(CORE) $(DECCORE) $(SLEIGH) $(GHIDRA) $(SLACOMP) $(SPECIAL),$(ALL_NAMES))
@ -114,8 +117,8 @@ COMMANDLINE_NAMES=$(CORE) $(DECCORE) $(EXTRA) $(SLEIGH) consolemain
COMMANDLINE_DEBUG=-DCPUI_DEBUG -D__TERMINAL__
COMMANDLINE_OPT=-D__TERMINAL__
TEST_NAMES=$(CORE) $(DECCORE) $(SLEIGH) test
TEST_DEBUG=-D__TERMINAL__ -g -O0
TEST_NAMES=$(CORE) $(DECCORE) $(SLEIGH) $(EXTRA) testfunction test
TEST_DEBUG=-D__TERMINAL__
GHIDRA_NAMES=$(CORE) $(DECCORE) $(GHIDRA)
GHIDRA_NAMES_DBG=$(GHIDRA_NAMES) callgraph ifacedecomp ifaceterm interface
@ -136,7 +139,7 @@ LIBDECOMP_NAMES=$(CORE) $(DECCORE) $(EXTRA) $(SLEIGH)
# object file macros
COMMANDLINE_DBG_OBJS=$(COMMANDLINE_NAMES:%=com_dbg/%.o)
COMMANDLINE_OPT_OBJS=$(COMMANDLINE_NAMES:%=com_opt/%.o)
TEST_DEBUG_OBJS=$(TEST_NAMES:%=test_dbg/%.o)
TEST_DEBUG_OBJS=$(TEST_NAMES:%=test_dbg/%.o) $(UNITTEST_STRIP:%=test_dbg/%.o)
GHIDRA_DBG_OBJS=$(GHIDRA_NAMES_DBG:%=ghi_dbg/%.o)
GHIDRA_OPT_OBJS=$(GHIDRA_NAMES:%=ghi_opt/%.o)
SLEIGH_DBG_OBJS=$(SLEIGH_NAMES:%=sla_dbg/%.o)
@ -214,7 +217,9 @@ com_dbg/%.o: %.cc
com_opt/%.o: %.cc
$(CXX) $(ARCH_TYPE) -c $(OPT_CXXFLAGS) $(ADDITIONAL_FLAGS) $(COMMANDLINE_OPT) $< -o $@
test_dbg/%.o: %.cc
$(CXX) $(ARCH_TYPE) -c $(OPT_CXXFLAGS) $(ADDITIONAL_FLAGS) $(TEST_DEBUG) $< -o $@
$(CXX) $(ARCH_TYPE) -c $(DBG_CXXFLAGS) $(ADDITIONAL_FLAGS) $(TEST_DEBUG) $< -o $@
test_dbg/%.o: ../unittests/%.cc
$(CXX) -I. $(ARCH_TYPE) -c $(DBG_CXXFLAGS) $(ADDITIONAL_FLAGS) $(TEST_DEBUG) $< -o $@
ghi_dbg/%.o: %.cc
$(CXX) $(ARCH_TYPE) -c $(DBG_CXXFLAGS) $(ADDITIONAL_FLAGS) $(GHIDRA_DEBUG) $< -o $@
ghi_opt/%.o: %.cc
@ -248,7 +253,7 @@ decomp_opt: $(COMMANDLINE_OPT_OBJS)
$(CXX) $(OPT_CXXFLAGS) $(ARCH_TYPE) -o decomp_opt $(COMMANDLINE_OPT_OBJS) $(BFDLIB) $(LNK)
ghidra_test_dbg: $(TEST_DEBUG_OBJS)
$(CXX) $(OPT_CXXFLAGS) $(ARCH_TYPE) -o ghidra_test_dbg $(TEST_DEBUG_OBJS) $(BFDLIB) $(LNK)
$(CXX) $(DBG_CXXFLAGS) $(ARCH_TYPE) -o ghidra_test_dbg $(TEST_DEBUG_OBJS) $(BFDLIB) $(LNK)
test: ghidra_test_dbg
./ghidra_test_dbg
@ -339,10 +344,10 @@ com_opt/depend: $(COMMANDLINE_NAMES:%=%.cc)
sed 's,\(.*\)\.o[ :]*,com_opt/\1.o $@ : ,g' < $@.$$$$ > $@; \
rm -f $@.$$$$
test_dbg/depend: $(TEST_NAMES:%=%.cc)
test_dbg/depend: $(TEST_NAMES:%=%.cc) $(UNITTEST_NAMES:%=%.cc)
mkdir -p test_dbg
@set -e; rm -f $@; \
$(CXX) -MM $(TEST_DEBUG) $^ > $@.$$$$; \
$(CXX) -I. -MM $(TEST_DEBUG) $^ > $@.$$$$; \
sed 's,\(.*\)\.o[ :]*,test_dbg/\1.o $@ : ,g' < $@.$$$$ > $@; \
rm -f $@.$$$$

View file

@ -64,6 +64,20 @@ ArchitectureCapability *ArchitectureCapability::findCapability(Document *doc)
return (ArchitectureCapability *)0;
}
/// Return the ArchitectureCapability object with the matching name
/// \param name is the name to match
/// \return the ArchitectureCapability or null if no match is found
ArchitectureCapability *ArchitectureCapability::getCapability(const string &name)
{
for(int4 i=0;i<thelist.size();++i) {
ArchitectureCapability *res = thelist[i];
if (res->getName() == name)
return res;
}
return (ArchitectureCapability *)0;
}
/// Modify order that extensions are searched, to effect which gets a chance
/// to run first.
/// Right now all we need to do is make sure the raw architecture comes last

View file

@ -104,6 +104,7 @@ public:
static ArchitectureCapability *findCapability(const string &filename); ///< Find an extension to process a file
static ArchitectureCapability *findCapability(Document *doc); ///< Find an extension to process an XML document
static ArchitectureCapability *getCapability(const string &name); ///< Get a capability by name
static void sortCapabilities(void); ///< Sort extensions
static uint4 getMajorVersion(void) { return majorversion; } ///< Get \e major decompiler version
static uint4 getMinorVersion(void) { return minorversion; } ///< Get \e minor decompiler version

View file

@ -13,7 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/// \file ifaceterm.hh
/// \brief Add some terminal capabilities to the command-line interface (IfaceStatus)

View file

@ -4,16 +4,15 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/// \file interface.hh
/// \brief Classes and utilities for a \e generic command-line interface

View file

@ -13,7 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/// \file sleigh.hh
/// \brief Classes and utilities for the main SLEIGH engine

View file

@ -13,470 +13,81 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/// \file test.cc
/// \brief Unit tests for Ghidra C++ components.
#include "float.hh"
#include "opbehavior.hh"
#include "test.hh"
#include "testfunction.hh"
#include <cmath>
#include <cstdint>
#include <cstring>
vector<UnitTest *> UnitTest::tests;
#include <limits>
#include <vector>
/// Run all the tests unless a non-empty set of names is passed in.
/// In which case, only the named tests in the set are run.
/// \param testnames is the set of names
void UnitTest::run(set<string> &testNames)
// utility functions
float floatFromRawBits(uintb e) {
float f;
memcpy(&f, &e, 4);
return f;
}
{
int total = 0;
int passed = 0;
uintb floatToRawBits(float f) {
uintb result = 0;
memcpy(&result, &f, 4);
return result;
}
double doubleFromRawBits(uintb e) {
double f;
memcpy(&f, &e, 8);
return f;
}
uintb doubleToRawBits(double f) {
uintb result = 0;
memcpy(&result, &f, 8);
return result;
}
// macros to preserve call site
#define ASSERT_FLOAT_ENCODING(f) \
do { \
FloatFormat format(4); \
\
uintb true_encoding = floatToRawBits(f); \
uintb encoding = format.getEncoding(f); \
\
ASSERT_EQUALS(true_encoding, encoding); \
} while (0);
#define ASSERT_DOUBLE_ENCODING(f) \
do { \
FloatFormat format(8); \
\
uintb true_encoding = doubleToRawBits(f); \
uintb encoding = format.getEncoding(f); \
\
ASSERT_EQUALS(true_encoding, encoding); \
} while (0);
//// FloatFormat tests
static std::vector<float> float_test_values{
-0.0f,
+0.0f,
-1.0f,
+1.0f,
-1.234f,
+1.234f,
-std::numeric_limits<float>::denorm_min(),
std::numeric_limits<float>::denorm_min(),
std::numeric_limits<float>::min() - std::numeric_limits<float>::denorm_min(),
std::numeric_limits<float>::min(),
std::numeric_limits<float>::min() + std::numeric_limits<float>::denorm_min(),
-std::numeric_limits<float>::min() + std::numeric_limits<float>::denorm_min(),
-std::numeric_limits<float>::min(),
-std::numeric_limits<float>::min() - std::numeric_limits<float>::denorm_min(),
std::numeric_limits<float>::max(),
std::numeric_limits<float>::quiet_NaN(),
-std::numeric_limits<float>::infinity(),
std::numeric_limits<float>::infinity()
};
static std::vector<int> int_test_values = {
0, -1, 1, 1234, -1234, std::numeric_limits<int>::min(), std::numeric_limits<int>::max()
};
TEST(float_encoding_normal) {
ASSERT_FLOAT_ENCODING(1.234);
ASSERT_FLOAT_ENCODING(-1.234);
}
TEST(double_encoding_normal) {
ASSERT_DOUBLE_ENCODING(1.234);
ASSERT_DOUBLE_ENCODING(-1.234);
}
TEST(float_encoding_nan) {
ASSERT_FLOAT_ENCODING(std::numeric_limits<float>::quiet_NaN());
ASSERT_FLOAT_ENCODING(-std::numeric_limits<float>::quiet_NaN());
}
TEST(double_encoding_nan) {
ASSERT_DOUBLE_ENCODING(std::numeric_limits<double>::quiet_NaN());
ASSERT_DOUBLE_ENCODING(-std::numeric_limits<double>::quiet_NaN());
}
TEST(float_encoding_subnormal) {
ASSERT_FLOAT_ENCODING(std::numeric_limits<float>::denorm_min());
ASSERT_FLOAT_ENCODING(-std::numeric_limits<float>::denorm_min());
}
TEST(double_encoding_subnormal) {
ASSERT_DOUBLE_ENCODING(std::numeric_limits<double>::denorm_min());
ASSERT_DOUBLE_ENCODING(-std::numeric_limits<double>::denorm_min());
}
TEST(float_encoding_min_normal) {
ASSERT_FLOAT_ENCODING(std::numeric_limits<float>::min());
ASSERT_FLOAT_ENCODING(-std::numeric_limits<float>::min());
}
TEST(double_encoding_min_normal) {
ASSERT_DOUBLE_ENCODING(std::numeric_limits<double>::min());
ASSERT_DOUBLE_ENCODING(-std::numeric_limits<double>::min());
}
TEST(float_encoding_infinity) {
ASSERT_FLOAT_ENCODING(std::numeric_limits<float>::infinity());
ASSERT_FLOAT_ENCODING(-std::numeric_limits<float>::infinity());
}
TEST(double_encoding_infinity) {
ASSERT_DOUBLE_ENCODING(std::numeric_limits<double>::infinity());
ASSERT_DOUBLE_ENCODING(-std::numeric_limits<double>::infinity());
}
TEST(float_midpoint_rounding) {
FloatFormat ff(4);
// IEEE754 recommends "round to nearest even" for binary formats, like single and double
// precision floating point. It rounds to the nearest integer (significand) when unambiguous,
// and to the nearest even on the midpoint.
// There are 52 bits of significand in a double and 23 in a float.
// Below we construct a sequence of double precision values to demonstrate each case
// in rounding,
// d0 - zeros in low 29 bits, round down
// d1 - on the rounding midpoint with integer even integer part, round down
// d2 - just above the midpoint, round up
double d0 = doubleFromRawBits(0x4010000000000000L);
double d1 = doubleFromRawBits(0x4010000010000000L);
double d2 = doubleFromRawBits(0x4010000010000001L);
// d3 - zeros in low 29 bits, round down
// d4 - on the rounding midpoint with integer part odd, round up
// d5 - just above the midpoint, round up
double d3 = doubleFromRawBits(0x4010000020000000L);
double d4 = doubleFromRawBits(0x4010000030000000L);
double d5 = doubleFromRawBits(0x4010000030000001L);
float f0 = (float)d0;
float f1 = (float)d1;
float f2 = (float)d2;
float f3 = (float)d3;
float f4 = (float)d4;
float f5 = (float)d5;
uintb e0 = ff.getEncoding(d0);
uintb e1 = ff.getEncoding(d1);
uintb e2 = ff.getEncoding(d2);
uintb e3 = ff.getEncoding(d3);
uintb e4 = ff.getEncoding(d4);
uintb e5 = ff.getEncoding(d5);
ASSERT_EQUALS(floatToRawBits(f0), e0);
ASSERT_EQUALS(floatToRawBits(f1), e1);
ASSERT_EQUALS(floatToRawBits(f2), e2);
ASSERT_EQUALS(floatToRawBits(f3), e3);
ASSERT_EQUALS(floatToRawBits(f4), e4);
ASSERT_EQUALS(floatToRawBits(f5), e5);
ASSERT_EQUALS(e0, e1);
ASSERT_NOT_EQUALS(e1, e2);
ASSERT_NOT_EQUALS(e3, e4);
ASSERT_EQUALS(e4, e5);
}
// op tests
// generated
TEST(float_opNan) {
FloatFormat format(4);
for(float f:float_test_values) {
uintb true_result = isnan(f);
uintb encoding = format.getEncoding(f);
uintb result = format.opNan(encoding);
ASSERT_EQUALS(true_result, result);
for(auto &t : UnitTest::tests) {
if (testNames.size() > 0 && testNames.find(t->name) == testNames.end()) {
continue;
}
}
TEST(float_opNeg) {
FloatFormat format(4);
for(float f:float_test_values) {
uintb true_result = floatToRawBits(-f);
uintb encoding = format.getEncoding(f);
uintb result = format.opNeg(encoding);
ASSERT_EQUALS(true_result, result);
std::cerr << "testing : " << t->name << " ..." << std::endl;
++total;
try {
t->func();
++passed;
std::cerr << " passed." << std::endl;
} catch(...) {
}
}
std::cerr << "==============================" << std::endl;
std::cerr << passed << "/" << total << " tests passed." << std::endl;
}
int main(int argc, char **argv) {
bool runUnitTests = true;
bool runDataTests = true;
TEST(float_opAbs) {
FloatFormat format(4);
for(float f:float_test_values) {
uintb true_result = floatToRawBits(abs(f));
uintb encoding = format.getEncoding(f);
uintb result = format.opAbs(encoding);
ASSERT_EQUALS(true_result, result);
argc -= 1;
argv += 1;
set<string> unitTestNames;
set<string> dataTestNames;
string dirname("../datatests");
if (argc > 0) {
string command(argv[0]);
if (command == "-path") {
dirname = argv[1];
runDataTests = true;
argv += 2;
argc -= 2;
}
}
TEST(float_opSqrt) {
FloatFormat format(4);
for(float f:float_test_values) {
uintb true_result = floatToRawBits(sqrtf(f));
uintb encoding = format.getEncoding(f);
uintb result = format.opSqrt(encoding);
ASSERT_EQUALS(true_result, result);
}
if (argc > 0) {
string command(argv[0]);
if (command == "unittests") {
runUnitTests = true;
runDataTests = false; // Run only unit tests
unitTestNames.insert(argv + 1,argv + argc);
}
}
TEST(float_opCeil) {
FloatFormat format(4);
for(float f:float_test_values) {
uintb true_result = floatToRawBits(ceilf(f));
uintb encoding = format.getEncoding(f);
uintb result = format.opCeil(encoding);
ASSERT_EQUALS(true_result, result);
else if (command == "datatests") {
runUnitTests = false; // Run only data-tests
runDataTests = true;
dataTestNames.insert(argv + 1,argv + argc);
}
}
TEST(float_opFloor) {
FloatFormat format(4);
for(float f:float_test_values) {
uintb true_result = floatToRawBits(floorf(f));
uintb encoding = format.getEncoding(f);
uintb result = format.opFloor(encoding);
ASSERT_EQUALS(true_result, result);
else {
cout << "USAGE: ghidra_test [-path <datatestdir>] [[unittests|datatests] [testname1 testname2 ...]]" << endl;
}
}
if (runUnitTests)
UnitTest::run(unitTestNames);
if (runDataTests) {
cout << endl << endl;
const char *sleighhomepath = getenv("SLEIGHHOME");
if (sleighhomepath != (const char *)0)
cout << "Using SLEIGHHOME=" << sleighhomepath << endl;
else
cout << "No SLEIGHHOME environment variable" << endl;
startDecompilerLibrary(sleighhomepath);
FunctionTestCollection::runTestCollections(dirname,dataTestNames);
}
}
TEST(float_opRound) {
FloatFormat format(4);
for(float f:float_test_values) {
uintb true_result = floatToRawBits(roundf(f));
uintb encoding = format.getEncoding(f);
uintb result = format.opRound(encoding);
ASSERT_EQUALS(true_result, result);
}
}
TEST(float_opInt2Float_size4) {
FloatFormat format(4);
for(int i:int_test_values) {
uintb true_result = floatToRawBits((float)i);
uintb result = format.opInt2Float(i, 4);
ASSERT_EQUALS(true_result, result);
}
}
// TODO other sized ints
TEST(float_to_double_opFloat2Float) {
FloatFormat format(4);
FloatFormat format8(8);
for(float f:float_test_values) {
uintb true_result = doubleToRawBits((double)f);
uintb encoding = format.getEncoding(f);
uintb result = format.opFloat2Float(encoding, format8);
ASSERT_EQUALS(true_result, result);
}
}
// TODO float2float going the other direction, double_to_float_opFloat2Float
TEST(float_opTrunc_to_int) {
FloatFormat format(4);
FloatFormat format8(8);
for(float f:float_test_values) {
// avoid undefined behavior
if((int64_t)f > std::numeric_limits<int>::max() || (int64_t)f < std::numeric_limits<int>::min())
continue;
uintb true_result = ((uintb)(int32_t)f) & 0xffffffff;
uintb encoding = format.getEncoding(f);
uintb result = format.opTrunc(encoding, 4);
ASSERT_EQUALS(true_result, result);
}
}
// TODO trunc to other sizes
TEST(float_opEqual) {
FloatFormat format(4);
for(float f1:float_test_values) {
uintb encoding1 = format.getEncoding(f1);
for(float f2:float_test_values) {
uintb true_result = (f1==f2);
uintb encoding2 = format.getEncoding(f2);
uintb result = format.opEqual(encoding1, encoding2);
ASSERT_EQUALS(true_result, result);
}
}
}
TEST(float_opNotEqual) {
FloatFormat format(4);
for(float f1:float_test_values) {
uintb encoding1 = format.getEncoding(f1);
for(float f2:float_test_values) {
uintb true_result = (f1!=f2);
uintb encoding2 = format.getEncoding(f2);
uintb result = format.opNotEqual(encoding1, encoding2);
ASSERT_EQUALS(true_result, result);
}
}
}
TEST(float_opLess) {
FloatFormat format(4);
for(float f1:float_test_values) {
uintb encoding1 = format.getEncoding(f1);
for(float f2:float_test_values) {
uintb true_result = (f1<f2);
uintb encoding2 = format.getEncoding(f2);
uintb result = format.opLess(encoding1, encoding2);
ASSERT_EQUALS(true_result, result);
}
}
}
TEST(float_opLessEqual) {
FloatFormat format(4);
for(float f1:float_test_values) {
uintb encoding1 = format.getEncoding(f1);
for(float f2:float_test_values) {
uintb true_result = (f1<=f2);
uintb encoding2 = format.getEncoding(f2);
uintb result = format.opLessEqual(encoding1, encoding2);
ASSERT_EQUALS(true_result, result);
}
}
}
TEST(float_opAdd) {
FloatFormat format(4);
for(float f1:float_test_values) {
uintb encoding1 = format.getEncoding(f1);
for(float f2:float_test_values) {
uintb true_result = floatToRawBits(f1+f2);
uintb encoding2 = format.getEncoding(f2);
uintb result = format.opAdd(encoding1, encoding2);
ASSERT_EQUALS(true_result, result);
}
}
}
TEST(float_opDiv) {
FloatFormat format(4);
for(float f1:float_test_values) {
uintb encoding1 = format.getEncoding(f1);
for(float f2:float_test_values) {
uintb true_result = floatToRawBits(f1/f2);
uintb encoding2 = format.getEncoding(f2);
uintb result = format.opDiv(encoding1, encoding2);
ASSERT_EQUALS(true_result, result);
}
}
}
TEST(float_opMult) {
FloatFormat format(4);
for(float f1:float_test_values) {
uintb encoding1 = format.getEncoding(f1);
for(float f2:float_test_values) {
uintb true_result = floatToRawBits(f1*f2);
uintb encoding2 = format.getEncoding(f2);
uintb result = format.opMult(encoding1, encoding2);
ASSERT_EQUALS(true_result, result);
}
}
}
TEST(float_opSub) {
FloatFormat format(4);
for(float f1:float_test_values) {
uintb encoding1 = format.getEncoding(f1);
for(float f2:float_test_values) {
uintb true_result = floatToRawBits(f1-f2);
uintb encoding2 = format.getEncoding(f2);
uintb result = format.opSub(encoding1, encoding2);
ASSERT_EQUALS(true_result, result);
}
}
}
// end generated

View file

@ -27,32 +27,40 @@
/// }
///
#include <cstdio>
#include <iostream>
#include <sstream>
#include <string>
#include <vector>
#include <set>
#include <string>
#include <iostream>
namespace {
struct Test;
typedef void (*testfunc_t)();
typedef void (*testfunc_t)();
std::vector<Test *> tests;
/// \brief Simple unit test class
///
/// The macro TEST instantiates this object with a name and function pointer.
/// The static run() method calls all the function pointers of all instantiated
/// objects.
struct UnitTest {
static std::vector<UnitTest *> tests; ///< The collection of test objects
std::string name; ///< Name of the test
testfunc_t func; ///< Call-back function executing the test
struct Test {
std::string name;
testfunc_t func;
/// \brief Constructor
///
/// \param name is the identifier for the test
/// \param func is a call-back function that executes the test
UnitTest(const std::string &name,testfunc_t func) :
name(name), func(func)
{
tests.push_back(this);
}
static void run(std::set<std::string> &testNames); ///< Run all the instantiated tests
};
Test(const std::string &name, testfunc_t func) : name(name), func(func) {
tests.push_back(this);
}
};
} // namespace
#define TEST(testname) \
void testname(); \
Test testname##_obj{ #testname, testname }; \
UnitTest testname##_obj{ #testname, testname }; \
void testname()
#define ASSERT(test) \
@ -80,26 +88,3 @@ namespace {
<< " != " << ssb.str() << "\"." << std::endl; \
throw 0; \
}
int main(int argc, char **argv) {
int total = 0;
int passed = 0;
std::set<std::string> testnames(argv + 1, argv + argc);
for (auto &t : tests) {
if(testnames.size()>0 && testnames.find(t->name)==testnames.end()) {
continue;
}
std::cerr << "testing : " << t->name << " ..." << std::endl;
++total;
try {
t->func();
++passed;
std::cerr << " passed." << std::endl;
} catch (...) {
}
}
std::cerr << "==============================" << std::endl;
std::cerr << passed << "/" << total << " tests passed." << std::endl;
}

View file

@ -0,0 +1,345 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "testfunction.hh"
#include "filemanage.hh"
void FunctionTestProperty::startTest(void) const
{
count = 0;
}
void FunctionTestProperty::processLine(const string &line) const
{
if (regex_search(line,pattern))
count += 1;
}
bool FunctionTestProperty::endTest(void) const
{
return (count >= minimumMatch && count <= maximumMatch);
}
void FunctionTestProperty::restoreXml(const Element *el)
{
name = el->getAttributeValue("name");
istringstream s1(el->getAttributeValue("min"));
s1 >> minimumMatch;
istringstream s2(el->getAttributeValue("max"));
s2 >> maximumMatch;
pattern = regex(el->getContent());
}
void ConsoleCommands::readLine(string &line)
{
if (pos >= commands.size()) {
line.clear();
return;
}
line = commands[pos];
pos += 1;
}
ConsoleCommands::ConsoleCommands(void) :
IfaceStatus("> ", cout)
{
pos = 0;
IfaceCapability::registerAllCommands(this);
}
void ConsoleCommands::reset(void)
{
commands.clear();
pos = 0;
inerror = false;
done = false;
}
/// \param el is the root \<script> tag
void ConsoleCommands::restoreXml(const Element *el)
{
const List &list(el->getChildren());
List::const_iterator iter;
for(iter=list.begin();iter!=list.end();++iter) {
const Element *subel = *iter;
commands.push_back(subel->getContent());
}
pos = 0;
}
void FunctionTestCollection::clear(void)
{
dcp->clearArchitecture();
testList.clear();
console.reset();
}
/// Instantiate an Architecture object
void FunctionTestCollection::buildProgram(DocumentStorage &docStorage)
{
ArchitectureCapability *capa = ArchitectureCapability::getCapability("xml");
if (capa == (ArchitectureCapability *)0)
throw IfaceExecutionError("Missing XML architecture capability");
dcp->conf = capa->buildArchitecture("test", "", console.optr);
string errmsg;
bool iserror = false;
try {
dcp->conf->init(docStorage);
dcp->conf->readLoaderSymbols("::"); // Read in loader symbols
} catch(XmlError &err) {
errmsg = err.explain;
iserror = true;
} catch(LowlevelError &err) {
errmsg = err.explain;
iserror = true;
}
if (iserror)
throw IfaceExecutionError("Error during architecture initialization: " + errmsg);
}
/// Let each test initialize itself thru its startTest() method
void FunctionTestCollection::startTests(void) const
{
list<FunctionTestProperty>::const_iterator iter;
for(iter=testList.begin();iter!=testList.end();++iter) {
(*iter).startTest();
}
}
/// Each test gets a chance to process a line of output
/// \param line is the given line of output
void FunctionTestCollection::passLineToTests(const string &line) const
{
list<FunctionTestProperty>::const_iterator iter;
for(iter=testList.begin();iter!=testList.end();++iter) {
(*iter).processLine(line);
}
}
/// \brief Do the final evaluation of each test
///
/// This is called after each test has been fed all lines of output.
/// The result of each test is printed to the \e midStream, and then
/// failures are written to the lateStream in order to see a summary.
/// \param midStream is the stream write results to as the test is performed
/// \param lateStream collects failures to display as a summary
void FunctionTestCollection::evaluateTests(ostream &midStream,list<string> &lateStream) const
{
list<FunctionTestProperty>::const_iterator iter;
for(iter=testList.begin();iter!=testList.end();++iter) {
numTestsApplied += 1;
if ((*iter).endTest()) {
midStream << "Success -- " << (*iter).getName() << endl;
numTestsSucceeded += 1;
}
else {
midStream << "FAIL -- " << (*iter).getName() << endl;
lateStream.push_back((*iter).getName());
}
}
}
FunctionTestCollection::FunctionTestCollection(void)
{
dcp = (IfaceDecompData *)console.getData("decompile");
console.setErrorIsDone(true);
numTestsApplied = 0;
numTestsSucceeded = 0;
}
/// Load the architecture based on the discovered \<binaryimage> tag.
/// Collect the script commands and the specific tests.
/// \param filename is the XML file holding the test data
void FunctionTestCollection::loadTest(const string &filename)
{
fileName = filename;
DocumentStorage docStorage;
Document *doc = docStorage.openDocument(filename);
Element *el = doc->getRoot();
if (el->getName() == "decompilertest")
restoreXml(docStorage,el);
else if (el->getName() == "binaryimage")
restoreXmlOldForm(docStorage,el);
else
throw IfaceParseError("Test file " + filename + " has unrecognized XML tag: "+el->getName());
}
void FunctionTestCollection::restoreXml(DocumentStorage &store,const Element *el)
{
clear();
const List &list(el->getChildren());
List::const_iterator iter = list.begin();
bool sawScript = false;
bool sawTests = false;
bool sawProgram = false;
while(iter != list.end()) {
const Element *subel = *iter;
++iter;
if (subel->getName() == "script") {
sawScript = true;
console.restoreXml(subel);
}
else if (subel->getName() == "stringmatch") {
sawTests = true;
testList.emplace_back();
testList.back().restoreXml(subel);
}
else if (subel->getName() == "binaryimage") {
sawProgram = true;
store.registerTag(subel);
buildProgram(store);
}
else
throw IfaceParseError("Unknown tag in <decompiletest>: "+subel->getName());
}
if (!sawScript)
throw IfaceParseError("Did not see <script> tag in <decompiletest>");
if (!sawTests)
throw IfaceParseError("Did not see any <stringmatch> tags in <decompiletest>");
if (!sawProgram)
throw IfaceParseError("No <binaryimage> tag in <decompiletest>");
}
/// Pull the script and tests from a comment in \<binaryimage>
void FunctionTestCollection::restoreXmlOldForm(DocumentStorage &store,const Element *el)
{
clear();
throw IfaceParseError("Old format test not supported");
}
/// Run the script commands on the current program.
/// Collect any bulk output, and run tests over the output.
/// Report test failures back to the caller
/// \param midStream is the output stream to write to during the test
/// \param lateStream collects messages for a final summary
void FunctionTestCollection::runTests(ostream &midStream,list<string> &lateStream)
{
numTestsApplied = 0;
numTestsSucceeded = 0;
ostringstream midBuffer; // Collect command console output
console.optr = &midBuffer;
ostringstream bulkout;
console.fileoptr = &bulkout;
mainloop(&console);
console.optr = &midStream;
console.fileoptr = &midStream;
if (console.isInError()) {
midStream << "Error: Did not apply tests in " << fileName << endl;
midStream << midBuffer.str() << endl;
ostringstream fs;
fs << "Execution failed for " << fileName;
lateStream.push_back(fs.str());
return;
}
string result = bulkout.str();
if (result.size() == 0) {
ostringstream fs;
fs << "No output for " << fileName;
lateStream.push_back(fs.str());
return;
}
startTests();
string::size_type prevpos = 0;
string::size_type pos = result.find_first_of('\n');
while(pos != string::npos) {
string line = result.substr(prevpos,pos - prevpos);
passLineToTests(line);
prevpos = pos + 1;
pos = result.find_first_of('\n',prevpos);
}
if (prevpos != result.size()) {
string line = result.substr(prevpos); // Process final line without a newline char
passLineToTests(line);
}
evaluateTests(midStream, lateStream);
}
/// Run through all XML files in the given directory, processing each in turn.
/// \param dirname is a directory containing the XML test files
/// \param testNames (if not empty) specifies particular tests to run
void FunctionTestCollection::runTestCollections(const string &dirname,set<string> &testNames)
{
FileManage fileManage;
set<string> fullNames;
for(set<string>::iterator iter=testNames.begin();iter!=testNames.end();++iter) {
string val = dirname;
if (dirname.back() != '/')
val += '/';
val += *iter;
fullNames.insert(val);
}
fileManage.addDir2Path(dirname);
vector<string> testFiles;
fileManage.matchList(testFiles,".xml",true);
int4 totalTestsApplied = 0;
int4 totalTestsSucceeded = 0;
list<string> failures;
FunctionTestCollection testCollection;
for(int4 i=0;i<testFiles.size();++i) {
if (!fullNames.empty() && fullNames.find(testFiles[i]) == fullNames.end())
continue;
try {
testCollection.loadTest(testFiles[i]);
testCollection.runTests(cout, failures);
totalTestsApplied += testCollection.getTestsApplied();
totalTestsSucceeded += testCollection.getTestsSucceeded();
} catch(IfaceParseError &err) {
ostringstream fs;
fs << "Error parsing " << testFiles[i] << ": " << err.explain;
cout << fs.str() << endl;
failures.push_back(fs.str());
} catch(IfaceExecutionError &err) {
ostringstream fs;
fs << "Error executing " << testFiles[i] << ": " << err.explain;
cout << fs.str() << endl;
failures.push_back(fs.str());
}
}
cout << endl;
cout << "Total tests applied = " << totalTestsApplied << endl;
cout << "Total passing tests = " << totalTestsSucceeded << endl;
cout << endl;
if (!failures.empty()) {
cout << "Failures: " << endl;
list<string>::const_iterator iter = failures.begin();
for(int4 i=0;i<10;++i) {
cout << " " << *iter << endl;
++iter;
if (iter == failures.end()) break;
}
}
}

View file

@ -0,0 +1,88 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/// \file testfunction.hh
/// \brief Framework for decompiler data driven single function tests
#ifndef __TESTFUNCTION__
#define __TESTFUNCTION__
#include "libdecomp.hh"
#include <iostream>
#include <regex>
/// \brief A single property to be searched for in the output of a function decompilation
///
/// This is generally a regular expression run over the characters in the
/// decompiled "source" form of the function.
/// The property may "match" more than once or not at all.
class FunctionTestProperty {
int4 minimumMatch; ///< Minimum number of times property is expected to match
int4 maximumMatch; ///< Maximum number of times property is expected to match
string name; ///< Name of the test, to be printed in test summaries
regex pattern; ///< Regular expression to match against a line of output
mutable uint4 count; ///< Number of times regular expression has been seen
public:
string getName(void) const { return name; } ///< Get the name of the property
void startTest(void) const; ///< Reset "state", counting number of matching lines
void processLine(const string &line) const; ///< Search thru \e line, update state if match found
bool endTest(void) const; ///< Return results of property search
void restoreXml(const Element *el); ///< Reconstruct the property from an XML tag
};
/// \brief A console command run as part of a test sequence
class ConsoleCommands : public IfaceStatus {
vector<string> commands; ///< Sequence of commands
uint4 pos; ///< Position of next command to execute
virtual void readLine(string &line);
public:
ConsoleCommands(void); ///< Constructor
void reset(void); ///< Reset console for a new program
virtual bool isStreamFinished(void) const { return pos == commands.size(); }
void restoreXml(const Element *el); ///< Reconstruct the command from an XML tag
};
/// \brief A collection of tests around a single program/function
///
/// The collection of tests is loaded from a single XML file via loadTest(),
/// and the tests are run by calling runTests().
/// An entire program is loaded and possibly annotated by a series of
/// console command lines. Decompiler output is also triggered by a command,
/// and then the output is scanned for by the test objects (FunctionTestProperty).
/// Results of passed/failed tests are collected. If the command line script
/// does not complete properly, this is considered a special kind of failure.
class FunctionTestCollection {
IfaceDecompData *dcp; ///< Program data for the test collection
string fileName; ///< Name of the file containing test data
list<FunctionTestProperty> testList; ///< List of tests for this collection
ConsoleCommands console; ///< Decompiler console for executing scripts
mutable int4 numTestsApplied; ///< Count of tests that were executed
mutable int4 numTestsSucceeded; ///< Count of tests that passed
void clear(void); ///< Clear any previous architecture and function
void buildProgram(DocumentStorage &store); ///< Build program (Architecture) from \<binaryimage> tag
void startTests(void) const; ///< Initialize each FunctionTestProperty
void passLineToTests(const string &line) const; ///< Let all tests analyze a line of the results
void evaluateTests(ostream &midStream,list<string> &lateStream) const;
public:
FunctionTestCollection(void); ///< Constructor
int4 getTestsApplied(void) const { return numTestsApplied; } ///< Get the number of tests executed
int4 getTestsSucceeded(void) const { return numTestsSucceeded; } ///< Get the number of tests that passed
void loadTest(const string &filename); ///< Load a test program, tests, and script
void restoreXml(DocumentStorage &store,const Element *el); ///< Load tests from a \<decompilertest> tag.
void restoreXmlOldForm(DocumentStorage &store,const Element *el); ///< Load tests from \<binaryimage> tag.
void runTests(ostream &midStream,list<string> &lateStream); ///< Run the script and perform the tests
static void runTestCollections(const string &dirname,set<string> &testNames); ///< Run test files in a whole directory
};
#endif