DEV Community

Anastasiia Ogneva
Anastasiia Ogneva

Posted on

Games! How they write code for SDL (+ interview with the creator)

Jack London once said, "You can't wait for inspiration. You have to go after it with a club". When it comes to game development, the SDL project may well be such a club. How is it made, though?

Image description

If you've ever wanted to develop your own game, you've probably come across SDL. If you've ever wanted to learn how to draw things on your screen for free (no viruses), you've probably come across SDL. If you've ever needed to initialize OpenGL, you've definitely encountered SDL.

And if you haven't, we highly recommend that you learn about it! SDL is a cross-platform library for graphics, audio, inputs, and everything else you need to finally create your game!

Neverwinter Nights, Dwarf Fortress, Amnesia, VVVVVV (on which we have a similar article), and the great and powerful Teeworlds (where Yours Truly spent a considerable amount of time hoockin' and bazookin' around) are among the games that use SDL in one way or another.

The project has been around for decades — its original creator and the community are still actively developing it. So, why don't we take this great chance to learn something new? Let's take a flashlight and look under the hood of the project!

Moreover, this time we decided to experiment a bit and take a little interview with the project creator, which we invite you, dear reader, to find at the end of the article.

All right, enough wasting time, go-go-go!

We use the code examples throughout the article. The ellipsis characters "...." in the code were added by the author of the article.
You can find the source files on the official GitHub of libsdl. In addition, each fragment has a reference to a specific area in the code.
By the time this article is published, many errors will have already been fixed thanks to the issues we opened (for example, here and there). However, the links in the examples point exactly to the code you see in this article. Not only do we enjoy teasing developers but we also like making their projects a little bit better!

Trust but verify

How much, dear reader, do you trust the documentation of the standard C library? Although a more appropriate question for our opening example would be: how much do you trust yourself when you use one? What do you think is wrong with the following example?

File: SDL/src/stdlib/SDL_iconv.c (GitHub permalink)

char *SDL_iconv_string(const char *tocode, const char *fromcode,
                       const char *inbuf, size_t inbytesleft)
{
  SDL_iconv_t cd;
  ....

  cd = SDL_iconv_open(tocode, fromcode);
  if (cd == (SDL_iconv_t)-1) {
    /* See if we can recover here (fixes iconv on Solaris 11) */
    if (tocode == NULL || !*tocode) {
      tocode = "UTF-8";
    }
    if (fromcode == NULL || !*fromcode) {
      fromcode = "UTF-8";
    }
    cd = SDL_iconv_open(tocode, fromcode);
  }
  ....
}
Enter fullscreen mode Exit fullscreen mode

"I might have answered," the attentive and keen reader may object, "if we were dealing with the standard iconv_open function. But there's a custom SDL_iconv_open implementation here!" That's an excellent observation! Here's how the function is implemented:

File: SDL/src/stdlib/SDL_iconv.c (GitHub permalink)

SDL_iconv_t SDL_iconv_open(const char *tocode, const char *fromcode)
{
  return (SDL_iconv_t)((uintptr_t)iconv_open(tocode, fromcode));
}
Enter fullscreen mode Exit fullscreen mode

With that out of the way, let's look at the neighborhood. The code below illustrates that null pointers can be passed to the input in the tocode and fromcode arguments. In this case, they will go into iconv_open before the check.

When we look at the man page of this function from the GNU project, we don't see any mention of the fact that NULL can't be pushed into it. "Case solved!" our junior investigator would say, but you can't fool a seasoned detective! He really loves looking at the C standard library source code, because looking at the C standard library source code is fun! You too, behold!

Well... there is no pointer dereferencing, but a call to strlen without a NULL check is for sure present. We all know what strlen does in this case, and hardly anybody anywhere ever enjoyed it!

But if you, kind reader, thought we would stop at one implementation, let us hasten to change your mind — BSD and Musl behave the same way.

A harmless fragment, it would seem — even the check is there! But you should know your library dealer by sight! By the way, how often do you, dear reader, have the need to see your vendor's source code? Let's chat in the comments!

Well, the analyzer issued a concise message:

V595 The 'tocode' pointer was utilized before it was verified against nullptr. Check lines: 37, 789, 792. SDL/src/stdlib/SDL_iconv.c:792:1

Cargo cult

What about a little confession? Dost thou have those little rituals that are to be followed, elsewise thou labor in vain? Do you pet a rubber duck or save a source file three times in a row? The following is an example of such behavior:

