dosbox-x/tests/dos_files_tests.cpp
Jaak Ristioja 4a6bcc706e
Use nullptr/NULL instead of 0 for pointers, two fixes for undefined behavior
This commit fixes most -Wzero-as-null-pointer-constant warnings, except in
some places which appear to be vendored 3rd party libraries, e.g. in sdl_ttf.c,
libchdr, physfs and gmock and in 3rd party components therein. Since it might
make more sense to update this 3rd party code to some later versions instead of
fixing the warnings manually, I decided not to enable the C++ compiler flag in
configure.ac at this point.

In two functions with the return type of std::string executed `return 0;`
statements, causing std::string to be constructed from a null pointer. To avoid
undefined behavior these were changed to `return {};`.

Signed-off-by: Jaak Ristioja <jaak@ristioja.ee>
2024-05-09 14:18:10 +03:00

397 lines
14 KiB
C++

/*
* SPDX-License-Identifier: GPL-2.0-or-later
*
* Copyright (C) 2020-2021 The DOSBox Staging Team
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "dos_inc.h"
#include <iterator>
#include <string>
#include <gtest/gtest.h>
#include "control.h"
#include "dos_system.h"
#include "shell.h"
#include "../src/dos/drives.h"
#include "dosbox_test_fixture.h"
void DTAExtendName(char * const name,char * const filename,char * const ext);
namespace {
class DOS_FilesTest : public DOSBoxTestFixture {};
// These aren't passed by reference to simplify the code, so that
// we can pass expectations as literals
void assert_DTAExtendName(std::string input,
std::string expected_name,
std::string expected_ext)
{
char *const input_str = const_cast<char *>(&input.c_str()[0]);
// char * const input_name = &input[0];
// needs to be minimum length of the input up to the dot + 1 (null)
char output_filename[DOS_PATHLENGTH];
char *const filename = &output_filename[0];
char output_ext[DOS_PATHLENGTH];
char *const ext = &output_ext[0];
DTAExtendName(input_str, filename, ext);
// mutates input up to dot
EXPECT_EQ(filename, expected_name);
EXPECT_EQ(ext, expected_ext);
}
void assert_DOS_MakeName(char const *const input,
bool exp_result,
std::string exp_fullname = "",
int exp_drive = 0)
{
uint8_t drive_result;
char fullname_result[DOS_PATHLENGTH];
bool result = DOS_MakeName(input, fullname_result, &drive_result);
EXPECT_EQ(result, exp_result);
// if we expected success, also test these
if (exp_result) {
EXPECT_EQ(std::string(fullname_result), exp_fullname);
EXPECT_EQ(drive_result, exp_drive);
}
}
TEST_F(DOS_FilesTest, DOS_MakeName_Basic_Failures)
{
// make sure we get failures, not explosions
assert_DOS_MakeName("\0", false);
assert_DOS_MakeName(" ", false);
assert_DOS_MakeName(" NAME", false);
assert_DOS_MakeName("\1:\\AUTOEXEC.BAT", false);
assert_DOS_MakeName(nullptr, false);
assert_DOS_MakeName("B:\\AUTOEXEC.BAT", false);
}
TEST_F(DOS_FilesTest, DOS_MakeName_Z_AUTOEXEC_BAT_exists)
{
assert_DOS_MakeName("Z:\\AUTOEXEC.BAT", true, "AUTOEXEC.BAT", 25);
}
// This captures a particularity of the DOSBox code where the
// drive index is set even though the path failed. this could have
// ramifications across the codebase if not replicated
TEST_F(DOS_FilesTest, DOS_MakeName_Drive_Index_Set_On_Failure)
{
for (int i=0;i<DOS_DRIVES-1;i++) {
if (Drives[i]) DriveManager::UnmountDrive(i);
Drives[i] = nullptr;
}
uint8_t drive_result;
char fullname_result[DOS_PATHLENGTH];
bool result;
result = DOS_MakeName("A:\r\n", fullname_result, &drive_result);
EXPECT_EQ(result, false);
EXPECT_EQ(drive_result, 0);
result = DOS_MakeName("B:\r\n", fullname_result, &drive_result);
EXPECT_EQ(drive_result, 1);
EXPECT_EQ(result, false);
result = DOS_MakeName("C:\r\n", fullname_result, &drive_result);
EXPECT_EQ(drive_result, 2);
EXPECT_EQ(result, false);
result = DOS_MakeName("Z:\r\n", fullname_result, &drive_result);
EXPECT_EQ(drive_result, 25);
//EXPECT_EQ(result, false);
}
TEST_F(DOS_FilesTest, DOS_MakeName_Uppercase)
{
bool oldlfn = uselfn;
uselfn = true;
assert_DOS_MakeName("Z:\\autoexec.bat", true, "autoexec.bat", 25);
safe_strcpy(Drives[25]->curdir, "Windows\\Folder");
assert_DOS_MakeName("autoexec.bat", true, "Windows\\Folder\\autoexec.bat", 25);
uselfn = false;
// lower case
assert_DOS_MakeName("Z:\\autoexec.bat", true, "AUTOEXEC.BAT", 25);
assert_DOS_MakeName("z:\\AUTOEXEC.BAT", true, "AUTOEXEC.BAT", 25);
// current dir isn't uppercased if it's not already
safe_strcpy(Drives[25]->curdir, "Windows\\Folder");
assert_DOS_MakeName("autoexec.bat", true, "Windows\\Folder\\AUTOEXEC.BAT", 25);
uselfn = oldlfn;
}
TEST_F(DOS_FilesTest, DOS_MakeName_CONVERTS_FWD_SLASH)
{
assert_DOS_MakeName("Z:/AUTOEXEC.BAT", true, "AUTOEXEC.BAT", 25);
assert_DOS_MakeName("Z://AUTOEXEC.BAT", true, "AUTOEXEC.BAT", 25);
assert_DOS_MakeName("Z:///AUTOEXEC.BAT", true, "AUTOEXEC.BAT", 25);
assert_DOS_MakeName("Z:/FOLDER/", true, "FOLDER", 25);
assert_DOS_MakeName("Z:/FOLDER/FILE", true, "FOLDER\\FILE", 25);}
// spaces get stripped out before processing (\t, \r, etc, are illegal chars,
// not whitespace)
TEST_F(DOS_FilesTest, DOS_MakeName_STRIP_SPACE)
{
bool oldlfn = uselfn;
uselfn = false;
safe_strcpy(Drives[25]->curdir, "");
assert_DOS_MakeName("Z:\\ A U T OE X EC .BAT", true,
"AUTOEXEC.BAT", 25);
assert_DOS_MakeName("Z: \\ A U T OE X EC .BAT", true,
"AUTOEXEC.BAT", 25);
assert_DOS_MakeName("12345 678.123", true, "12345678.123", 25);
assert_DOS_MakeName("Z:\\\\A\\ B \\CDE\\ E F G\\", true, "A\\B\\CDE\\EFG", 25);
// except here, whitespace isn't stripped & causes failure
//assert_DOS_MakeName("Z :\\AUTOEXEC.BAT", false);
uselfn = oldlfn;
}
TEST_F(DOS_FilesTest, DOS_MakeName_Dir_Handling)
{
assert_DOS_MakeName("Z:\\CODE\\", true, "CODE", 25);
assert_DOS_MakeName("Z:\\CODE\\AUTOEXEC.BAT", true, "CODE\\AUTOEXEC.BAT", 25);
assert_DOS_MakeName("Z:\\DIR\\UNTERM", true, "DIR\\UNTERM", 25);
// trailing gets trimmed
assert_DOS_MakeName("Z:\\CODE\\TERM\\", true, "CODE\\TERM", 25);
assert_DOS_MakeName("Z:\\CODE\\BIN\\", true, "CODE\\BIN", 25);
assert_DOS_MakeName("Z:\\CODE\\BIN\\\\", true, "CODE\\BIN", 25);
assert_DOS_MakeName("Z:\\CODE\\BIN\\\\\\\\", true, "CODE\\BIN", 25);
assert_DOS_MakeName("Z:\\CODE\\\\BIN\\\\\\\\", true, "CODE\\BIN", 25);}
TEST_F(DOS_FilesTest, DOS_MakeName_Assumes_Current_Drive_And_Dir)
{
bool oldlfn = uselfn;
uselfn = false;
// when passed only a filename, assume default drive and current dir
safe_strcpy(Drives[25]->curdir, "");
assert_DOS_MakeName("AUTOEXEC.BAT", true, "AUTOEXEC.BAT", 25);
// artificially change directory
safe_strcpy(Drives[25]->curdir, "CODE");
assert_DOS_MakeName("AUTOEXEC.BAT", true, "CODE\\AUTOEXEC.BAT", 25);
// artificially change directory
safe_strcpy(Drives[25]->curdir, "CODE\\BIN");
assert_DOS_MakeName("AUTOEXEC.BAT", true, "CODE\\BIN\\AUTOEXEC.BAT", 25);
// ignores current dir and goes to root
assert_DOS_MakeName("\\AUTOEXEC.BAT", true, "AUTOEXEC.BAT", 25);
safe_strcpy(Drives[25]->curdir, "");
assert_DOS_MakeName("Z:\\CODE\\BIN", true, "CODE\\BIN", 25);
assert_DOS_MakeName("Z:", true, "", 25);
assert_DOS_MakeName("Z:\\", true, "", 25);
// This is a bug but we need to capture this functionality
safe_strcpy(Drives[25]->curdir, "CODE\\BIN\\");
assert_DOS_MakeName("AUTOEXEC.BAT", true, "CODE\\BIN\\\\AUTOEXEC.BAT", 25);
safe_strcpy(Drives[25]->curdir, "CODE\\BIN\\\\");
assert_DOS_MakeName("AUTOEXEC.BAT", true, "CODE\\BIN\\\\\\AUTOEXEC.BAT", 25);
uselfn = oldlfn;
}
// This tests that illegal char matching happens AFTER 8.3 trimming
TEST_F(DOS_FilesTest, DOS_MakeName_Illegal_Chars_After_8_3)
{
bool oldlfn = uselfn;
uselfn = false;
safe_strcpy(Drives[25]->curdir, "BIN");
assert_DOS_MakeName("\n2345678AAAAABBB.BAT", false);
//assert_DOS_MakeName("12345678.\n23BBBBBAAA", false);
assert_DOS_MakeName("12345678AAAAABB\n.BAT", true, "BIN\\12345678.BAT", 25);
assert_DOS_MakeName("12345678.123BBBBBAAA\n", true, "BIN\\12345678.123", 25);
uselfn = oldlfn;
}
TEST_F(DOS_FilesTest, DOS_MakeName_DOS_PATHLENGTH_checks)
{
// Right on the line ...
safe_strcpy(Drives[25]->curdir, "aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaaa");
assert_DOS_MakeName("BBBBB.BB", true, "aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaaa\\BBBBB.BB", 25);
assert_DOS_MakeName("BBBBBBB.BB", true, "aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaaa\\BBBBBBB.BB", 25);
}
TEST_F(DOS_FilesTest, DOS_MakeName_Enforce_8_3)
{
bool oldlfn = uselfn;
uselfn = false;
safe_strcpy(Drives[25]->curdir, "BIN");
assert_DOS_MakeName("12345678AAAAABBBB.BAT", true, "BIN\\12345678.BAT", 25);
assert_DOS_MakeName("12345678.123BBBBBAAAA", true, "BIN\\12345678.123", 25);
uselfn = oldlfn;
}
TEST_F(DOS_FilesTest, DOS_MakeName_Dot_Handling)
{
safe_strcpy(Drives[25]->curdir, "WINDOWS\\CONFIG");
assert_DOS_MakeName(".", true, "WINDOWS\\CONFIG", 25);
assert_DOS_MakeName("..", true, "WINDOWS", 25);
assert_DOS_MakeName("...", true, "", 25);
assert_DOS_MakeName(".\\AUTOEXEC.BAT", true,
"WINDOWS\\CONFIG\\AUTOEXEC.BAT", 25);
assert_DOS_MakeName("..\\AUTOEXEC.BAT", true, "WINDOWS\\AUTOEXEC.BAT", 25);
assert_DOS_MakeName("...\\AUTOEXEC.BAT", true, "AUTOEXEC.BAT", 25);
safe_strcpy(Drives[25]->curdir, "WINDOWS\\CONFIG\\FOLDER");
assert_DOS_MakeName("...\\AUTOEXEC.BAT", true, "WINDOWS\\AUTOEXEC.BAT", 25);
assert_DOS_MakeName("....\\AUTOEXEC.BAT", true, "AUTOEXEC.BAT", 25);
safe_strcpy(Drives[25]->curdir, "WINDOWS\\CONFIG\\FOLDER\\DEEP");
assert_DOS_MakeName("....\\AUTOEXEC.BAT", true, "WINDOWS\\AUTOEXEC.BAT", 25);
assert_DOS_MakeName(".....\\AUTOEXEC.BAT", true, "AUTOEXEC.BAT", 25);
// make sure we can exceed the depth
assert_DOS_MakeName("......\\AUTOEXEC.BAT", true, "AUTOEXEC.BAT", 25);
assert_DOS_MakeName("...........\\AUTOEXEC.BAT", true, "AUTOEXEC.BAT", 25);
// make sure we have arbitrary expansion
assert_DOS_MakeName("...\\FOLDER\\...\\AUTOEXEC.BAT", true,
"WINDOWS\\AUTOEXEC.BAT", 25);
assert_DOS_MakeName("...\\FOLDER\\....\\.\\AUTOEXEC.BAT", true,
"AUTOEXEC.BAT", 25);
}
TEST_F(DOS_FilesTest, DOS_MakeName_No_SlashSlash)
{
safe_strcpy(Drives[25]->curdir, "");
bool oldlfn = uselfn;
uselfn = false;
assert_DOS_MakeName("Z:..\\tmp.txt", true, "TMP.TXT", 25);
uselfn = true;
assert_DOS_MakeName("Z:..\\tmp.txt", true, "tmp.txt", 25);
uselfn = oldlfn;
}
// Exhaustive test of all good chars
TEST_F(DOS_FilesTest, DOS_MakeName_GoodChars)
{
safe_strcpy(Drives[25]->curdir, "");
bool oldlfn = uselfn;
unsigned char start_letter = 'A';
unsigned char start_number = '0';
std::vector<unsigned char> symbols{'$', '#', '@', '(', ')', '!', '%',
'{', '}', '`', '~', '_', '-', '.',
'*', '?', '&', '\'', '+', '^', 246,
255, 0xa0, 0xe5, 0xbd, 0x9d};
// iterate A-Z
for (unsigned char li = 0; li < 26; li++) {
// iterate 0-9
for (unsigned char ni = 0; ni < 10; ni++) {
for (auto &c : symbols) {
unsigned char input_array[3] = {
static_cast<unsigned char>(start_letter + li),
static_cast<unsigned char>(start_number + ni),
c,
};
std::string test_input(reinterpret_cast<char *>(input_array), 3);
uselfn = false;
assert_DOS_MakeName(test_input.c_str(), true, test_input, 25);
uselfn = true;
assert_DOS_MakeName(test_input.c_str(), true, test_input, 25);
}
}
}
uselfn = oldlfn;
}
TEST_F(DOS_FilesTest, DOS_MakeName_Colon_Illegal_Paths)
{
assert_DOS_MakeName(":..\\tmp.txt", false);
assert_DOS_MakeName(" :..\\tmp.txt", false);
assert_DOS_MakeName(": \\tmp.txt", false);
assert_DOS_MakeName(":", false);
}
// ensures a fix for dark forces installer
TEST_F(DOS_FilesTest, DOS_FindFirst_Ending_Slash)
{
// `dos` comes from dos_inc.h
dos.errorcode = DOSERR_NONE;
EXPECT_FALSE(DOS_FindFirst("Z:\\DARK\\LFD\\", DOS_ATTR_VOLUME, false));
EXPECT_EQ(dos.errorcode, DOSERR_NO_MORE_FILES);
dos.errorcode = DOSERR_NONE;
EXPECT_FALSE(DOS_FindFirst("Z:\\DARK\\", DOS_ATTR_VOLUME, false));
EXPECT_EQ(dos.errorcode, DOSERR_NO_MORE_FILES);
// volume names alone don't trigger the failure
dos.errorcode = DOSERR_NONE;
EXPECT_TRUE(DOS_FindFirst("Z:\\", DOS_ATTR_VOLUME, false));
EXPECT_NE(dos.errorcode, DOSERR_NO_MORE_FILES);
// volume attr NOT required
dos.errorcode = DOSERR_NONE;
EXPECT_FALSE(DOS_FindFirst("Z:\\NOMATCH\\", 0, false));
EXPECT_EQ(dos.errorcode, DOSERR_NO_MORE_FILES);
}
TEST_F(DOS_FilesTest, DOS_FindFirst_Rejects_Invalid_Names)
{
// triggers failures via DOS_FindFirst
EXPECT_FALSE(DOS_FindFirst("Z:\\BAD\nDIR\\HI.TXT", 0, false));
EXPECT_EQ(dos.errorcode, DOSERR_PATH_NOT_FOUND);
}
TEST_F(DOS_FilesTest, DOS_FindFirst_FindVolume)
{
dos.errorcode = DOSERR_NONE;
EXPECT_TRUE(DOS_FindFirst("Z", DOS_ATTR_VOLUME, false));
EXPECT_EQ(dos.errorcode, DOSERR_NONE);
}
TEST_F(DOS_FilesTest, DOS_FindFirst_FindDevice)
{
dos.errorcode = DOSERR_NONE;
EXPECT_TRUE(DOS_FindFirst("COM1", DOS_ATTR_DEVICE, false));
EXPECT_EQ(dos.errorcode, DOSERR_NONE);
}
TEST_F(DOS_FilesTest, DOS_FindFirst_FindFile)
{
dos.errorcode = DOSERR_NONE;
EXPECT_TRUE(DOS_FindFirst("Z:\\AUTOEXEC.BAT", 0, false));
EXPECT_EQ(dos.errorcode, DOSERR_NONE);
}
TEST_F(DOS_FilesTest, DOS_FindFirst_FindFile_Nonexistant)
{
dos.errorcode = DOSERR_NONE;
EXPECT_FALSE(DOS_FindFirst("Z:\\AUTOEXEC.NO", 0, false));
EXPECT_EQ(dos.errorcode, DOSERR_NO_MORE_FILES);
}
// this probably isn't a desirable quality, but figure that out later
TEST_F(DOS_FilesTest, DOS_DTAExtendName_Mutates_Input)
{
char input_str[] = "123456789AAAA.EXT\0";
int initial_input_name = strlen(input_str);
char *const input_name = &input_str[0];
// needs to be minimum length of the input up to the dot + 1 (null)
char output_filename[14];
char *const filename = &output_filename[0];
char output_ext[4];
char *const ext = &output_ext[0];
DTAExtendName(input_name, filename, ext);
EXPECT_EQ(strlen(input_name), 13);
EXPECT_NE(initial_input_name, strlen(input_str));
}
TEST_F(DOS_FilesTest, DOS_DTAExtendName_Space_Pads)
{
assert_DTAExtendName("1234.E ", "1234 ", "E ");
}
TEST_F(DOS_FilesTest, DOS_DTAExtendName_Enforces_8_3)
{
assert_DTAExtendName("12345678ABCDEF.123ABCDE", "12345678", "123");
}
} // namespace