Question

I know there is a similarly titled question already on SO but I want to know my options for this specific case.

MSVC compiler gives a warning about strcpy:

1>c:\something\mycontrol.cpp(65): warning C4996: 'strcpy': This function or
variable may be unsafe. Consider using strcpy_s instead. To disable
deprecation, use _CRT_SECURE_NO_WARNINGS. See online help for details.

Here's my code:

void MyControl::SetFontFace(const char *faceName)
{
    LOGFONT lf;

    CFont *currentFont = GetFont();
    currentFont->GetLogFont(&lf);
    strcpy(lf.lfFaceName, faceName); <--- offending line
    font_.DeleteObject();
    // Create the font.
    font_.CreateFontIndirect(&lf);

    // Use the font to paint a control.
    SetFont(&font_);
}

Note font_ is an instance variable. LOGFONT is a windows structure where lfFaceName is defined as TCHAR lfFaceName[LF_FACESIZE].

What I'm wondering is can I do something like the following (and if not why not):

void MyControl::SetFontFace(const std::string& faceName)
...
  lf.lfFaceName = faceName.c_str();
...

Or if there is a different alternative altogether then let me know.

Was it helpful?

Solution

The reason you're getting the security warning is, your faceName argument could point to a string that is longer than LF_FACESIZE characters, and then strcpy would blindly overwrite whatever comes after lfFaceName in the LOGFONT structure. You do have a bug.

You should not blindly fix the bug by changing strcpy to strcpy_s, because:

  1. The *_s functions are unportable Microsoft inventions almost all of which duplicate the functionality of other C library functions that are portable. They should never be used, even in a program not intended to be portable (as this appears to be).
  2. Blind changes tend to not actually fix this class of bug. For instance, the "safe" variants of strcpy (strncpy, strlcpy, strcpy_s) simply truncate the string if it's too long, which in this case would make you try to load the wrong font. Worse, strncpy omits the NUL terminator when it does that, so you'd probably just move the crash inside CreateFontIndirect if you used that one. The correct fix is to check the length up front and fail the entire operation if it's too long. At which point strcpy becomes safe (because you know it's not too long), although I prefer memcpy because it makes it obvious to future readers of the code that I've thought about this.
  3. TCHAR and char are not the same thing; copying either a C-style const char * string or a C++ std::string into an array of TCHAR without a proper encoding conversion may produce complete nonsense. (Using TCHAR is, in my experience, always a mistake, and the biggest problem with it is that code like this will appear to work correctly in an ASCII build, and will still compile in UNICODE mode, but will then fail catastrophically at runtime.)

You certainly can use std::string to help with this problem, but it won't get you out of needing to check the length and manually copy the string. I'd probably do it like this. Note that I am using LOGFONTW and CreateFontIndirectW and an explicit conversion from UTF-8 in the std::string. Note also that chunks of this were cargo-culted out of MSDN and none of it has been tested. Sorry.

void MyControl::SetFontFace(const std::string& faceName)
{
    LOGFONTW lf;
    this->font_.GetLogFontW(&lf);

    int count = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS,
                                    faceName.data(), faceName.length(),
                                    lf.lfFaceName, LF_FACESIZE - 1)
    if (count <= 0)
        throw GetLastError(); // FIXME: use a real exception

    lf.lfFaceName[count] = L'\0'; // MultiByteToWideChar does not NUL-terminate.

    this->font_.DeleteObject();
    if (!this->font_.CreateFontIndirectW(&lf))
        throw GetLastError(); // FIXME: use a real exception

    // ...
}

OTHER TIPS

lf.lfFaceName = faceName.c_str();

No you shouldn't do that because you are making a local copy of the poitner to the data held inside the std::string. If the c++ string changes, or is deleted, the pointer is no longer valid, and if lFaceName decides to change the data this will almost certainly break the std::string.

Since you need to copy a c string, you need a 'c' function, then strcpy_s (or it's equivalent) is the safe alternative

Have you tried? Given the information in your post, the assignment should generate a compiler error because you're trying to assign a pointer to an array, which does not work in C(++).

#include <cstdio>
#include <string>
using namespace std;

struct LOGFONT {
 char lfFaceName[3];
};


int main() {
        struct LOGFONT f;
        string foo="bar";
        f.lfFaceName = foo.c_str();
        return 0;
}

leads to

x.c:13: error: incompatible types in assignment of `const char*' to `char[3]'

I'd recommend using a secure strcpy alternative like the warning says, given that you know the size of the destination space anyway.

#include <algorithm>
#include <iostream>
#include <string>

enum { LF_FACESIZE = 256 }; // = 3 // test too-long input
struct LOGFONT
{
    char lfFaceName[LF_FACESIZE];
};

int main()
{
    LOGFONT f;
    std::string foo("Sans-Serif");
    std::copy_n(foo.c_str(), foo.size()+1 > LF_FACESIZE ? LF_FACESIZE : foo.size()+1,
                f.lfFaceName);

    std::cout << f.lfFaceName << std::endl;
    return 0;
}

lf.lfFaceName = faceName.c_str(); won't work for two reasons (assuming you change faceName to a std:string)

  1. The lifetime of the pointer returned by c_str() is temporary. It's only valid as long as the fileName object doesn't change and in alive.
  2. The line won't compile. .c_str() returns a pointer to a char, and lfFaceName is a character array and can't be assigned to. You need to do something to fill in the string array, to fill in the bytes at lfFaceName, and pointer assignment doesn't do that.

There isn't anything C++ that can help here, since lfFaceName is a C "string". You need to use a C string function, like strcpy or strcpy_s. You can change your code to:

strcpy_s(lf.lfFaceName, LF_FACESIZE, faceName);
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top