diff options
Diffstat (limited to 'src/emu/validity.cpp')
-rw-r--r-- | src/emu/validity.cpp | 1373 |
1 files changed, 1005 insertions, 368 deletions
diff --git a/src/emu/validity.cpp b/src/emu/validity.cpp index c8b5b52892c..4cb6202b4d9 100644 --- a/src/emu/validity.cpp +++ b/src/emu/validity.cpp @@ -12,345 +12,140 @@ #include "validity.h" #include "emuopts.h" +#include "main.h" #include "romload.h" +#include "speaker.h" #include "video/rgbutil.h" +#include "corestr.h" +#include "path.h" +#include "unicode.h" + #include <cctype> #include <type_traits> #include <typeinfo> -//************************************************************************** -// 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<char (void const *&)>; //------------------------------------------------- -// random_u64 -// random_s64 -// random_u32 -// random_s32 -//------------------------------------------------- -#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); } - - - -//------------------------------------------------- -// validate_tag - ensure that the given tag -// meets the general requirements +// make_diamond_class_delegate - make a delegate +// bound to an instance of an incomplete class +// type //------------------------------------------------- -void validity_checker::validate_tag(const char *tag) +test_delegate make_diamond_class_delegate(char (diamond_inheritance::*func)(void const *&), diamond_inheritance *obj) { - // 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); + return test_delegate(func, obj); } - -//************************************************************************** -// VALIDATION FUNCTIONS -//************************************************************************** - //------------------------------------------------- -// validity_checker - constructor +// virtual_base - simple class that will be used +// as the top vertex of the diamond //------------------------------------------------- -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_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_base(void const *&p) { p = this; return 'x'; } + int x; +}; -//------------------------------------------------- -// validity_checker - destructor -//------------------------------------------------- - -validity_checker::~validity_checker() -{ - validate_end(); -} //------------------------------------------------- -// check_driver - check a single driver +// virtual_derived_a - first class derived from +// virtual base //------------------------------------------------- -void validity_checker::check_driver(const game_driver &driver) +struct virtual_derived_a : virtual virtual_base { - // simply validate the one driver - validate_begin(); - validate_one(driver); - validate_end(); -} + char get_derived_a(void const *&p) { p = this; return 'a'; } + int a; +}; //------------------------------------------------- -// check_shared_source - check all drivers that -// share the same source file as the given driver +// virtual_derived_b - second class derived from +// virtual base //------------------------------------------------- -void validity_checker::check_shared_source(const game_driver &driver) +struct virtual_derived_b : virtual virtual_base { - // 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(); -} + char get_derived_b(void const *&p) { p = this; return 'b'; } + int b; +}; //------------------------------------------------- -// check_all_matching - check all drivers whose -// names match the given string +// diamond_inheritance - actual definition of +// class with diamond inheritance //------------------------------------------------- -bool validity_checker::check_all_matching(const char *string) +class diamond_inheritance : public virtual_derived_a, public virtual_derived_b { - // 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); -} +}; //------------------------------------------------- -// validate_begin - prepare for validation by -// taking over the output callbacks and resetting -// our internal state +// pure_virtual_base - abstract class with a +// vtable //------------------------------------------------- -void validity_checker::validate_begin() +struct pure_virtual_base { - // 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(); -} + virtual ~pure_virtual_base() = default; + virtual char operator()(void const *&p) const = 0; +}; //------------------------------------------------- -// validate_end - restore output callbacks and -// clean up +// ioport_string_from_index - return an indexed +// string from the I/O port system //------------------------------------------------- -void validity_checker::validate_end() +inline char const *ioport_string_from_index(u32 index) { - // restore the original output callbacks - osd_output::pop(this); + return ioport_configurer::string_from_token(reinterpret_cast<char const *>(uintptr_t(index))); } //------------------------------------------------- -// validate_drivers - master validity checker +// random_u64 +// random_s64 +// random_u32 +// random_s32 //------------------------------------------------- - -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()).c_str()); - - // 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()).c_str()); - 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; -} +#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_core - validate core internal systems +// validate_integer_semantics - validate that +// integers behave as expected, particularly +// with regards to overflow and shifting //------------------------------------------------- -void validity_checker::validate_core() +void validate_integer_semantics() { // basic system checks if (~0 != -1) osd_printf_error("Machine must be two's complement\n"); @@ -403,7 +198,7 @@ void validity_checker::validate_core() // behaviors //------------------------------------------------- -void validity_checker::validate_inlines() +void validate_inlines() { volatile u64 testu64a = random_u64(); volatile s64 testi64a = random_i64(); @@ -474,13 +269,13 @@ void validity_checker::validate_inlines() if (resultu32 != expectedu32) osd_printf_error("Error testing divu_64x32 (%16X / %08X) = %08X (expected %08X)\n", u64(testu64a), u32(testu32a), resultu32, expectedu32); - resulti32 = div_64x32_rem(testi64a, testi32a, &remainder); + resulti32 = div_64x32_rem(testi64a, testi32a, remainder); expectedi32 = testi64a / s64(testi32a); expremainder = testi64a % s64(testi32a); if (resulti32 != expectedi32 || remainder != expremainder) osd_printf_error("Error testing div_64x32_rem (%16X / %08X) = %08X,%08X (expected %08X,%08X)\n", s64(testi64a), s32(testi32a), resulti32, remainder, expectedi32, expremainder); - resultu32 = divu_64x32_rem(testu64a, testu32a, &uremainder); + resultu32 = divu_64x32_rem(testu64a, testu32a, uremainder); expectedu32 = testu64a / u64(testu32a); expuremainder = testu64a % u64(testu32a); if (resultu32 != expectedu32 || uremainder != expuremainder) @@ -517,14 +312,42 @@ void validity_checker::validate_inlines() for (int i = 0; i <= 32; i++) { u32 t = i < 32 ? (1 << (31 - i) | testu32a >> i) : 0; - u8 resultu8 = count_leading_zeros(t); + u8 resultu8 = count_leading_zeros_32(t); if (resultu8 != i) - osd_printf_error("Error testing count_leading_zeros %08x=%02x (expected %02x)\n", t, resultu8, i); + osd_printf_error("Error testing count_leading_zeros_32 %08x=%02x (expected %02x)\n", t, resultu8, i); t ^= 0xffffffff; - resultu8 = count_leading_ones(t); + resultu8 = count_leading_ones_32(t); if (resultu8 != i) - osd_printf_error("Error testing count_leading_ones %08x=%02x (expected %02x)\n", t, resultu8, i); + osd_printf_error("Error testing count_leading_ones_32 %08x=%02x (expected %02x)\n", t, resultu8, i); + } + + u32 expected32 = testu32a << 1 | testu32a >> 31; + for (int i = -33; i <= 33; i++) + { + u32 resultu32r = rotr_32(testu32a, i); + u32 resultu32l = rotl_32(testu32a, -i); + + if (resultu32r != expected32) + osd_printf_error("Error testing rotr_32 %08x, %d=%08x (expected %08x)\n", u32(testu32a), i, resultu32r, expected32); + if (resultu32l != expected32) + osd_printf_error("Error testing rotl_32 %08x, %d=%08x (expected %08x)\n", u32(testu32a), -i, resultu32l, expected32); + + expected32 = expected32 >> 1 | expected32 << 31; + } + + u64 expected64 = testu64a << 1 | testu64a >> 63; + for (int i = -65; i <= 65; i++) + { + u64 resultu64r = rotr_64(testu64a, i); + u64 resultu64l = rotl_64(testu64a, -i); + + if (resultu64r != expected64) + osd_printf_error("Error testing rotr_64 %016x, %d=%016x (expected %016x)\n", u64(testu64a), i, resultu64r, expected64); + if (resultu64l != expected64) + osd_printf_error("Error testing rotl_64 %016x, %d=%016x (expected %016x)\n", u64(testu64a), -i, resultu64l, expected64); + + expected64 = expected64 >> 1 | expected64 << 63; } } @@ -534,7 +357,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 @@ -564,29 +387,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<s32>::min()) || (result == std::numeric_limits<s32>::max())); - return result; - }; + auto random_i32_nolimit = + [] () + { + s32 result; + do { result = random_i32(); } while ((result == std::numeric_limits<s32>::min()) || (result == std::numeric_limits<s32>::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(); @@ -1009,6 +834,15 @@ void validity_checker::validate_rgb() rgb.shl(rgbaint_t(19, 3, 21, 6)); check_expected("rgbaint_t::shl"); + // test shift left out of range + expected_a = (actual_a = random_i32()) & 0; + expected_r = (actual_r = random_i32()) & 0; + expected_g = (actual_g = random_i32()) & 0; + expected_b = (actual_b = random_i32()) & 0; + rgb.set(actual_a, actual_r, actual_g, actual_b); + rgb.shl(rgbaint_t(-19, 32, -21, 38)); + check_expected("rgbaint_t::shl"); + // test shift left immediate expected_a = (actual_a = random_i32()) << 7; expected_r = (actual_r = random_i32()) << 7; @@ -1018,6 +852,15 @@ void validity_checker::validate_rgb() rgb.shl_imm(7); check_expected("rgbaint_t::shl_imm"); + // test shift left immediate out of range + expected_a = (actual_a = random_i32()) & 0; + expected_r = (actual_r = random_i32()) & 0; + expected_g = (actual_g = random_i32()) & 0; + expected_b = (actual_b = random_i32()) & 0; + rgb.set(actual_a, actual_r, actual_g, actual_b); + rgb.shl_imm(32); + check_expected("rgbaint_t::shl_imm"); + // test logical shift right expected_a = s32(u32(actual_a = random_i32()) >> 8); expected_r = s32(u32(actual_r = random_i32()) >> 18); @@ -1036,6 +879,15 @@ void validity_checker::validate_rgb() rgb.shr(rgbaint_t(21, 13, 11, 17)); check_expected("rgbaint_t::shr"); + // test logical shift right out of range + expected_a = (actual_a = random_i32()) & 0; + expected_r = (actual_r = random_i32()) & 0; + expected_g = (actual_g = random_i32()) & 0; + expected_b = (actual_b = random_i32()) & 0; + rgb.set(actual_a, actual_r, actual_g, actual_b); + rgb.shr(rgbaint_t(40, -18, -26, 32)); + check_expected("rgbaint_t::shr"); + // test logical shift right immediate expected_a = s32(u32(actual_a = random_i32()) >> 5); expected_r = s32(u32(actual_r = random_i32()) >> 5); @@ -1054,6 +906,15 @@ void validity_checker::validate_rgb() rgb.shr_imm(15); check_expected("rgbaint_t::shr_imm"); + // test logical shift right immediate out of range + expected_a = (actual_a = random_i32()) & 0; + expected_r = (actual_r = random_i32()) & 0; + expected_g = (actual_g = random_i32()) & 0; + expected_b = (actual_b = random_i32()) & 0; + rgb.set(actual_a, actual_r, actual_g, actual_b); + rgb.shr_imm(35); + check_expected("rgbaint_t::shr_imm"); + // test arithmetic shift right expected_a = (actual_a = random_i32()) >> 16; expected_r = (actual_r = random_i32()) >> 20; @@ -1072,6 +933,15 @@ void validity_checker::validate_rgb() rgb.sra(rgbaint_t(1, 29, 10, 22)); check_expected("rgbaint_t::sra"); + // test arithmetic shift right out of range + expected_a = (actual_a = random_i32()) >> 31; + expected_r = (actual_r = random_i32()) >> 31; + expected_g = (actual_g = random_i32()) >> 31; + expected_b = (actual_b = random_i32()) >> 31; + rgb.set(actual_a, actual_r, actual_g, actual_b); + rgb.sra(rgbaint_t(-16, -20, 46, 32)); + check_expected("rgbaint_t::sra"); + // test arithmetic shift right immediate (method) expected_a = (actual_a = random_i32()) >> 12; expected_r = (actual_r = random_i32()) >> 12; @@ -1090,6 +960,15 @@ void validity_checker::validate_rgb() rgb.sra_imm(9); check_expected("rgbaint_t::sra_imm"); + // test arithmetic shift right immediate out of range (method) + expected_a = (actual_a = random_i32()) >> 31; + expected_r = (actual_r = random_i32()) >> 31; + expected_g = (actual_g = random_i32()) >> 31; + expected_b = (actual_b = random_i32()) >> 31; + rgb.set(actual_a, actual_r, actual_g, actual_b); + rgb.sra_imm(38); + check_expected("rgbaint_t::sra_imm"); + // test arithmetic shift right immediate (operator) expected_a = (actual_a = random_i32()) >> 7; expected_r = (actual_r = random_i32()) >> 7; @@ -1108,6 +987,15 @@ void validity_checker::validate_rgb() rgb >>= 11; check_expected("rgbaint_t::operator>>="); + // test arithmetic shift right immediate out of range (operator) + expected_a = (actual_a = random_i32()) >> 31; + expected_r = (actual_r = random_i32()) >> 31; + expected_g = (actual_g = random_i32()) >> 31; + expected_b = (actual_b = random_i32()) >> 31; + rgb.set(actual_a, actual_r, actual_g, actual_b); + rgb >>= 41; + check_expected("rgbaint_t::operator>>="); + // test RGB equality comparison actual_a = random_i32_nolimit(); actual_r = random_i32_nolimit(); @@ -1452,6 +1340,711 @@ 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<base_a *>(&mi) != addr) + osd_printf_error("Error testing delegate this pointer adjustment %p -> %p (expected %p)\n", static_cast<void const *>(&mi), addr, static_cast<void const *>(static_cast<base_a *>(&mi))); + + addr = nullptr; + ch = cb2(addr); + if ('b' != ch) + osd_printf_error("Error testing delegate non-virtual member function dispatch\n"); + if (static_cast<base_b *>(&mi) != addr) + osd_printf_error("Error testing delegate this pointer adjustment %p -> %p (expected %p)\n", static_cast<void const *>(&mi), addr, static_cast<void const *>(static_cast<base_b *>(&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<base_a *>(&mi) != addr) + osd_printf_error("Error testing copy constructed delegate this pointer adjustment %p -> %p (expected %p)\n", static_cast<void const *>(&mi), addr, static_cast<void const *>(static_cast<base_a *>(&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<base_b *>(&mi) != addr) + osd_printf_error("Error testing copy constructed delegate this pointer adjustment %p -> %p (expected %p)\n", static_cast<void const *>(&mi), addr, static_cast<void const *>(static_cast<base_b *>(&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<base_b *>(&mi) != addr) + osd_printf_error("Error testing assigned delegate this pointer adjustment %p -> %p (expected %p)\n", static_cast<void const *>(&mi), addr, static_cast<void const *>(static_cast<base_b *>(&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<base_a *>(&mi) != addr) + osd_printf_error("Error testing assigned delegate this pointer adjustment %p -> %p (expected %p)\n", static_cast<void const *>(&mi), addr, static_cast<void const *>(static_cast<base_a *>(&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<base_a *>(&mi) != addr) + osd_printf_error("Error testing delegate this pointer adjustment for virtual member function %p -> %p (expected %p)\n", static_cast<void const *>(&mi), addr, static_cast<void const *>(static_cast<base_a *>(&mi))); + + addr = nullptr; + ch = cb2(addr); + if ('B' != ch) + osd_printf_error("Error testing delegate virtual member function dispatch\n"); + if (static_cast<base_b *>(&mi) != addr) + osd_printf_error("Error testing delegate this pointer adjustment for virtual member function %p -> %p (expected %p)\n", static_cast<void const *>(&mi), addr, static_cast<void const *>(static_cast<base_b *>(&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<base_a *>(&mi) != addr) + osd_printf_error("Error testing copy constructed delegate this pointer adjustment for virtual member function %p -> %p (expected %p)\n", static_cast<void const *>(&mi), addr, static_cast<void const *>(static_cast<base_a *>(&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<base_b *>(&mi) != addr) + osd_printf_error("Error testing copy constructed delegate this pointer adjustment for virtual member function %p -> %p (expected %p)\n", static_cast<void const *>(&mi), addr, static_cast<void const *>(static_cast<base_b *>(&mi))); + + // test virtual member function dispatch through base pointer + cb1 = test_delegate(&base_a::get_a_v, static_cast<base_a *>(&o)); + cb2 = test_delegate(&base_b::get_b_v, static_cast<base_b *>(&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<void const *>(static_cast<base_a *>(&o)), addr, static_cast<void const *>(&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<void const *>(static_cast<base_b *>(&o)), addr, static_cast<void const *>(&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<virtual_derived_a *>(&d) != addr) + osd_printf_error("Error testing delegate this pointer adjustment for incomplete class %p -> %p (expected %p)\n", static_cast<void const *>(&d), addr, static_cast<void const *>(static_cast<virtual_derived_b *>(&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<virtual_derived_b *>(&d) != addr) + osd_printf_error("Error testing delegate this pointer adjustment for incomplete class %p -> %p (expected %p)\n", static_cast<void const *>(&d), addr, static_cast<void const *>(static_cast<virtual_derived_b *>(&d))); + +#if defined(_MSC_VER) && !defined(__clang__) + // test MSVC extension allowing casting member pointer types across virtual inheritance relationships + 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<virtual_base *>(&d) != addr) + osd_printf_error("Error testing delegate this pointer adjustment for incomplete class %p -> %p (expected %p)\n", static_cast<void const *>(&d), addr, static_cast<void const *>(static_cast<virtual_base *>(&d))); +#endif // defined(_MSC_VER) && !defined(__clang__) +} + + +//------------------------------------------------- +// validate_delegates_latebind - test binding a +// delegate to an object after the function is +// set +//------------------------------------------------- + +void validate_delegates_latebind() +{ + struct derived_a : pure_virtual_base, delegate_late_bind + { + virtual char operator()(void const *&p) const override { p = this; return 'a'; } + }; + + struct derived_b : pure_virtual_base, 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(&pure_virtual_base::operator(), static_cast<pure_virtual_base *>(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(pure_virtual_base)) || (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(pure_virtual_base).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<char (void const *&), pure_virtual_base> cb4( + [] (auto &o, void const *&p) { p = &o; return 'l'; }, + static_cast<unrelated *>(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<void (void const *&)>; + 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<void const *>(&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<void const *>(&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<void const *>(&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<void const *>(&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<void const *>(&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<void const *>(&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(); + m_slotcard_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 //------------------------------------------------- @@ -1491,6 +2084,10 @@ void validity_checker::validate_driver(device_t &root) if (clone_of != -1 && (clone_of = driver_list::non_bios_clone(clone_of)) != -1) osd_printf_error("Driver is a clone of a clone\n"); + // look for drivers specifying a parent ROM device type + if (root.type().parent_rom_device_type()) + osd_printf_error("Driver has parent ROM device type '%s'\n", root.type().parent_rom_device_type()->shortname()); + // make sure the driver name is not too long if (!is_clone && strlen(m_current_driver->name) > 16) osd_printf_error("Parent driver name must be 16 characters or less\n"); @@ -1539,18 +2136,18 @@ void validity_checker::validate_driver(device_t &root) device_t::feature_type const imperfect(m_current_driver->type.imperfect_features()); if (!(m_current_driver->flags & (machine_flags::IS_BIOS_ROOT | machine_flags::NO_SOUND_HW)) && !(unemulated & device_t::feature::SOUND)) { - sound_interface_iterator iter(root); + speaker_device_enumerator iter(root); if (!iter.first()) osd_printf_error("Driver is missing MACHINE_NO_SOUND or MACHINE_NO_SOUND_HW flag\n"); } // catch invalid flag combinations if (unemulated & ~device_t::feature::ALL) - osd_printf_error("Driver has invalid unemulated feature flags (0x%08lX)\n", static_cast<unsigned long>(unemulated & ~device_t::feature::ALL)); + osd_printf_error("Driver has invalid unemulated feature flags (0x%08X)\n", util::underlying_value(unemulated & ~device_t::feature::ALL)); if (imperfect & ~device_t::feature::ALL) - osd_printf_error("Driver has invalid imperfect feature flags (0x%08lX)\n", static_cast<unsigned long>(imperfect & ~device_t::feature::ALL)); + osd_printf_error("Driver has invalid imperfect feature flags (0x%08X)\n", util::underlying_value(imperfect & ~device_t::feature::ALL)); if (unemulated & imperfect) - osd_printf_error("Driver cannot have features that are both unemulated and imperfect (0x%08lX)\n", static_cast<unsigned long>(unemulated & imperfect)); + osd_printf_error("Driver cannot have features that are both unemulated and imperfect (0x%08X)\n", util::underlying_value(unemulated & imperfect)); if ((m_current_driver->flags & machine_flags::NO_SOUND_HW) && ((unemulated | imperfect) & device_t::feature::SOUND)) osd_printf_error("Machine without sound hardware cannot have unemulated/imperfect sound\n"); } @@ -1563,7 +2160,7 @@ void validity_checker::validate_driver(device_t &root) void validity_checker::validate_roms(device_t &root) { // iterate, starting with the driver's ROMs and continuing with device ROMs - for (device_t &device : device_iterator(root)) + for (device_t &device : device_enumerator(root)) { // track the current device m_current_device = &device; @@ -1574,7 +2171,6 @@ void validity_checker::validate_roms(device_t &root) u32 current_length = 0; int items_since_region = 1; int last_bios = 0, max_bios = 0; - int total_files = 0; std::unordered_map<std::string, int> bios_names; std::unordered_map<std::string, std::string> bios_descs; char const *defbios = nullptr; @@ -1608,34 +2204,46 @@ void validity_checker::validate_roms(device_t &root) current_length = ROMREGION_GETLENGTH(romp); if (!m_region_map.emplace(fulltag, current_length).second) osd_printf_error("Multiple ROM_REGIONs with the same tag '%s' defined\n", fulltag); + if (current_length == 0) + osd_printf_error("ROM region '%s' has zero length\n", fulltag); } else if (ROMENTRY_ISSYSTEM_BIOS(romp)) // If this is a system bios, make sure it is using the next available bios number { int const bios_flags = ROM_GETBIOSFLAGS(romp); char const *const biosname = romp->name; if (bios_flags != last_bios + 1) - osd_printf_error("Non-sequential BIOS %s (specified as %d, expected to be %d)\n", biosname, bios_flags - 1, last_bios); + osd_printf_error("Non-sequential BIOS %s (specified as %d, expected to be %d)\n", biosname ? biosname : "UNNAMED", bios_flags - 1, last_bios); last_bios = bios_flags; // validate the name - if (strlen(biosname) > 16) - osd_printf_error("BIOS name %s exceeds maximum 16 characters\n", biosname); - for (char const *s = biosname; *s; ++s) + if (!biosname || biosname[0] == 0) + osd_printf_error("BIOS %d is missing a name\n", bios_flags - 1); + else { - if (((*s < '0') || (*s > '9')) && ((*s < 'a') || (*s > 'z')) && (*s != '.') && (*s != '_') && (*s != '-')) + if (strlen(biosname) > 16) + osd_printf_error("BIOS name %s exceeds maximum 16 characters\n", biosname); + for (char const *s = biosname; *s; ++s) { - osd_printf_error("BIOS name %s contains invalid characters\n", biosname); - break; + if (((*s < '0') || (*s > '9')) && ((*s < 'a') || (*s > 'z')) && (*s != '.') && (*s != '_') && (*s != '-')) + { + osd_printf_error("BIOS name %s contains invalid characters\n", biosname); + break; + } } - } - // check for duplicate names/descriptions - auto const nameins = bios_names.emplace(biosname, bios_flags); - if (!nameins.second) - osd_printf_error("Duplicate BIOS name %s specified (%d and %d)\n", biosname, nameins.first->second, bios_flags - 1); - auto const descins = bios_descs.emplace(romp->hashdata, biosname); - if (!descins.second) - osd_printf_error("BIOS %s has duplicate description '%s' (was %s)\n", biosname, romp->hashdata, descins.first->second); + // check for duplicate names/descriptions + auto const nameins = bios_names.emplace(biosname, bios_flags); + if (!nameins.second) + osd_printf_error("Duplicate BIOS name %s specified (%d and %d)\n", biosname, nameins.first->second, bios_flags - 1); + if (!romp->hashdata || romp->hashdata[0] == 0) + osd_printf_error("BIOS %s has empty description\n", biosname); + else + { + auto const descins = bios_descs.emplace(romp->hashdata, biosname); + if (!descins.second) + osd_printf_error("BIOS %s has duplicate description '%s' (was %s)\n", biosname, romp->hashdata, descins.first->second); + } + } } else if (ROMENTRY_ISDEFAULT_BIOS(romp)) // if this is a default BIOS setting, remember it so it to check at the end { @@ -1645,7 +2253,6 @@ void validity_checker::validate_roms(device_t &root) { // track the last filename we found last_name = romp->name; - total_files++; max_bios = std::max<int>(max_bios, ROM_GETBIOSFLAGS(romp)); // validate the name @@ -1704,7 +2311,7 @@ void validity_checker::validate_roms(device_t &root) // analog input field //------------------------------------------------- -void validity_checker::validate_analog_input_field(ioport_field &field) +void validity_checker::validate_analog_input_field(const ioport_field &field) { // analog ports must have a valid sensitivity if (field.sensitivity() == 0) @@ -1785,54 +2392,56 @@ void validity_checker::validate_analog_input_field(ioport_field &field) // setting //------------------------------------------------- -void validity_checker::validate_dip_settings(ioport_field &field) +void validity_checker::validate_dip_settings(const ioport_field &field) { - const char *demo_sounds = ioport_string_from_index(INPUT_STRING_Demo_Sounds); - const char *flipscreen = ioport_string_from_index(INPUT_STRING_Flip_Screen); + char const *const demo_sounds = ioport_string_from_index(INPUT_STRING_Demo_Sounds); + char const *const flipscreen = ioport_string_from_index(INPUT_STRING_Flip_Screen); + char const *const name = field.specific_name() ? field.specific_name() : "UNNAMED"; u8 coin_list[__input_string_coinage_end + 1 - __input_string_coinage_start] = { 0 }; bool coin_error = false; // iterate through the settings - for (ioport_setting &setting : field.settings()) + for (auto setting = field.settings().begin(); field.settings().end() != setting; ++setting) { // note any coinage strings - int strindex = get_defstr_index(setting.name()); + int strindex = get_defstr_index(setting->name()); if (strindex >= __input_string_coinage_start && strindex <= __input_string_coinage_end) coin_list[strindex - __input_string_coinage_start] = 1; // make sure demo sounds default to on - if (field.name() == demo_sounds && strindex == INPUT_STRING_On && field.defvalue() != setting.value()) + if (name == demo_sounds && strindex == INPUT_STRING_On && field.defvalue() != setting->value()) osd_printf_error("Demo Sounds must default to On\n"); // check for bad demo sounds options - if (field.name() == demo_sounds && (strindex == INPUT_STRING_Yes || strindex == INPUT_STRING_No)) - osd_printf_error("Demo Sounds option must be Off/On, not %s\n", setting.name()); + if (name == demo_sounds && (strindex == INPUT_STRING_Yes || strindex == INPUT_STRING_No)) + osd_printf_error("Demo Sounds option must be Off/On, not %s\n", setting->name()); // check for bad flip screen options - if (field.name() == flipscreen && (strindex == INPUT_STRING_Yes || strindex == INPUT_STRING_No)) - osd_printf_error("Flip Screen option must be Off/On, not %s\n", setting.name()); + if (name == flipscreen && (strindex == INPUT_STRING_Yes || strindex == INPUT_STRING_No)) + osd_printf_error("Flip Screen option must be Off/On, not %s\n", setting->name()); // if we have a neighbor, compare ourselves to him - if (setting.next() != nullptr) + auto const nextsetting = std::next(setting); + if (field.settings().end() != nextsetting) { - // check for inverted off/on dispswitch order - int next_strindex = get_defstr_index(setting.next()->name(), true); + // 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()); + osd_printf_error("%s option must have Off/On options in the order: Off, On\n", 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()); + osd_printf_error("%s option must have Yes/No options in the order: No, Yes\n", 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()); + osd_printf_error("%s option must have Upright/Cocktail options in the order: Upright, Cocktail\n", name); // check for proper coin ordering else if (strindex >= __input_string_coinage_start && strindex <= __input_string_coinage_end && next_strindex >= __input_string_coinage_start && next_strindex <= __input_string_coinage_end && - strindex >= next_strindex && setting.condition() == setting.next()->condition()) + strindex >= next_strindex && setting->condition() == nextsetting->condition()) { - osd_printf_error("%s option has unsorted coinage %s > %s\n", field.name(), setting.name(), setting.next()->name()); + osd_printf_error("%s option has unsorted coinage %s > %s\n", name, setting->name(), nextsetting->name()); coin_error = true; } } @@ -1842,7 +2451,7 @@ void validity_checker::validate_dip_settings(ioport_field &field) if (coin_error) { output_via_delegate(OSD_OUTPUT_CHANNEL_ERROR, " Note proper coin sort order should be:\n"); - for (int entry = 0; entry < ARRAY_LENGTH(coin_list); entry++) + for (int entry = 0; entry < std::size(coin_list); entry++) if (coin_list[entry]) output_via_delegate(OSD_OUTPUT_CHANNEL_ERROR, " %s\n", ioport_string_from_index(__input_string_coinage_start + entry)); } @@ -1854,8 +2463,14 @@ void validity_checker::validate_dip_settings(ioport_field &field) // stored within an ioport field or setting //------------------------------------------------- -void validity_checker::validate_condition(ioport_condition &condition, device_t &device) +void validity_checker::validate_condition(const ioport_condition &condition, device_t &device) { + if (condition.tag() == nullptr) + { + osd_printf_error("Condition referencing null ioport tag\n"); + return; + } + // resolve the tag, then find a matching port if (m_ioport_set.find(device.subtag(condition.tag())) == m_ioport_set.end()) osd_printf_error("Condition referencing non-existent ioport tag '%s'\n", condition.tag()); @@ -1869,7 +2484,7 @@ void validity_checker::validate_condition(ioport_condition &condition, device_t void validity_checker::validate_inputs(device_t &root) { // iterate over devices - for (device_t &device : device_iterator(root)) + for (device_t &device : device_enumerator(root)) { // see if this device has ports; if not continue if (device.input_ports() == nullptr) @@ -1914,40 +2529,49 @@ void validity_checker::validate_inputs(device_t &root) } // iterate through the fields on this port - for (ioport_field &field : port.second->fields()) + for (ioport_field const &field : port.second->fields()) { // verify analog inputs if (field.is_analog()) validate_analog_input_field(field); - // look for invalid (0) types which should be mapped to IPT_OTHER - if (field.type() == IPT_INVALID) + // checks based on field type + if ((field.type() > IPT_UI_FIRST) && (field.type() < IPT_UI_LAST)) + { + osd_printf_error("Field has invalid UI control type\n"); + } + else if (field.type() == IPT_INVALID) + { osd_printf_error("Field has an invalid type (0); use IPT_OTHER instead\n"); - - if (field.type() == IPT_SPECIAL) + } + else if (field.type() == IPT_SPECIAL) + { osd_printf_error("Field has an invalid type IPT_SPECIAL\n"); - - // verify dip switches - if (field.type() == IPT_DIPSWITCH) + } + else if (field.type() == IPT_DIPSWITCH) { - // dip switch fields must have a specific name + // DIP switch fields must have a specific name if (field.specific_name() == nullptr) osd_printf_error("DIP switch has no specific name\n"); // verify the settings list validate_dip_settings(field); } - - // verify config settings - if (field.type() == IPT_CONFIG) + else if (field.type() == IPT_CONFIG) { // config fields must have a specific name if (field.specific_name() == nullptr) osd_printf_error("Config switch has no specific name\n"); } + else if (field.type() == IPT_ADJUSTER) + { + // adjuster fields must have a specific name + if (field.specific_name() == nullptr) + osd_printf_error("Adjuster has no specific name\n"); + } // verify names - const char *name = field.specific_name(); + char const *const name = field.specific_name(); if (name != nullptr) { // check for empty string @@ -1961,9 +2585,6 @@ void validity_checker::validate_inputs(device_t &root) // check for invalid UTF-8 if (!utf8_is_valid_string(name)) osd_printf_error("Field '%s' has invalid characters\n", name); - - // look up the string and print an error if default strings are not used - /*strindex =get_defstr_index(defstr_map, name, driver, &error);*/ } // verify conditions on the field @@ -1971,7 +2592,7 @@ void validity_checker::validate_inputs(device_t &root) validate_condition(field.condition(), device); // verify conditions on the settings - for (ioport_setting &setting : field.settings()) + for (ioport_setting const &setting : field.settings()) if (!setting.condition().none()) validate_condition(setting.condition(), device); @@ -2011,7 +2632,7 @@ void validity_checker::validate_devices(machine_config &config) { std::unordered_set<std::string> device_map; - for (device_t &device : device_iterator(config.root_device())) + for (device_t &device : device_enumerator(config.root_device())) { // track the current device m_current_device = &device; @@ -2043,6 +2664,10 @@ void validity_checker::validate_devices(machine_config &config) if (slot->default_option() != nullptr && option.first == slot->default_option()) continue; + // if we need to save time, instantiate and validate each slot card type at most once + if (m_quick && !m_slotcard_set.insert(option.second->devtype().shortname()).second) + continue; + m_checking_card = true; device_t *card; { @@ -2057,7 +2682,7 @@ void validity_checker::validate_devices(machine_config &config) additions(card); } - for (device_slot_interface &subslot : slot_interface_iterator(*card)) + for (device_slot_interface &subslot : slot_interface_enumerator(*card)) { if (subslot.fixed()) { @@ -2077,14 +2702,15 @@ void validity_checker::validate_devices(machine_config &config) } } - for (device_t &card_dev : device_iterator(*card)) + for (device_t &card_dev : device_enumerator(*card)) card_dev.config_complete(); validate_roms(*card); - for (device_t &card_dev : device_iterator(*card)) + for (device_t &card_dev : device_enumerator(*card)) { m_current_device = &card_dev; card_dev.findit(this); + validate_tag(card_dev.basetag()); card_dev.validity_check(*this); m_current_device = nullptr; } @@ -2120,10 +2746,10 @@ void validity_checker::validate_device_types() device_t *const dev = config.device_add(type.shortname(), type, 0); char const *name((dev->shortname() && *dev->shortname()) ? dev->shortname() : type.type().name()); - std::string const description((dev->source() && *dev->source()) ? util::string_format("%s(%s)", core_filename_extract_base(dev->source()).c_str(), name) : name); + std::string const description((dev->source() && *dev->source()) ? util::string_format("%s(%s)", core_filename_extract_base(dev->source()), name) : name); if (m_print_verbose) - output_via_delegate(OSD_OUTPUT_CHANNEL_ERROR, "Validating device %s...\n", description.c_str()); + output_via_delegate(OSD_OUTPUT_CHANNEL_ERROR, "Validating device %s...\n", description); // ensure shortname exists if (!dev->shortname() || !*dev->shortname()) @@ -2199,11 +2825,22 @@ void validity_checker::validate_device_types() device_t::feature_type const unemulated(dev->type().unemulated_features()); device_t::feature_type const imperfect(dev->type().imperfect_features()); if (unemulated & ~device_t::feature::ALL) - osd_printf_error("Device has invalid unemulated feature flags (0x%08lX)\n", static_cast<unsigned long>(unemulated & ~device_t::feature::ALL)); + osd_printf_error("Device has invalid unemulated feature flags (0x%08X)\n", util::underlying_value(unemulated & ~device_t::feature::ALL)); if (imperfect & ~device_t::feature::ALL) - osd_printf_error("Device has invalid imperfect feature flags (0x%08lX)\n", static_cast<unsigned long>(imperfect & ~device_t::feature::ALL)); + osd_printf_error("Device has invalid imperfect feature flags (0x%08X)\n", util::underlying_value(imperfect & ~device_t::feature::ALL)); if (unemulated & imperfect) - osd_printf_error("Device cannot have features that are both unemulated and imperfect (0x%08lX)\n", static_cast<unsigned long>(unemulated & imperfect)); + osd_printf_error("Device cannot have features that are both unemulated and imperfect (0x%08X)\n", util::underlying_value(unemulated & imperfect)); + + // check that parents are only ever one generation deep + auto const parent(dev->type().parent_rom_device_type()); + if (parent) + { + auto const grandparent(parent->parent_rom_device_type()); + if ((dev->type() == *parent) || !strcmp(parent->shortname(), name)) + osd_printf_error("Device has parent ROM set that identical to its type\n"); + if (grandparent) + osd_printf_error("Device has parent ROM set '%s' which has parent ROM set '%s'\n", parent->shortname(), grandparent->shortname()); + } // give devices some of the same scrutiny that drivers get - necessary for cards not default for any slots validate_roms(*dev); @@ -2256,7 +2893,7 @@ void validity_checker::build_output_prefix(std::ostream &str) const // error_output - error message output override //------------------------------------------------- -void validity_checker::output_callback(osd_output_channel channel, const util::format_argument_pack<std::ostream> &args) +void validity_checker::output_callback(osd_output_channel channel, const util::format_argument_pack<char> &args) { std::ostringstream output; switch (channel) @@ -2326,5 +2963,5 @@ void validity_checker::output_indented_errors(std::string &text, const char *hea if (text[text.size()-1] == '\n') text.erase(text.size()-1, 1); strreplace(text, "\n", "\n "); - output_via_delegate(OSD_OUTPUT_CHANNEL_ERROR, "%s:\n %s\n", header, text.c_str()); + output_via_delegate(OSD_OUTPUT_CHANNEL_ERROR, "%s:\n %s\n", header, text); } |