"Illegal exception" error - at wit's end

Started by Dave Gilbert, Thu 08/08/2013 16:18:06

Previous topic - Next topic

Dave Gilbert

So in my game I have a GUI that contains a listbox. This GUI acts as a notebook interface, which is used to ask characters about various topics.

Sometimes - not always - when turning on the GUI the game crashes with this error:



Line 473 is the following:

      int noteitem = note_list.GetItemAtLocation(mouse.x, mouse.y);

note_list is the name of the listbox. The command is telling the game to get the index of whatever listbox item you are hovering over, and assigning it to int "noteitem."

This line of code is located within the repeatedly_execute_always() function of the global script. This command is within the IF statement "if (gNotes.Visible==true)" - e.g., it only gets activated if the notes GUI is on the screen.

Here's the chunk of code:

Code: AGS

(from within repeatedly_execute_always() )

if (gNotes.Visible==true)
{

    int noteitem = note_list.GetItemAtLocation(mouse.x, mouse.y); //<-- the line that gives me the illegal exception
    if (noteitem >= 0) 
    {
        note_list.SelectedIndex = noteitem;
        GetNoteInfoText();
        note_info.Text=noteinfotext;
    }
    else
    {
        note_list.SelectedIndex = -1;
        note_info.Text="";
    }      
      Mouse.UseModeGraphic(eModeInteract);
      GUIControl *theControl = GUIControl.GetAtScreenXY(mouse.x, mouse.y);  //get the control
      if (theControl==btn_notesup && btn_notesup.NormalGraphic != 441) 
        btn_notesup.NormalGraphic=441;
   
      if (theControl != btn_notesup && btn_notesup.NormalGraphic!=440)
        btn_notesup.NormalGraphic=440;

      if (theControl==btn_notesdown && btn_notesdown.NormalGraphic != 443) 
        btn_notesdown.NormalGraphic=443;
      
      if (theControl != btn_notesdown && btn_notesdown.NormalGraphic!=442)
        btn_notesdown.NormalGraphic=442;

    }


By happenstance, I had made a savegame right before I got the error message. But when I reloaded, the command executed just fine. Since this bug only happens very occasionally, it's very hard to track. And the error message doesn't give me any useful information.

So... help? Thanks in advance!

-Dave

edit: Oh, and here's the crashdump file.


Calin Leafshade

My initial guess would be that the position of your mouse is causing the error. Of course this still shouldn't crash it but it would be a good place to start.

Is the mouse over the GUI when it crashes? Is it off the screen? Things like that.

Gilbert

Maybe sometimes the engine will give incorrect line number for an error.
Can you try comment that line out, and add the following line (to ensure the thing compile and continue to execute):
Code: AGS
int noteitem = -1;


and then test it a couple of times? If it doesn't crash, well... it doesn't indicate anything (as the crash just happens occasionally), but if it still crashes sometimes, a good chance is that particular line is not the culpit of the problem.

Crimson Wizard

FYI, we cannot use the crashdumps created by AGS 3.2.1, because CJ did not leave us the matching PDB file.
This will only be useful if you'd run your game on newer engine, like 3.3.0 beta (you do not need to open project in the editor, just run your compiled game using new acwin.exe as explained in the "Backward Compatibility" section in the thread: http://www.adventuregamestudio.co.uk/forums/index.php?topic=47966.0).


Now, regarding the exception meaning,
Exception 0xc0000094 = EXCEPTION_INT_DIVIDE_BY_ZERO.

I tried to trace the algorythm and the only place in "GetItemAtLocation" where division is used seemingly is this:
Code: cpp

int GUIListBox::GetIndexFromCoordinates(int xx, int yy) {
 <...>
  int onindex = yy / rowheight + topItem;
 <...>
}


"rowheight" is calculated by text's font height. I don't know what to think... Maybe you are using some weird font there? Or change font over time?
These are just blind guesses.


Dave Gilbert

Hmm. The position of the GUI does change depending on whether you are accessing the notebook via a conversation, or through the main menu. It could have something to do with that. I'll experiment. Thanks!

monkey0506

Obviously this situation is an exceptional case, but I'm rather baffled how this particular division could ever be resulting in a divide-by-zero error. That said, I do agree that this is the only division that I can find tied to ListBox.GetItemAtLocation. The problem is that the way the row height is being calculated, wgettextheight would have to be returning -2 (or possibly -4, if using low-res coordinates in a high-res game) to give a row height of zero. The flaw in that though is that the height of each character being checked is calculated by storing the result in an unsigned short, casting that into a signed int, and comparing for the greatest value among the characters compared.

I'm sure you're already aware of all this, of course, but it's quite the conundrum. Even if every bit of the ushort was being used, and even with the implicit casting from unsigned to signed, there's no way that the sign bit of the int would be set, so there's zero possibility of it being negative. There is simply no conceivable case in which row height could be getting assigned in this way and still resulting in zero. The only thing I could think of... Dave said that this happens when turning on the GUI. Is it possible that the engine is actually calling the function before the ListBox is actually being drawn? Because, well, row height is initialized to 0, and only updated when the control is drawn to the screen.

I don't know quite enough about the internal workings of the engine to know if that's even possible, but could this be happening?

Dave Gilbert

#6
OK, I finally managed to get this bug to replicate.

The crash ONLY happens when these conditions are met:

-The notebook is turned on for the first time
-The mouse is on the listbox area of the notebook GUI

I'm guessing that the crash happens because the notes are generated the first time you turn on the GUI, and the game is trying to find the listbox item at the cursor position during that fraction of a second when the listbox doesn't exist. I changed the code so the listbox is generated BEFORE the gui turns on and that seems to have solved it. Yay!

Crimson Wizard

#7
Thanks, I was able to make a 100% reproducible test. I should have tried before, but was too busy and forgot about this...

1. Make a game with Gui and ListBox on it. The Gui should be "Normal, initially off".
2. In GlobalScript:
Code: ags

function repeatedly_execute_always()
{
  if (gGui1.Visible)
  {
    int item = ListBox1.GetItemAtLocation(20, 20);
    // where 20,20 are any coordinates in the list box interior.
  }
}

function on_key_press(eKeyCode keycode)
{
  if (keycode == eKeyA)
  {
    gGui1.Visible = !gGui1.Visible;
  }
}

3. Run game and press 'A'. Observe crash.

Interesting thing is that this happens ONLY if the GetItemAtLocation call is inside "repeatedly_execute_ALWAYS". When put inside "repeatedly_execute" there's no crash.

The division by zero is on the line I mentioned above. Monkey gave some interesting points about rowheight initialization. I'm going to look into this now.

EDIT: Ok, like monkey_05_06 said, the "rowheight" is first initialized when the control is first drawn (i.e. the painting function is called)! This means that the GetItemAtLocation called before the first drawing uses uninitialized variable.

Dave Gilbert


Crimson Wizard

#9
Heh, it appears it is not the only problem with the list box.
Apparently, when you set a new font, the rowheight is recalculated with an old one :). This only works because it recalculates it again when redrawing itself. I think the Gui code is a pretty old one and was not touched much since it was first written, so there may be few surprises...

SMF spam blocked by CleanTalk