File: SDL/src/events/SDL_mouse.c (GitHub permalink)

SDL_Cursor *SDL_GetCursor(void)
{
  SDL_Mouse *mouse = SDL_GetMouse();
  if (mouse == NULL) {
    return NULL;
  }
  return mouse->cur_cursor;
}
Enter fullscreen mode Exit fullscreen mode

"What's wrong now?" the reader may cry out. The pointer is requested, the pointer is checked — everything is fair and square! We understand, after all, as we have written above, this is exactly the desired behavior! However, I urge you to hold your outrage and see where NULL comes from.

File: SDL/src/events/SDL_mouse.c (GitHub permalink)

SDL_Mouse *SDL_GetMouse(void)
{
  return &SDL_mouse;
}
Enter fullscreen mode Exit fullscreen mode

Well, well, what kind of variable do we have here? Let's find out!

File: SDL/src/events/SDL_mouse.c (GitHub permalink)

static SDL_Mouse SDL_mouse;
Enter fullscreen mode Exit fullscreen mode

It's a global variable! This means that NULL has no place here, as a global variable is always located in memory at a specific address.

What can the analyzer do other than issue another concise message:

V547 Expression 'mouse == NULL' is always false. SDL/src/events/SDL_mouse.c:1376:1

P.S. With the above in mind, we can rewrite the body of the function as follows:

return SDL_GetMouse()->cur_cursor;
Enter fullscreen mode Exit fullscreen mode

_Some vigilant readers may have noticed that if we replace a static variable with an allocation, the missing NULL check will play a cruel trick on us. Rest assured, any static analyzer worth its salt can tell you that the NULL check is required. This saves both the abstraction layer and the programmer's nerves.

We ask the readers who rightly point out that "every pointer returned by a function must be checked no matter what" to be patient and wait a little. I promise, we'll come back to this topic later with another fun example!_

Do or do not

Did you know that the number of fingers on Master Yoda's hand changes from movie to movie? And now, back to the C language! There is a certain structure:

File: /SDL/src/render/SDL_yuv_sw_c.h (GitHub permalink)

struct SDL_SW_YUVTexture
{
  ....
  int w, h;
  ....
};

typedef struct SDL_SW_YUVTexture SDL_SW_YUVTexture;
Enter fullscreen mode Exit fullscreen mode

That is used in a certain fragment:

File: /SDL/src/render/SDL_yuv_sw.c (GitHub permalink)

int SDL_SW_UpdateYUVTexture(SDL_SW_YUVTexture *swdata, const SDL_Rect *rect,
                            const void *pixels, int pitch
{
  ....
  SDL_memcpy(swdata->pixels, pixels,
       (size_t)(swdata->h * swdata->w) + 
            2 * ((swdata->h + 1) / 2) * ((swdata->w + 1) / 2));
  ....
}
Enter fullscreen mode Exit fullscreen mode

When people say that you should consult the classics from time to time, they are hardly talking about classic type conversion errors in C. Look at the swdata->h * swdata->w expression, then at the types of its operands, and finally at the type to which it's cast. It would be more correct to cast the operands first and then multiply them, don't you think?

The author of the article should note, of course, that he has no idea how big the texture would have to be for its sides to overflow when multiplied. However, to conclude this section, Yours Truly would like to resurrect this article by a former Google employee and author of some Java sections. The article describes how simple mergesort contained a potential overflow bug for years. It had taken a long time, but its hour to "shine" had finally come.

The analyzer issues a warning:

V1028 Possible overflow. Consider casting operands of the 'swdata->h * swdata->w' operator to the 'size_t' type, not the result. SDL/src/render/SDL_yuv_sw.c:125:1

P.S. For example, we can rewrite the code in question this way:

(size_t)(swdata->h) * (size_t)(swdata->w) + 
 (2 * (((size_t)(swdata->h) + 1) / 2)) *
  (((size_t)(swdata->w) + 1) / 2);
Enter fullscreen mode Exit fullscreen mode

However, why pay for security with code readability? Let's rewrite the code as follows:

const size_t h = swdata->h;
const size_t w = swdata->w;
SDL_memcpy(swdata->pixels, pixels,
           h * w + 2 * ((h + 1) / 2) * ((w + 1) / 2));
Enter fullscreen mode Exit fullscreen mode

Image description

The full article is here

Top comments (0)