Author Topic: [FIXED] Glitch/Bug between _FREEIMAGE/_COPYIMAGE/_MEM (Critical Error #308)  (Read 2549 times)

0 Members and 1 Guest are viewing this topic.

Offline RhoSigma

  • Seasoned Forum Regular
  • Posts: 417
Hi QB64 Team,

It seems I found a longstanding glitch/bug between _FREEIMAGE, _COPYIMAGE and the _MEM functions (probably _MEMIMAGE), which leads to an Critical Error #308 (memory has been freed).

The error happens, whenever _COPYIMAGE does use a handle, which was already used by another image earlier but which got freed in the meantime and hence is available for re-use. I could verify this error starting from the old SDL 0.954 version through different GL builts in between up to the latest GL development built available from QB64.org.

I've made a small program to show the issue. Note you need adjust the _LOADIMAGE line (3) to use any image on your system (must be at least 1024x768).

To get an understanding of the workflow, first read the comments right above the ModifyBrightness&() function (lines 37-40), then the comments in the main module (lines 14-26).

If you run the program as is, it will show the loaded image and wait for a keypress. When you press a key, it will do the _FREIMAGE and then the final ModifyBrightness&() call and will error out with #308 immediately. After that try to comment the _FREEIMAGE line !!OR!! commenting the _COPYIMAGE in ModifyBrightness&() and instead uncommenting the _NEWIMAGE/_PUTIMAGE pair (lines 63-67) and try again. It will work in both cases, a further keypress will then end the program.

Code: QB64: [Select]
  1. '--- 1st show the original image ---
  2. _TITLE "The original Image, press any key..."
  3. ihan& = _LOADIMAGE("AnyImage.jpg", 32) 'any image file (at least 1024x768)
  4. IF ihan& >= -1 THEN ERROR 53 'file not found
  5. SCREEN ihan&
  6.  
  7. '--- now make a darken cascade ---
  8. _TITLE "Darken Cascade, press any key..."
  9. dark& = ModifyBrightness&(ihan&, -0.15, 250, 70, 820, -1)
  10. darker& = ModifyBrightness&(dark&, -0.15, 275, 95, 795, -1)
  11. COLOR _RGB32(255, 255, 200): PRINT "Used Handle: "; dark&: SLEEP
  12.  
  13. '--------------------------------------------------------------------
  14. '--- With activated _FREEIMAGE the freed handle is re-used by the
  15. '--- next _COPYIMAGE call done in ModifyBrightness&() called right
  16. '--- below the _FREEIMAGE, but the handle in question obviously gets
  17. '--- not correctly initialized by _COPYIMAGE, as the _MEM function(s)
  18. '--- used later to iterate through the image do fail to work on it
  19. '--- (Critical Error #308 (memory was freed)).
  20. '-----
  21. '--- If otherwise the _FREEIMAGE call is commented out, then it works
  22. '--- with _COPYIMAGE, as a new clean handle must be used instead.
  23. '-----
  24. '--- In alternative it also works with activated _FREEIMAGE, but change
  25. '--- the _COPYIMAGE in FUNCTION ModifyBrightness&() into a combination
  26. '--- of _NEWIMAGE and _PUTIMAGE instead to make the image copy.
  27. '--------------------------------------------------------------------
  28. darkest& = ModifyBrightness&(darker&, -0.15, 300, 120, 770, -1)
  29. '--------------------------------------------------------------------
  30.  
  31. SCREEN darkest&
  32. COLOR _RGB32(255, 255, 200): PRINT "Used Handle: "; darkest&: SLEEP
  33. '--- done ---
  34.  
  35. 'This function is a cutout from 'QB64Library\IMG-Support\imageprocess.bm',
  36. 'which is part of my Libraries Collection. It takes a source image handle
  37. 'and returns a modified image in a new handle usually made with _COPYIMAGE.
  38. 'This seems to fail, if for the copy an old (freed) handle is re-used.
  39. '---------------------------------------------------------------------
  40. 'NAME
  41. '   ModifyBrightness -- Brightness adjustment, keep alpha
  42. '
  43. 'TEMPLATE
  44. '   newImg& = ModifyBrightness& (shan&, change#, minX%, minY%, maxX%, maxY%)
  45. '
  46. '   change# -- must be between -1.0 and 1.0  (clipped)
  47. '
  48. ' DESCRIPTION
  49. '   This changes the general brightness of the image. The smaller the
  50. '   value is the darker the image becomes.  0.0 means no change.
  51. '   The alpha channel of the source image is retained and copied 1:1.
  52. '---------------------------------------------------------------------
  53. FUNCTION ModifyBrightness& (shan&, change#, minX%, minY%, maxX%, maxY%)
  54. ModifyBrightness& = -1 'so far return invalid handle
  55. IF shan& < -1 THEN
  56.     IF _PIXELSIZE(shan&) = 4 THEN
  57.         '--- get source image size and a copy of the image ---
  58.         swid% = _WIDTH(shan&): shei% = _HEIGHT(shan&)
  59.  
  60.         '--------------------------------------------------
  61.         '--- This works only, if a new handle must be used,
  62.         nhan& = _COPYIMAGE(shan&)
  63.         '--- and this works for both re-used and new handles
  64.         'nhan& = _NEWIMAGE(swid%, shei%, 32)
  65.         '_PUTIMAGE ,shan&, nhan&
  66.         '--------------------------------------------------
  67.  
  68.         '--- check selected processing area ---
  69.         IF minX% < 0 OR minX% >= swid% THEN minX% = 0
  70.         IF maxX% < 0 OR maxX% >= swid% THEN maxX% = swid% - 1
  71.         IF minY% < 0 OR minY% >= shei% THEN minY% = 0
  72.         IF maxY% < 0 OR maxY% >= shei% THEN maxY% = shei% - 1
  73.         '--- process copied image ---
  74.         IF nhan& < -1 THEN
  75.             '--- build histogram transformation table ---
  76.             IF change# < -1.0# THEN change# = -1.0
  77.             IF change# > 1.0# THEN change# = 1.0
  78.             REDIM hist%(255)
  79.             FOR i% = 0 TO 255
  80.                 v% = FIX(i% * (change# + 1.0#))
  81.                 IF v% < 0 THEN v% = 0: ELSE IF v% > 255 THEN v% = 255
  82.                 hist%(i%) = v%
  83.             NEXT i%
  84.             '--- for speed we do direct memory access ---
  85.             DIM nbuf AS _MEM: nbuf = _MEMIMAGE(nhan&)
  86.             '--- iterate through pixels ---
  87.             FOR y% = minY% TO maxY%
  88.                 noff%& = nbuf.OFFSET + (y% * swid% * 4) + (minX% * 4)
  89.                 FOR x% = minX% TO maxX%
  90.                     '--- get pixel ARGB value from source ---
  91.                     _MEMGET nbuf, noff%&, orgb~&
  92.                     '--- modify pixel components ---
  93.                     newA% = _ALPHA32(orgb~&) 'ignored (put through)
  94.                     newR% = hist%(_RED32(orgb~&))
  95.                     newG% = hist%(_GREEN32(orgb~&))
  96.                     newB% = hist%(_BLUE32(orgb~&))
  97.                     '--- put new pixel ARGB value to dest ---
  98.                     nrgb~& = _RGBA32(newR%, newG%, newB%, newA%)
  99.                     _MEMPUT nbuf, noff%&, nrgb~&
  100.                     '--- set next pixel offset ---
  101.                     noff%& = noff%& + 4
  102.                 NEXT x%
  103.             NEXT y%
  104.             '--- cleanup ---
  105.             _MEMFREE nbuf
  106.             ERASE hist%
  107.             '--- set result ---
  108.             ModifyBrightness& = nhan&
  109.         END IF
  110.     END IF
  111.  
  112.  
« Last Edit: January 03, 2019, 12:44:23 PM by RhoSigma »
My Projects: https://www.qb64.org/forum/index.php?topic=809
GuiTools - Build graphic UIs dynamically at runtime. Is able to have multiple forms windows in one program.
Bonus - Libraries (Image processing/Data buffering/MD5/SHA2/LZW...), Blankers, QB64/Notepad++ setup pack

Offline FellippeHeitor

  • QB64 Developer
  • Forum Resident
  • Posts: 2689
  • LET IT = BE
    • QB64.org
Re: Glitch/Bug between _FREEIMAGE/_COPYIMAGE/_MEM (Critical Error #308)
« Reply #1 on: July 20, 2018, 11:10:50 AM »
Thanks for the report, RhoSigma. It will be investigated.
« Last Edit: July 20, 2018, 11:11:42 AM by odin »

Offline SMcNeill

  • QB64 Developer
  • Forum Resident
  • Posts: 3183
    • Steve’s QB64 Archive Forum
Re: Glitch/Bug between _FREEIMAGE/_COPYIMAGE/_MEM (Critical Error #308)
« Reply #2 on: July 20, 2018, 02:19:35 PM »
A simpler demo of the issue, as reported:

Code: QB64: [Select]
  1.  
  2. ihan& = _LOADIMAGE("BeachGirl.jpg", 32) 'any image file (at least 1024x768)
  3. IF ihan& >= -1 THEN ERROR 53: END 'file not found
  4. SCREEN ihan&
  5.  
  6. dark& = _COPYIMAGE(ihan&) 'make a copy
  7. m = _MEMIMAGE(dark&)
  8. PRINT _MEMGET(m, m.OFFSET, _UNSIGNED _BYTE) 'just print the first byte of info to show we can access memory from the image
  9.  
  10.  
  11. darker& = _COPYIMAGE(dark&) 'make a copy of the copy
  12.  
  13. _FREEIMAGE dark& 'free the first copy
  14.  
  15. m = _MEMIMAGE(darker&)
  16.  
  17.  
https://github.com/SteveMcNeill/Steve64 — A github collection of all things Steve!

Offline RhoSigma

  • Seasoned Forum Regular
  • Posts: 417
Re: Glitch/Bug between _FREEIMAGE/_COPYIMAGE/_MEM (Critical Error #308)
« Reply #3 on: July 20, 2018, 03:23:15 PM »
Steve, i'd say you even show a different issue here, as you first make a 2nd copy (darker&) then free the 1st one and finally (without making a 3rd copy which would re-use the handle of the just freed 1st copy) you do a memory access to the 2nd copy, which then fails.

So for me it looks like the _FREEIMAGE dark& will even corrupt the previously copied darker& handle, so that the following memory access made on darker& will fail.

That's definitly another bug, i was only aware of it for re-used handles, but your example shows that _FREEIMAGE does obviously corrupt innocent handles, which were not even specified to free.
My Projects: https://www.qb64.org/forum/index.php?topic=809
GuiTools - Build graphic UIs dynamically at runtime. Is able to have multiple forms windows in one program.
Bonus - Libraries (Image processing/Data buffering/MD5/SHA2/LZW...), Blankers, QB64/Notepad++ setup pack

Offline SMcNeill

  • QB64 Developer
  • Forum Resident
  • Posts: 3183
    • Steve’s QB64 Archive Forum
Re: Glitch/Bug between _FREEIMAGE/_COPYIMAGE/_MEM (Critical Error #308)
« Reply #4 on: July 21, 2018, 01:34:11 PM »
Doing a little tracing of this error reveals the following for me:

In libqb.cpp, we can trace the glitch to the last line in sub__freeimage:

 
Code: QB64: [Select]
  1. freeimg(i);

Tracing back into the freeimg routine in libqb.cpp, we can narrow the issue down to the IF statement which deals with mem_lock status:

Code: QB64: [Select]
  1.   IF (img[i].lock_id){
  2.     free_mem_lock((mem_lock*)img[i].lock_offset);//untag
  3.   }

Remark out that free_mem_lock statement and the program will run without generating the error message -- and run with no issues.


I'm thinking the glitch is in the .lock_offset, as it seems as if _COPYIMAGE doesn't create a new lock_offset, but instead just basically copies the majority of that information from the original image to the copied version. 

image ihan& gets the original .lock_offset, then image dark& basically copies that value, and then image &darker basically copies that same exact value...  Freeing any of those makes our _MEM routines fail.  (if ( ((mem_lock*)(((mem_block*)(blk))->lock_offset))->id!=((mem_block*)(blk))->lock_id ){error(308); goto fail;})

The solution is still beyond me, but it appears that lock_offset may be the issue at the root of the problem. 

The memory is good.  The memblocks are valid -- just remark out that call to free_mem_lock in the freeing routine, and you'll see that we can read and write to the memory properly.  The images aren't corrupt.  _MEM isn't corrupt.  It's just a glitch with that lock_offset being copied and freed once, which seems to be the issue to me. 
https://github.com/SteveMcNeill/Steve64 — A github collection of all things Steve!

Offline FellippeHeitor

  • QB64 Developer
  • Forum Resident
  • Posts: 2689
  • LET IT = BE
    • QB64.org
Re: Glitch/Bug between _FREEIMAGE/_COPYIMAGE/_MEM (Critical Error #308)
« Reply #5 on: July 21, 2018, 01:44:07 PM »
Hopefully it's something simple.

If you do decide to push changes, please make sure to work on the development branch.

Offline RhoSigma

  • Seasoned Forum Regular
  • Posts: 417
Re: Glitch/Bug between _FREEIMAGE/_COPYIMAGE/_MEM (Critical Error #308)
« Reply #6 on: July 28, 2018, 06:18:54 AM »
Hi QB64 team,
I've found the easiest fix for this problem.

As far I've inspected libqb.cpp, new images are always created without lock_id/lock_offset. Just in the moment when I'll use the image with _MEM functions, then a lock is assigned to this image by the _MEMIMAGE call. Also _MEMIMAGE is smart enough to check for an existing lock first, before creating a new one, hence we can call _MEMIMAGE multiple times on the very same image and it will always use the first assigned lock.

The _FREEIMAGE function makes sure to kill an existing lock on the freed image (if any), if the image was never used with _MEM functions, then there's even no lock to kill.

As already explained by Steve, _COPYIMAGE just makes a copy of the general data of the given image, hence it also duplicates an existing lock, but the lock was intended for the original image only and causes us problems when one of the images (copy or original) is freed.

Now we know _MEMIMAGE is a smart function when it comes to lock the image, so all we need to do in _COPYIMAGE is to make the copy of course, but right after that we clear out the lock related fields in the new image structure to force _MEMIMAGE to create a new lock for the copied image. We otherwise don't touch an existing lock of the original image, as this must stay in effect.

So finally after all the theory, we just need to add one line of code in file libqb.cpp in the func_copyimage its "//duplicate structure" block right after the memcpy call.

Code: C: [Select]
  1.     ...
  2.     ...
  3.     ...
  4.     //duplicate structure
  5.     i2=newimg();
  6.     d=&img[i2];
  7.     memcpy(d,s,sizeof(img_struct));
  8.     img[i2].lock_id=NULL; img[i2].lock_offset=NULL; // force _MEMIMAGE to get a new lock for the copy
  9.     ...
  10.     ...
  11.     ...
  12.  

I've tested this fix in various QB64 versions inclusive my oldest (v0.954 SDL) and the latest available development build and it works, so somebody can please make the change in the repository.
« Last Edit: May 01, 2019, 11:23:04 PM by RhoSigma »
My Projects: https://www.qb64.org/forum/index.php?topic=809
GuiTools - Build graphic UIs dynamically at runtime. Is able to have multiple forms windows in one program.
Bonus - Libraries (Image processing/Data buffering/MD5/SHA2/LZW...), Blankers, QB64/Notepad++ setup pack

Offline FellippeHeitor

  • QB64 Developer
  • Forum Resident
  • Posts: 2689
  • LET IT = BE
    • QB64.org
Re: Glitch/Bug between _FREEIMAGE/_COPYIMAGE/_MEM (Critical Error #308)
« Reply #7 on: July 28, 2018, 06:49:11 AM »
A one-liner fix is always a very satisfying solution. Thanks for investigating, RhoSigma.

You sure you don't wanna make the change yourself? It'd be a pleasure to welcome a patch from you to the repo (I can walk you through if you need me to).
« Last Edit: July 28, 2018, 06:50:13 AM by FellippeHeitor »

Offline RhoSigma

  • Seasoned Forum Regular
  • Posts: 417
Re: Glitch/Bug between _FREEIMAGE/_COPYIMAGE/_MEM (Critical Error #308)
« Reply #8 on: July 28, 2018, 08:40:30 AM »
...You sure you don't wanna make the change yourself?...

:), yes I'am, please go on do it for me, see the text beneth my avatar? I hardly can find time to improve on my GuiTools Framework, so I won't start going any deeper into QB64 development at this point. This fix in an exception, as the glitch came up while working on GuiTools.

BTW -  The problem is not really the time, but my wife. Every day I spend more than 1-2 houres in front of the pc I get into trouble (should have known this 20 years ago) :(
My Projects: https://www.qb64.org/forum/index.php?topic=809
GuiTools - Build graphic UIs dynamically at runtime. Is able to have multiple forms windows in one program.
Bonus - Libraries (Image processing/Data buffering/MD5/SHA2/LZW...), Blankers, QB64/Notepad++ setup pack

Offline FellippeHeitor

  • QB64 Developer
  • Forum Resident
  • Posts: 2689
  • LET IT = BE
    • QB64.org
Re: Glitch/Bug between _FREEIMAGE/_COPYIMAGE/_MEM (Critical Error #308)
« Reply #9 on: July 28, 2018, 08:48:33 AM »
This is not going any deeper than you've gone already. You've got it done, it'd be a matter of copy pasting the line.

It'd take you five minutes. I'll let you consider. We have time. I'll post instructions, may encourage others.
« Last Edit: July 28, 2018, 08:49:39 AM by FellippeHeitor »

Offline RhoSigma

  • Seasoned Forum Regular
  • Posts: 417
Re: Glitch/Bug between _FREEIMAGE/_COPYIMAGE/_MEM (Critical Error #308)
« Reply #10 on: July 28, 2018, 09:44:57 AM »
Five minutes? - Well, then post the instructions and I'll see...
My Projects: https://www.qb64.org/forum/index.php?topic=809
GuiTools - Build graphic UIs dynamically at runtime. Is able to have multiple forms windows in one program.
Bonus - Libraries (Image processing/Data buffering/MD5/SHA2/LZW...), Blankers, QB64/Notepad++ setup pack

Offline FellippeHeitor

  • QB64 Developer
  • Forum Resident
  • Posts: 2689
  • LET IT = BE
    • QB64.org
Re: Glitch/Bug between _FREEIMAGE/_COPYIMAGE/_MEM (Critical Error #308)
« Reply #11 on: July 28, 2018, 12:03:37 PM »
First, make sure you made your edit to the latest version of libqb.cpp (this one: https://raw.githubusercontent.com/Galleondragon/qb64/development/internal/c/libqb.cpp)

  • Create a free account at www.github.com
  • Go to https://github.com/Galleondragon/qb64 and click the Fork button
  • Now you have your own repository fork at your-account/qb64, which you can edit. Where you see Branch: master, click to change to Branch: development
  • Navigate to internal/c
  • Click on Upload files and upload your edited version of libqb.cpp

After all is done, GitHub will offer you a button to Make a pull request, which you will do. Then you're done.

You became a contributor. The rest is with us.

Offline RhoSigma

  • Seasoned Forum Regular
  • Posts: 417
Re: Glitch/Bug between _FREEIMAGE/_COPYIMAGE/_MEM (Critical Error #308)
« Reply #12 on: July 28, 2018, 12:45:06 PM »
Done, hopefully everything went well, GitHub is a monster - seems much to complex for my taste.

Username "RhoSigma" was already taken, so you find me as "RhoSigma-QB64" instead.
My Projects: https://www.qb64.org/forum/index.php?topic=809
GuiTools - Build graphic UIs dynamically at runtime. Is able to have multiple forms windows in one program.
Bonus - Libraries (Image processing/Data buffering/MD5/SHA2/LZW...), Blankers, QB64/Notepad++ setup pack

Offline FellippeHeitor

  • QB64 Developer
  • Forum Resident
  • Posts: 2689
  • LET IT = BE
    • QB64.org
Re: Glitch/Bug between _FREEIMAGE/_COPYIMAGE/_MEM (Critical Error #308)
« Reply #13 on: July 28, 2018, 01:24:28 PM »
Great! I’ve just merged your pull request into the development branch and the dev build at www.qb64.org should be updated in a few minutes with this patch.

Edit: development build updated and available.
« Last Edit: July 28, 2018, 01:39:10 PM by FellippeHeitor »

Offline RhoSigma

  • Seasoned Forum Regular
  • Posts: 417
Well, now I've one remaining question, does merging branches automatically keep the fork in my repository up-to-date, or must I get a new fork from galleondragon/qb64 whenever I've to contribute something?
My Projects: https://www.qb64.org/forum/index.php?topic=809
GuiTools - Build graphic UIs dynamically at runtime. Is able to have multiple forms windows in one program.
Bonus - Libraries (Image processing/Data buffering/MD5/SHA2/LZW...), Blankers, QB64/Notepad++ setup pack