From 401b22bbf50dbddc45fa99fb92249fd0c86c1bed Mon Sep 17 00:00:00 2001 From: Vas Crabb Date: Mon, 20 Sep 2021 03:03:26 +1000 Subject: util/delegate.h: Try to catch issues earlier, and some cleanup. Optimised generation of late bind helper functions. The late bind helper function doesn't depend on the delegate signature - only on the late bind base class and function target class. Having it inside the delegate base class means it needs to be instantiated for every combination of late bind base class, function target class and delegate signature. In a typical driver file, there is only one late bind base class (delegate_late_bind), and there will be delegates with multiple signatures bound to function members of the same class (e.g. read and write handlers, possibly of different widths, bound to members of the driver state class). By moving the late bind helper out of the delegate base class, the number of required instantiations can be reduced. By moving the body out of the enclosing class declaration, the compiler can be encouraged to coalese instantiations across translation units. While this won't give a further reduction in compile time, it should at least reduce the output binary size by reducing duplication for devices that frequently have handlers installed in memory maps. Added an additional template parameter to delegates allowing the late bind base class to be changed if desired. Moved the MSVC implementation "this" pointer optimisation out-of-line and added logging. Also cleaned up the Itanium "this" pointer adjustment and code pointer resolution implementation to reduce duplication and conditional compilation. Made binding_type_exception give a more meaningful what() message than "std::exception". Added extensive validity tests for delegate functionality. Pointers to member functions are tested, including multiple inheritance, virtual and non-virtual member functions, and checking for generational loss across copying/assigning delegates. This should properly exercise "this" pointer adjustment for the Itanium and MSVC implementations, and vtable lookup for the Itanium implementation. So-called late binding functionality is tested, including exceptions on failure. Functoids are tested, although given the encapsulation it's not possible to check that an apator isn't generated when it shouldn't be. --- src/emu/validity.cpp | 1171 +++++++++++++++++++++++++++++++++++--------------- 1 file changed, 830 insertions(+), 341 deletions(-) (limited to 'src/emu/validity.cpp') diff --git a/src/emu/validity.cpp b/src/emu/validity.cpp index fd8b8d4464b..539b6f3a747 100644 --- a/src/emu/validity.cpp +++ b/src/emu/validity.cpp @@ -22,390 +22,167 @@ #include -//************************************************************************** -// TYPE DEFINITIONS -//************************************************************************** - -//************************************************************************** -// INLINE FUNCTIONS -//************************************************************************** +namespace { //------------------------------------------------- -// ioport_string_from_index - return an indexed -// string from the I/O port system +// diamond_inheritance - forward declaration of a +// class to force MSVC to use unknown inheritance +// form of pointers to member functions //------------------------------------------------- -inline const char *validity_checker::ioport_string_from_index(u32 index) -{ - return ioport_configurer::string_from_token((const char *)(uintptr_t)index); -} +class diamond_inheritance; //------------------------------------------------- -// get_defstr_index - return the index of the -// string assuming it is one of the default -// strings +// test_delegate - a delegate that can return a +// result in a register //------------------------------------------------- -inline int validity_checker::get_defstr_index(const char *string, bool suppress_error) -{ - // check for strings that should be DEF_STR - auto strindex = m_defstr_map.find(string); - if (!suppress_error && strindex != m_defstr_map.end() && string != ioport_string_from_index(strindex->second)) - osd_printf_error("Must use DEF_STR( %s )\n", string); - return (strindex != m_defstr_map.end()) ? strindex->second : 0; -} +using test_delegate = delegate; //------------------------------------------------- -// random_u64 -// random_s64 -// random_u32 -// random_s32 +// make_diamond_class_delegate - make a delegate +// bound to an instance of an incomplete class +// type //------------------------------------------------- -#undef rand -inline s32 validity_checker::random_i32() { return s32(random_u32()); } -inline u32 validity_checker::random_u32() { return rand() ^ (rand() << 15); } -inline s64 validity_checker::random_i64() { return s64(random_u64()); } -inline u64 validity_checker::random_u64() { return u64(random_u32()) ^ (u64(random_u32()) << 30); } +test_delegate make_diamond_class_delegate(char (diamond_inheritance::*func)(void const *&), diamond_inheritance *obj) +{ + return test_delegate(func, obj); +} //------------------------------------------------- -// validate_tag - ensure that the given tag -// meets the general requirements +// virtual_base - simple class that will be used +// as the top vertex of the diamond //------------------------------------------------- -void validity_checker::validate_tag(const char *tag) +struct virtual_base { - // some common names that are now deprecated - if (strcmp(tag, "main") == 0 || strcmp(tag, "audio") == 0 || strcmp(tag, "sound") == 0 || strcmp(tag, "left") == 0 || strcmp(tag, "right") == 0) - osd_printf_error("Invalid generic tag '%s' used\n", tag); - - // scan for invalid characters - static char const *const validchars = "abcdefghijklmnopqrstuvwxyz0123456789_.:^$"; - for (char const *p = tag; *p; ++p) - { - // only lower-case permitted - if (*p != tolower(u8(*p))) - { - osd_printf_error("Tag '%s' contains upper-case characters\n", tag); - break; - } - if (*p == ' ') - { - osd_printf_error("Tag '%s' contains spaces\n", tag); - break; - } - if (!strchr(validchars, *p)) - { - osd_printf_error("Tag '%s' contains invalid character '%c'\n", tag, *p); - break; - } - } - - // find the start of the final tag - const char *begin = strrchr(tag, ':'); - if (begin == nullptr) - begin = tag; - else - begin += 1; - - // 0-length = bad - if (*begin == 0) - osd_printf_error("Found 0-length tag\n"); - - // too short/too long = bad - if (strlen(begin) < MIN_TAG_LENGTH) - osd_printf_error("Tag '%s' is too short (must be at least %d characters)\n", tag, MIN_TAG_LENGTH); -} - + char get_base(void const *&p) { p = this; return 'x'; } + int x; +}; -//************************************************************************** -// VALIDATION FUNCTIONS -//************************************************************************** - //------------------------------------------------- -// validity_checker - constructor +// virtual_derived_a - first class derived from +// virtual base //------------------------------------------------- -validity_checker::validity_checker(emu_options &options, bool quick) - : m_drivlist(options) - , m_errors(0) - , m_warnings(0) - , m_print_verbose(options.verbose()) - , m_current_driver(nullptr) - , m_current_device(nullptr) - , m_current_ioport(nullptr) - , m_checking_card(false) - , m_quick(quick) +struct virtual_derived_a : virtual virtual_base { - // pre-populate the defstr map with all the default strings - for (int strnum = 1; strnum < INPUT_STRING_COUNT; strnum++) - { - const char *string = ioport_string_from_index(strnum); - if (string != nullptr) - m_defstr_map.insert(std::make_pair(string, strnum)); - } -} + char get_derived_a(void const *&p) { p = this; return 'a'; } + int a; +}; + //------------------------------------------------- -// validity_checker - destructor +// virtual_derived_b - second class derived from +// virtual base //------------------------------------------------- -validity_checker::~validity_checker() +struct virtual_derived_b : virtual virtual_base { - validate_end(); -} + char get_derived_b(void const *&p) { p = this; return 'b'; } + int b; +}; + //------------------------------------------------- -// check_driver - check a single driver +// diamond_inheritance - actual definition of +// class with diamond inheritance //------------------------------------------------- -void validity_checker::check_driver(const game_driver &driver) +class diamond_inheritance : public virtual_derived_a, public virtual_derived_b { - // simply validate the one driver - validate_begin(); - validate_one(driver); - validate_end(); -} +}; //------------------------------------------------- -// check_shared_source - check all drivers that -// share the same source file as the given driver +// ioport_string_from_index - return an indexed +// string from the I/O port system //------------------------------------------------- -void validity_checker::check_shared_source(const game_driver &driver) +inline char const *ioport_string_from_index(u32 index) { - // initialize - validate_begin(); - - // then iterate over all drivers and check the ones that share the same source file - m_drivlist.reset(); - while (m_drivlist.next()) - if (strcmp(driver.type.source(), m_drivlist.driver().type.source()) == 0) - validate_one(m_drivlist.driver()); - - // cleanup - validate_end(); + return ioport_configurer::string_from_token(reinterpret_cast(uintptr_t(index))); } //------------------------------------------------- -// check_all_matching - check all drivers whose -// names match the given string +// random_u64 +// random_s64 +// random_u32 +// random_s32 //------------------------------------------------- - -bool validity_checker::check_all_matching(const char *string) -{ - // start by checking core stuff - validate_begin(); - validate_core(); - validate_inlines(); - validate_rgb(); - - // if we had warnings or errors, output - if (m_errors > 0 || m_warnings > 0 || !m_verbose_text.empty()) - { - output_via_delegate(OSD_OUTPUT_CHANNEL_ERROR, "Core: %d errors, %d warnings\n", m_errors, m_warnings); - if (m_errors > 0) - output_indented_errors(m_error_text, "Errors"); - if (m_warnings > 0) - output_indented_errors(m_warning_text, "Warnings"); - if (!m_verbose_text.empty()) - output_indented_errors(m_verbose_text, "Messages"); - output_via_delegate(OSD_OUTPUT_CHANNEL_ERROR, "\n"); - } - - // then iterate over all drivers and check them - m_drivlist.reset(); - bool validated_any = false; - while (m_drivlist.next()) - { - if (driver_list::matches(string, m_drivlist.driver().name)) - { - validate_one(m_drivlist.driver()); - validated_any = true; - } - } - - // validate devices - if (!string) - validate_device_types(); - - // cleanup - validate_end(); - - // if we failed to match anything, it - if (string && !validated_any) - throw emu_fatalerror(EMU_ERR_NO_SUCH_SYSTEM, "No matching systems found for '%s'", string); - - return !(m_errors > 0 || m_warnings > 0); -} +#undef rand +inline u32 random_u32() { return rand() ^ (rand() << 15); } +inline s32 random_i32() { return s32(random_u32()); } +inline u64 random_u64() { return u64(random_u32()) ^ (u64(random_u32()) << 30); } +inline s64 random_i64() { return s64(random_u64()); } //------------------------------------------------- -// validate_begin - prepare for validation by -// taking over the output callbacks and resetting -// our internal state +// validate_integer_semantics - validate that +// integers behave as expected, particularly +// with regards to overflow and shifting //------------------------------------------------- -void validity_checker::validate_begin() +void validate_integer_semantics() { - // take over error and warning outputs - osd_output::push(this); + // basic system checks + if (~0 != -1) osd_printf_error("Machine must be two's complement\n"); - // reset all our maps - m_names_map.clear(); - m_descriptions_map.clear(); - m_roms_map.clear(); - m_defstr_map.clear(); - m_region_map.clear(); - m_ioport_set.clear(); + u8 a = 0xff; + u8 b = a + 1; + if (b > a) osd_printf_error("u8 must be 8 bits\n"); - // reset internal state - m_errors = 0; - m_warnings = 0; - m_already_checked.clear(); -} + // check size of core integer types + if (sizeof(s8) != 1) osd_printf_error("s8 must be 8 bits\n"); + if (sizeof(u8) != 1) osd_printf_error("u8 must be 8 bits\n"); + if (sizeof(s16) != 2) osd_printf_error("s16 must be 16 bits\n"); + if (sizeof(u16) != 2) osd_printf_error("u16 must be 16 bits\n"); + if (sizeof(s32) != 4) osd_printf_error("s32 must be 32 bits\n"); + if (sizeof(u32) != 4) osd_printf_error("u32 must be 32 bits\n"); + if (sizeof(s64) != 8) osd_printf_error("s64 must be 64 bits\n"); + if (sizeof(u64) != 8) osd_printf_error("u64 must be 64 bits\n"); + // check signed right shift + s8 a8 = -3; + s16 a16 = -3; + s32 a32 = -3; + s64 a64 = -3; + if (a8 >> 1 != -2) osd_printf_error("s8 right shift must be arithmetic\n"); + if (a16 >> 1 != -2) osd_printf_error("s16 right shift must be arithmetic\n"); + if (a32 >> 1 != -2) osd_printf_error("s32 right shift must be arithmetic\n"); + if (a64 >> 1 != -2) osd_printf_error("s64 right shift must be arithmetic\n"); -//------------------------------------------------- -// validate_end - restore output callbacks and -// clean up -//------------------------------------------------- + // check pointer size +#ifdef PTR64 + static_assert(sizeof(void *) == 8, "PTR64 flag enabled, but was compiled for 32-bit target\n"); +#else + static_assert(sizeof(void *) == 4, "PTR64 flag not enabled, but was compiled for 64-bit target\n"); +#endif -void validity_checker::validate_end() -{ - // restore the original output callbacks - osd_output::pop(this); + // TODO: check if this is actually working + // check endianness definition + u16 lsbtest = 0; + *(u8 *)&lsbtest = 0xff; +#ifdef LSB_FIRST + if (lsbtest == 0xff00) osd_printf_error("LSB_FIRST specified, but running on a big-endian machine\n"); +#else + if (lsbtest == 0x00ff) osd_printf_error("LSB_FIRST not specified, but running on a little-endian machine\n"); +#endif } //------------------------------------------------- -// validate_drivers - master validity checker +// validate_inlines - validate inline function +// behaviors //------------------------------------------------- -void validity_checker::validate_one(const game_driver &driver) -{ - // help verbose validation detect configuration-related crashes - if (m_print_verbose) - output_via_delegate(OSD_OUTPUT_CHANNEL_ERROR, "Validating driver %s (%s)...\n", driver.name, core_filename_extract_base(driver.type.source())); - - // set the current driver - m_current_driver = &driver; - m_current_device = nullptr; - m_current_ioport = nullptr; - m_region_map.clear(); - m_ioport_set.clear(); - m_checking_card = false; - - // reset error/warning state - int start_errors = m_errors; - int start_warnings = m_warnings; - m_error_text.clear(); - m_warning_text.clear(); - m_verbose_text.clear(); - - // wrap in try/catch to catch fatalerrors - try - { - machine_config config(driver, m_blank_options); - validate_driver(config.root_device()); - validate_roms(config.root_device()); - validate_inputs(config.root_device()); - validate_devices(config); - } - catch (emu_fatalerror const &err) - { - osd_printf_error("Fatal error %s", err.what()); - } - - // if we had warnings or errors, output - if (m_errors > start_errors || m_warnings > start_warnings || !m_verbose_text.empty()) - { - if (!m_print_verbose) - output_via_delegate(OSD_OUTPUT_CHANNEL_ERROR, "Driver %s (file %s): ", driver.name, core_filename_extract_base(driver.type.source())); - output_via_delegate(OSD_OUTPUT_CHANNEL_ERROR, "%d errors, %d warnings\n", m_errors - start_errors, m_warnings - start_warnings); - if (m_errors > start_errors) - output_indented_errors(m_error_text, "Errors"); - if (m_warnings > start_warnings) - output_indented_errors(m_warning_text, "Warnings"); - if (!m_verbose_text.empty()) - output_indented_errors(m_verbose_text, "Messages"); - output_via_delegate(OSD_OUTPUT_CHANNEL_ERROR, "\n"); - } - - // reset the driver/device - m_current_driver = nullptr; - m_current_device = nullptr; - m_current_ioport = nullptr; - m_region_map.clear(); - m_ioport_set.clear(); - m_checking_card = false; -} - - -//------------------------------------------------- -// validate_core - validate core internal systems -//------------------------------------------------- - -void validity_checker::validate_core() -{ - // basic system checks - if (~0 != -1) osd_printf_error("Machine must be two's complement\n"); - - u8 a = 0xff; - u8 b = a + 1; - if (b > a) osd_printf_error("u8 must be 8 bits\n"); - - // check size of core integer types - if (sizeof(s8) != 1) osd_printf_error("s8 must be 8 bits\n"); - if (sizeof(u8) != 1) osd_printf_error("u8 must be 8 bits\n"); - if (sizeof(s16) != 2) osd_printf_error("s16 must be 16 bits\n"); - if (sizeof(u16) != 2) osd_printf_error("u16 must be 16 bits\n"); - if (sizeof(s32) != 4) osd_printf_error("s32 must be 32 bits\n"); - if (sizeof(u32) != 4) osd_printf_error("u32 must be 32 bits\n"); - if (sizeof(s64) != 8) osd_printf_error("s64 must be 64 bits\n"); - if (sizeof(u64) != 8) osd_printf_error("u64 must be 64 bits\n"); - - // check signed right shift - s8 a8 = -3; - s16 a16 = -3; - s32 a32 = -3; - s64 a64 = -3; - if (a8 >> 1 != -2) osd_printf_error("s8 right shift must be arithmetic\n"); - if (a16 >> 1 != -2) osd_printf_error("s16 right shift must be arithmetic\n"); - if (a32 >> 1 != -2) osd_printf_error("s32 right shift must be arithmetic\n"); - if (a64 >> 1 != -2) osd_printf_error("s64 right shift must be arithmetic\n"); - - // check pointer size -#ifdef PTR64 - static_assert(sizeof(void *) == 8, "PTR64 flag enabled, but was compiled for 32-bit target\n"); -#else - static_assert(sizeof(void *) == 4, "PTR64 flag not enabled, but was compiled for 64-bit target\n"); -#endif - - // TODO: check if this is actually working - // check endianness definition - u16 lsbtest = 0; - *(u8 *)&lsbtest = 0xff; -#ifdef LSB_FIRST - if (lsbtest == 0xff00) osd_printf_error("LSB_FIRST specified, but running on a big-endian machine\n"); -#else - if (lsbtest == 0x00ff) osd_printf_error("LSB_FIRST not specified, but running on a little-endian machine\n"); -#endif -} - - -//------------------------------------------------- -// validate_inlines - validate inline function -// behaviors -//------------------------------------------------- - -void validity_checker::validate_inlines() +void validate_inlines() { volatile u64 testu64a = random_u64(); volatile s64 testi64a = random_i64(); @@ -536,7 +313,7 @@ void validity_checker::validate_inlines() // class //------------------------------------------------- -void validity_checker::validate_rgb() +void validate_rgb() { /* This performs cursory tests of most of the vector-optimised RGB @@ -566,29 +343,31 @@ void validity_checker::validate_rgb() scale_imm_add_and_clamp(const s32, const rgbaint_t&); */ - auto random_i32_nolimit = [this] - { - s32 result; - do { result = random_i32(); } while ((result == std::numeric_limits::min()) || (result == std::numeric_limits::max())); - return result; - }; + auto random_i32_nolimit = + [] () + { + s32 result; + do { result = random_i32(); } while ((result == std::numeric_limits::min()) || (result == std::numeric_limits::max())); + return result; + }; volatile s32 expected_a, expected_r, expected_g, expected_b; volatile s32 actual_a, actual_r, actual_g, actual_b; volatile s32 imm; rgbaint_t rgb, other; rgb_t packed; - auto check_expected = [&] (const char *desc) - { - const volatile s32 a = rgb.get_a32(); - const volatile s32 r = rgb.get_r32(); - const volatile s32 g = rgb.get_g32(); - const volatile s32 b = rgb.get_b32(); - if (a != expected_a) osd_printf_error("Error testing %s get_a32() = %d (expected %d)\n", desc, s32(a), s32(expected_a)); - if (r != expected_r) osd_printf_error("Error testing %s get_r32() = %d (expected %d)\n", desc, s32(r), s32(expected_r)); - if (g != expected_g) osd_printf_error("Error testing %s get_g32() = %d (expected %d)\n", desc, s32(g), s32(expected_g)); - if (b != expected_b) osd_printf_error("Error testing %s get_b32() = %d (expected %d)\n", desc, s32(b), s32(expected_b)); - }; + auto check_expected = + [&] (const char *desc) + { + const volatile s32 a = rgb.get_a32(); + const volatile s32 r = rgb.get_r32(); + const volatile s32 g = rgb.get_g32(); + const volatile s32 b = rgb.get_b32(); + if (a != expected_a) osd_printf_error("Error testing %s get_a32() = %d (expected %d)\n", desc, s32(a), s32(expected_a)); + if (r != expected_r) osd_printf_error("Error testing %s get_r32() = %d (expected %d)\n", desc, s32(r), s32(expected_r)); + if (g != expected_g) osd_printf_error("Error testing %s get_g32() = %d (expected %d)\n", desc, s32(g), s32(expected_g)); + if (b != expected_b) osd_printf_error("Error testing %s get_b32() = %d (expected %d)\n", desc, s32(b), s32(expected_b)); + }; // check set/get expected_a = random_i32(); @@ -1453,6 +1232,716 @@ void validity_checker::validate_rgb() } +//------------------------------------------------- +// validate_delegates_mfp - test delegate member +// function functionality +//------------------------------------------------- + +void validate_delegates_mfp() +{ + struct base_a + { + virtual ~base_a() = default; + char get_a(void const *&p) { p = this; return 'a'; } + virtual char get_a_v(void const *&p) { p = this; return 'A'; } + int a; + }; + + struct base_b + { + virtual ~base_b() = default; + char get_b(void const *&p) { p = this; return 'b'; } + virtual char get_b_v(void const *&p) { p = this; return 'B'; } + int b; + }; + + struct multiple_inheritance : base_a, base_b + { + }; + + struct overridden : base_a, base_b + { + virtual char get_a_v(void const *&p) override { p = this; return 'x'; } + virtual char get_b_v(void const *&p) override { p = this; return 'y'; } + }; + + multiple_inheritance mi; + overridden o; + diamond_inheritance d; + char ch; + void const *addr; + + // test non-virtual member functions and "this" pointer adjustment + test_delegate cb1(&multiple_inheritance::get_a, &mi); + test_delegate cb2(&multiple_inheritance::get_b, &mi); + + addr = nullptr; + ch = cb1(addr); + if ('a' != ch) + osd_printf_error("Error testing delegate non-virtual member function dispatch\n"); + if (static_cast(&mi) != addr) + osd_printf_error("Error testing delegate this pointer adjustment %p -> %p (expected %p)\n", static_cast(&mi), addr, static_cast(static_cast(&mi))); + + addr = nullptr; + ch = cb2(addr); + if ('b' != ch) + osd_printf_error("Error testing delegate non-virtual member function dispatch\n"); + if (static_cast(&mi) != addr) + osd_printf_error("Error testing delegate this pointer adjustment %p -> %p (expected %p)\n", static_cast(&mi), addr, static_cast(static_cast(&mi))); + + // test that "this" pointer adjustment survives copy construction + test_delegate cb3(cb1); + test_delegate cb4(cb2); + + addr = nullptr; + ch = cb3(addr); + if ('a' != ch) + osd_printf_error("Error testing copy constructed delegate non-virtual member function dispatch\n"); + if (static_cast(&mi) != addr) + osd_printf_error("Error testing copy constructed delegate this pointer adjustment %p -> %p (expected %p)\n", static_cast(&mi), addr, static_cast(static_cast(&mi))); + + addr = nullptr; + ch = cb4(addr); + if ('b' != ch) + osd_printf_error("Error testing copy constructed delegate non-virtual member function dispatch\n"); + if (static_cast(&mi) != addr) + osd_printf_error("Error testing copy constructed delegate this pointer adjustment %p -> %p (expected %p)\n", static_cast(&mi), addr, static_cast(static_cast(&mi))); + + // test that "this" pointer adjustment survives assignment and doesn't suffer generational loss + cb1 = cb4; + cb2 = cb3; + + addr = nullptr; + ch = cb1(addr); + if ('b' != ch) + osd_printf_error("Error testing assigned delegate non-virtual member function dispatch\n"); + if (static_cast(&mi) != addr) + osd_printf_error("Error testing assigned delegate this pointer adjustment %p -> %p (expected %p)\n", static_cast(&mi), addr, static_cast(static_cast(&mi))); + + addr = nullptr; + ch = cb2(addr); + if ('a' != ch) + osd_printf_error("Error testing assigned delegate non-virtual member function dispatch\n"); + if (static_cast(&mi) != addr) + osd_printf_error("Error testing assigned delegate this pointer adjustment %p -> %p (expected %p)\n", static_cast(&mi), addr, static_cast(static_cast(&mi))); + + // test virtual member functions and "this" pointer adjustment + cb1 = test_delegate(&multiple_inheritance::get_a_v, &mi); + cb2 = test_delegate(&multiple_inheritance::get_b_v, &mi); + + addr = nullptr; + ch = cb1(addr); + if ('A' != ch) + osd_printf_error("Error testing delegate virtual member function dispatch\n"); + if (static_cast(&mi) != addr) + osd_printf_error("Error testing delegate this pointer adjustment for virtual member function %p -> %p (expected %p)\n", static_cast(&mi), addr, static_cast(static_cast(&mi))); + + addr = nullptr; + ch = cb2(addr); + if ('B' != ch) + osd_printf_error("Error testing delegate virtual member function dispatch\n"); + if (static_cast(&mi) != addr) + osd_printf_error("Error testing delegate this pointer adjustment for virtual member function %p -> %p (expected %p)\n", static_cast(&mi), addr, static_cast(static_cast(&mi))); + + // test that virtual member functions survive copy construction + test_delegate cb5(cb1); + test_delegate cb6(cb2); + + addr = nullptr; + ch = cb5(addr); + if ('A' != ch) + osd_printf_error("Error testing copy constructed delegate virtual member function dispatch\n"); + if (static_cast(&mi) != addr) + osd_printf_error("Error testing copy constructed delegate this pointer adjustment for virtual member function %p -> %p (expected %p)\n", static_cast(&mi), addr, static_cast(static_cast(&mi))); + + addr = nullptr; + ch = cb6(addr); + if ('B' != ch) + osd_printf_error("Error testing copy constructed delegate virtual member function dispatch\n"); + if (static_cast(&mi) != addr) + osd_printf_error("Error testing copy constructed delegate this pointer adjustment for virtual member function %p -> %p (expected %p)\n", static_cast(&mi), addr, static_cast(static_cast(&mi))); + + // test virtual member function dispatch through base pointer + cb1 = test_delegate(&base_a::get_a_v, static_cast(&o)); + cb2 = test_delegate(&base_b::get_b_v, static_cast(&o)); + + addr = nullptr; + ch = cb1(addr); + if ('x' != ch) + osd_printf_error("Error testing delegate virtual member function dispatch through base class pointer\n"); + if (&o != addr) + osd_printf_error("Error testing delegate this pointer adjustment for virtual member function through base class pointer %p -> %p (expected %p)\n", static_cast(static_cast(&o)), addr, static_cast(&o)); + + addr = nullptr; + ch = cb2(addr); + if ('y' != ch) + osd_printf_error("Error testing delegate virtual member function dispatch through base class pointer\n"); + if (&o != addr) + osd_printf_error("Error testing delegate this pointer adjustment for virtual member function through base class pointer %p -> %p (expected %p)\n", static_cast(static_cast(&o)), addr, static_cast(&o)); + + // test creating delegates for a forward-declared class + cb1 = make_diamond_class_delegate(&diamond_inheritance::get_derived_a, &d); + cb2 = make_diamond_class_delegate(&diamond_inheritance::get_derived_b, &d); + + addr = nullptr; + ch = cb1(addr); + if ('a' != ch) + osd_printf_error("Error testing delegate non-virtual member function dispatch for incomplete class\n"); + if (static_cast(&d) != addr) + osd_printf_error("Error testing delegate this pointer adjustment for incomplete class %p -> %p (expected %p)\n", static_cast(&d), addr, static_cast(static_cast(&d))); + + addr = nullptr; + ch = cb2(addr); + if ('b' != ch) + osd_printf_error("Error testing delegate non-virtual member function dispatch for incomplete class\n"); + if (static_cast(&d) != addr) + osd_printf_error("Error testing delegate this pointer adjustment for incomplete class %p -> %p (expected %p)\n", static_cast(&d), addr, static_cast(static_cast(&d))); + + // test MSVC extension allowing casting member pointer types across virtual inheritance relationships +#if defined(_MSC_VER) + cb1 = make_diamond_class_delegate(&diamond_inheritance::get_base, &d); + + addr = nullptr; + ch = cb1(addr); + if ('x' != ch) + osd_printf_error("Error testing delegate non-virtual member function dispatch for incomplete class\n"); + if (static_cast(&d) != addr) + osd_printf_error("Error testing delegate this pointer adjustment for incomplete class %p -> %p (expected %p)\n", static_cast(&d), addr, static_cast(static_cast(&d))); +#endif // defined(_MSC_VER) +} + + +//------------------------------------------------- +// validate_delegates_latebind - test binding a +// delegate to an object after the function is +// set +//------------------------------------------------- + +void validate_delegates_latebind() +{ + struct target + { + virtual ~target() = default; + virtual char operator()(void const *&p) const = 0; + }; + + struct derived_a : target, delegate_late_bind + { + virtual char operator()(void const *&p) const override { p = this; return 'a'; } + }; + + struct derived_b : target, delegate_late_bind + { + virtual char operator()(void const *&p) const override { p = this; return 'b'; } + }; + + struct unrelated : delegate_late_bind + { + }; + + char ch; + void const *addr; + derived_a a; + derived_b b; + unrelated u; + + // delegate with no target object + test_delegate cb1(&target::operator(), static_cast(nullptr)); + + // test late bind on construction + test_delegate cb2(cb1, a); + addr = nullptr; + ch = cb2(addr); + if (('a' != ch) || (&a != addr)) + osd_printf_error("Error testing delegate late bind on construction\n"); + + // test explicit late bind + cb1.late_bind(b); + ch = cb1(addr); + if (('b' != ch) || (&b != addr)) + osd_printf_error("Error testing delegate explicit late bind\n"); + + // test late bind when object is set + cb1.late_bind(a); + ch = cb1(addr); + if (('a' != ch) || (&a != addr)) + osd_printf_error("Error testing delegate explicit late bind when object is set\n"); + + // test late bind on copy of delegate with target set + test_delegate cb3(cb1, b); + addr = nullptr; + ch = cb3(addr); + if (('b' != ch) || (&b != addr)) + osd_printf_error("Error testing delegate late bind on construction using template with object set\n"); + + // test late bind exception + ch = '-'; + try + { + cb1.late_bind(u); + } + catch (binding_type_exception const &e) + { + if ((e.target_type() != typeid(target)) || (e.actual_type() != typeid(unrelated))) + { + osd_printf_error( + "Error testing delegate late bind type error %s -> %s (expected %s -> %s)\n", + e.actual_type().name(), + e.target_type().name(), + typeid(unrelated).name(), + typeid(target).name()); + } + ch = '+'; + } + if ('+' != ch) + osd_printf_error("Error testing delegate late bind type error\n"); + + // test syntax for creating delegate with alternate late bind base + delegate cb4( + [] (auto &o, void const *&p) { p = &o; return 'l'; }, + static_cast(nullptr)); + try { cb1.late_bind(a); } + catch (binding_type_exception const &) { } +} + + +//------------------------------------------------- +// validate_delegates_functoid - test delegate +// functoid functionality +//------------------------------------------------- + +void validate_delegates_functoid() +{ + using void_delegate = delegate; + struct const_only + { + char operator()(void const *&p) const { return 'C'; } + }; + + struct const_or_not + { + char operator()(void const *&p) { return 'n'; } + char operator()(void const *&p) const { return 'c'; } + }; + + struct noncopyable + { + noncopyable() = default; + noncopyable(noncopyable const &) = delete; + noncopyable &operator=(noncopyable const &) = delete; + + char operator()(void const *&p) { p = this; return '*'; } + }; + + noncopyable n; + char ch; + void const *addr = nullptr; + + // test that const call operators are supported + test_delegate cb1{ const_only() }; + if ('C' != cb1(addr)) + osd_printf_error("Error testing delegate functoid dispatch\n"); + + // test that non-const call operators are preferred + cb1 = test_delegate{ const_or_not() }; + if ('n' != cb1(addr)) + osd_printf_error("Error testing delegate functoid dispatch\n"); + + // test that functoids are implicitly mutable + cb1 = test_delegate{ [a = &addr, c = '0'] (void const *&p) mutable { p = a; return c++; } }; + + addr = nullptr; + ch = cb1(addr); + if (('0' != ch) || (&addr != addr)) + osd_printf_error("Error testing delegate functoid %c (expected 0)\n", ch); + + addr = nullptr; + ch = cb1(addr); + if (('1' != ch) || (&addr != addr)) + osd_printf_error("Error testing delegate functoid %c (expected 1)\n", ch); + + // test that functoids survive copy construction + test_delegate cb2(cb1); + + addr = nullptr; + ch = cb2(addr); + if (('2' != ch) || (&addr != addr)) + osd_printf_error("Error testing delegate functoid %c (expected 2)\n", ch); + + addr = nullptr; + ch = cb2(addr); + if (('3' != ch) || (&addr != addr)) + osd_printf_error("Error testing delegate functoid %c (expected 3)\n", ch); + + addr = nullptr; + ch = cb1(addr); + if (('2' != ch) || (&addr != addr)) + osd_printf_error("Error testing delegate functoid %c (expected 2)\n", ch); + + // test that functoids survive assignment + cb1 = cb2; + + addr = nullptr; + ch = cb1(addr); + if (('4' != ch) || (&addr != addr)) + osd_printf_error("Error testing delegate functoid %c (expected 4)\n", ch); + + addr = nullptr; + ch = cb1(addr); + if (('5' != ch) || (&addr != addr)) + osd_printf_error("Error testing delegate functoid %c (expected 5)\n", ch); + + addr = nullptr; + ch = cb2(addr); + if (('4' != ch) || (&addr != addr)) + osd_printf_error("Error testing delegate functoid %c (expected 4)\n", ch); + + // test that std::ref can be used with non-copyable functoids + test_delegate cb3(std::ref(n)); + + addr = nullptr; + ch = cb3(addr); + if (('*' != ch) || (&n != addr)) + osd_printf_error("Error testing delegate with functoid reference wrapper %p (expected %p)\n", addr, static_cast(&n)); + + // test that std::ref survives copy construction and assignment + cb2 = cb3; + test_delegate cb4(cb3); + + addr = nullptr; + ch = cb2(addr); + if (('*' != ch) || (&n != addr)) + osd_printf_error("Error testing delegate with functoid reference wrapper %p (expected %p)\n", addr, static_cast(&n)); + + addr = nullptr; + ch = cb4(addr); + if (('*' != ch) || (&n != addr)) + osd_printf_error("Error testing delegate with functoid reference wrapper %p (expected %p)\n", addr, static_cast(&n)); + + // test discarding return value for delegates returning void + void_delegate void_cb1{ [&cb1] (void const *&p) { p = &cb1; return 123; } }; + void_delegate void_cb2{ std::ref(n) }; + + addr = nullptr; + void_cb1(addr); + if (&cb1 != addr) + osd_printf_error("Error testing delegate with functoid requiring adapter %p (expected %p)\n", addr, static_cast(&cb1)); + + addr = nullptr; + void_cb2(addr); + if (&n != addr) + osd_printf_error("Error testing delegate with functoid requiring adapter %p (expected %p)\n", addr, static_cast(&n)); + + // test that adaptor is generated after assignment + void_cb2 = void_cb1; + + addr = nullptr; + void_cb2(addr); + if (&cb1 != addr) + osd_printf_error("Error testing delegate with functoid requiring adapter %p (expected %p)\n", addr, static_cast(&cb1)); +} + +} // anonymous namespace + + + +//------------------------------------------------- +// get_defstr_index - return the index of the +// string assuming it is one of the default +// strings +//------------------------------------------------- + +inline int validity_checker::get_defstr_index(const char *string, bool suppress_error) +{ + // check for strings that should be DEF_STR + auto strindex = m_defstr_map.find(string); + if (!suppress_error && strindex != m_defstr_map.end() && string != ioport_string_from_index(strindex->second)) + osd_printf_error("Must use DEF_STR( %s )\n", string); + return (strindex != m_defstr_map.end()) ? strindex->second : 0; +} + + + +//------------------------------------------------- +// validate_tag - ensure that the given tag +// meets the general requirements +//------------------------------------------------- + +void validity_checker::validate_tag(const char *tag) +{ + // some common names that are now deprecated + if (strcmp(tag, "main") == 0 || strcmp(tag, "audio") == 0 || strcmp(tag, "sound") == 0 || strcmp(tag, "left") == 0 || strcmp(tag, "right") == 0) + osd_printf_error("Invalid generic tag '%s' used\n", tag); + + // scan for invalid characters + static char const *const validchars = "abcdefghijklmnopqrstuvwxyz0123456789_.:^$"; + for (char const *p = tag; *p; ++p) + { + // only lower-case permitted + if (*p != tolower(u8(*p))) + { + osd_printf_error("Tag '%s' contains upper-case characters\n", tag); + break; + } + if (*p == ' ') + { + osd_printf_error("Tag '%s' contains spaces\n", tag); + break; + } + if (!strchr(validchars, *p)) + { + osd_printf_error("Tag '%s' contains invalid character '%c'\n", tag, *p); + break; + } + } + + // find the start of the final tag + const char *begin = strrchr(tag, ':'); + if (begin == nullptr) + begin = tag; + else + begin += 1; + + // 0-length = bad + if (*begin == 0) + osd_printf_error("Found 0-length tag\n"); + + // too short/too long = bad + if (strlen(begin) < MIN_TAG_LENGTH) + osd_printf_error("Tag '%s' is too short (must be at least %d characters)\n", tag, MIN_TAG_LENGTH); +} + + + +//************************************************************************** +// VALIDATION FUNCTIONS +//************************************************************************** + +//------------------------------------------------- +// validity_checker - constructor +//------------------------------------------------- + +validity_checker::validity_checker(emu_options &options, bool quick) + : m_drivlist(options) + , m_errors(0) + , m_warnings(0) + , m_print_verbose(options.verbose()) + , m_current_driver(nullptr) + , m_current_device(nullptr) + , m_current_ioport(nullptr) + , m_checking_card(false) + , m_quick(quick) +{ + // pre-populate the defstr map with all the default strings + for (int strnum = 1; strnum < INPUT_STRING_COUNT; strnum++) + { + const char *string = ioport_string_from_index(strnum); + if (string != nullptr) + m_defstr_map.insert(std::make_pair(string, strnum)); + } +} + +//------------------------------------------------- +// validity_checker - destructor +//------------------------------------------------- + +validity_checker::~validity_checker() +{ + validate_end(); +} + +//------------------------------------------------- +// check_driver - check a single driver +//------------------------------------------------- + +void validity_checker::check_driver(const game_driver &driver) +{ + // simply validate the one driver + validate_begin(); + validate_one(driver); + validate_end(); +} + + +//------------------------------------------------- +// check_shared_source - check all drivers that +// share the same source file as the given driver +//------------------------------------------------- + +void validity_checker::check_shared_source(const game_driver &driver) +{ + // initialize + validate_begin(); + + // then iterate over all drivers and check the ones that share the same source file + m_drivlist.reset(); + while (m_drivlist.next()) + if (strcmp(driver.type.source(), m_drivlist.driver().type.source()) == 0) + validate_one(m_drivlist.driver()); + + // cleanup + validate_end(); +} + + +//------------------------------------------------- +// check_all_matching - check all drivers whose +// names match the given string +//------------------------------------------------- + +bool validity_checker::check_all_matching(const char *string) +{ + // start by checking core stuff + validate_begin(); + validate_integer_semantics(); + validate_inlines(); + validate_rgb(); + validate_delegates_mfp(); + validate_delegates_latebind(); + validate_delegates_functoid(); + + // if we had warnings or errors, output + if (m_errors > 0 || m_warnings > 0 || !m_verbose_text.empty()) + { + output_via_delegate(OSD_OUTPUT_CHANNEL_ERROR, "Core: %d errors, %d warnings\n", m_errors, m_warnings); + if (m_errors > 0) + output_indented_errors(m_error_text, "Errors"); + if (m_warnings > 0) + output_indented_errors(m_warning_text, "Warnings"); + if (!m_verbose_text.empty()) + output_indented_errors(m_verbose_text, "Messages"); + output_via_delegate(OSD_OUTPUT_CHANNEL_ERROR, "\n"); + } + + // then iterate over all drivers and check them + m_drivlist.reset(); + bool validated_any = false; + while (m_drivlist.next()) + { + if (driver_list::matches(string, m_drivlist.driver().name)) + { + validate_one(m_drivlist.driver()); + validated_any = true; + } + } + + // validate devices + if (!string) + validate_device_types(); + + // cleanup + validate_end(); + + // if we failed to match anything, it + if (string && !validated_any) + throw emu_fatalerror(EMU_ERR_NO_SUCH_SYSTEM, "No matching systems found for '%s'", string); + + return !(m_errors > 0 || m_warnings > 0); +} + + +//------------------------------------------------- +// validate_begin - prepare for validation by +// taking over the output callbacks and resetting +// our internal state +//------------------------------------------------- + +void validity_checker::validate_begin() +{ + // take over error and warning outputs + osd_output::push(this); + + // reset all our maps + m_names_map.clear(); + m_descriptions_map.clear(); + m_roms_map.clear(); + m_defstr_map.clear(); + m_region_map.clear(); + m_ioport_set.clear(); + + // reset internal state + m_errors = 0; + m_warnings = 0; + m_already_checked.clear(); +} + + +//------------------------------------------------- +// validate_end - restore output callbacks and +// clean up +//------------------------------------------------- + +void validity_checker::validate_end() +{ + // restore the original output callbacks + osd_output::pop(this); +} + + +//------------------------------------------------- +// validate_drivers - master validity checker +//------------------------------------------------- + +void validity_checker::validate_one(const game_driver &driver) +{ + // help verbose validation detect configuration-related crashes + if (m_print_verbose) + output_via_delegate(OSD_OUTPUT_CHANNEL_ERROR, "Validating driver %s (%s)...\n", driver.name, core_filename_extract_base(driver.type.source())); + + // set the current driver + m_current_driver = &driver; + m_current_device = nullptr; + m_current_ioport = nullptr; + m_region_map.clear(); + m_ioport_set.clear(); + m_checking_card = false; + + // reset error/warning state + int start_errors = m_errors; + int start_warnings = m_warnings; + m_error_text.clear(); + m_warning_text.clear(); + m_verbose_text.clear(); + + // wrap in try/catch to catch fatalerrors + try + { + machine_config config(driver, m_blank_options); + validate_driver(config.root_device()); + validate_roms(config.root_device()); + validate_inputs(config.root_device()); + validate_devices(config); + } + catch (emu_fatalerror const &err) + { + osd_printf_error("Fatal error %s", err.what()); + } + + // if we had warnings or errors, output + if (m_errors > start_errors || m_warnings > start_warnings || !m_verbose_text.empty()) + { + if (!m_print_verbose) + output_via_delegate(OSD_OUTPUT_CHANNEL_ERROR, "Driver %s (file %s): ", driver.name, core_filename_extract_base(driver.type.source())); + output_via_delegate(OSD_OUTPUT_CHANNEL_ERROR, "%d errors, %d warnings\n", m_errors - start_errors, m_warnings - start_warnings); + if (m_errors > start_errors) + output_indented_errors(m_error_text, "Errors"); + if (m_warnings > start_warnings) + output_indented_errors(m_warning_text, "Warnings"); + if (!m_verbose_text.empty()) + output_indented_errors(m_verbose_text, "Messages"); + output_via_delegate(OSD_OUTPUT_CHANNEL_ERROR, "\n"); + } + + // reset the driver/device + m_current_driver = nullptr; + m_current_device = nullptr; + m_current_ioport = nullptr; + m_region_map.clear(); + m_ioport_set.clear(); + m_checking_card = false; +} + + //------------------------------------------------- // validate_driver - validate basic driver // information @@ -1822,16 +2311,16 @@ void validity_checker::validate_dip_settings(const ioport_field &field) auto const nextsetting = std::next(setting); if (field.settings().end() != nextsetting) { - // check for inverted off/on dispswitch order + // check for inverted off/on DIP switch order int next_strindex = get_defstr_index(nextsetting->name(), true); if (strindex == INPUT_STRING_On && next_strindex == INPUT_STRING_Off) osd_printf_error("%s option must have Off/On options in the order: Off, On\n", field.name()); - // check for inverted yes/no dispswitch order + // check for inverted yes/no DIP switch order else if (strindex == INPUT_STRING_Yes && next_strindex == INPUT_STRING_No) osd_printf_error("%s option must have Yes/No options in the order: No, Yes\n", field.name()); - // check for inverted upright/cocktail dispswitch order + // check for inverted upright/cocktail DIP switch order else if (strindex == INPUT_STRING_Cocktail && next_strindex == INPUT_STRING_Upright) osd_printf_error("%s option must have Upright/Cocktail options in the order: Upright, Cocktail\n", field.name()); -- cgit v1.2.3