Ошибка memory access out bounds

@gordyr

I have successfully compiled stb_dxt, a DXT texture compressor written in C++, to emscripten and asm.js. This works wonderfully in Firefox but performance in Chrome is poor. I am therefore attempting to compile the same program to WebAssembly using the following flags:

emcc -O3 stb_dxt.cpp -o dxt.js -s DISABLE_EXCEPTION_CATCHING=1 -s NO_FILESYSTEM=1 -s EXPORTED_FUNCTIONS="['_rygCompress']" -s WASM=1 -s ALLOW_MEMORY_GROWTH=1

My Javascript glue code, which works perfectly with asm.js, needs to pass in an image ArrayBuffer like so:

    this.width = input.width;
    this.height = input.height;
    this.srcSize = input.data.length*input.data.BYTES_PER_ELEMENT;
    this.inputPtr = STB.Module._malloc(this.srcSize);
    this.outputPtr = STB.Module._malloc(Math.max(8, this.srcSize/8));

    this.inputHeap = new Uint8Array(STB.Module.HEAPU8.buffer, this.inputPtr, this.srcSize);
    this.outputHeap = new Uint8Array(STB.Module.HEAPU8.buffer, this.outputPtr, Math.max(8, this.srcSize/8));

    //set inputHeap to jpeg decoded RGBA data
    this.inputHeap.set(input.data);

As you can see I am using _malloc to allocate the memory for the passed in ArrayBuffer and also for what will be the resulting output ArrayBuffer once the program has run and compressed the texture to DXT1.

However, as soon as the first call to _malloc fires, I am getting an out of bounds memory error:

Uncaught (in promise) RuntimeError: memory access out of bounds
    at <WASM UNNAMED> (<WASM>[24]+7062)
    at Object.Module._malloc

I assume that I’m doing something wrong, any help would be greatly appreciated.

The code I am trying to compile is available here: https://github.com/nothings/stb/blob/master/stb_dxt.h

@kripken

My first guess is that wasm startup is async, and perhaps you’re calling into compiled code too early? Building with -s ASSERTIONS=1 -s SAFE_HEAP=1 might find something.

(Otherwise, all those options and that code should work identically on asm.js and wasm, nothing seems wrong there.)

@kripken

Also worth checking if -s BINARYEN_ASYNC_COMPILATION=0 changes things.

@gordyr

Thanks for the quick response, it’s nearly 1am here. I’ll give your suggestions a try in the morning and let you know how I get on.

@gordyr

@kripken — I’ve tried compiling with the flags you suggested and I now get a series of memory enlargement related errors:

Module.reallocBuffer: Attempted to grow from 16777216 bytes to 134217728 bytes, but got error: RangeError: WebAssembly.Memory.grow(): maximum memory size exceeded
Failed to grow the heap from 16777216 bytes to 134217728 bytes, not enough memory!
failed to set errno from JS
wasm-00010efe-24:3754 Uncaught (in promise) RuntimeError: memory access out of bounds
    at <WASM UNNAMED> (<WASM>[24]+7062)

I forgot to mentioned previously, but once I have compiled the program and first run it, I get the following error:

WebAssembly Instantiation: memory import 10 has no maximum limit, expected at most 4294967295

It would appear that WebAssembly has recently changed their instantiation method to require the previously optional «maximum» option to be present.

Therefore, I am manually adding this option into the resulting javascript file following compilation:

Module["wasmMemory"]=new WebAssembly.Memory({"initial":TOTAL_MEMORY/WASM_PAGE_SIZE, "maximum" : 256});

Perhaps this has something to do with it? However, my understanding was this that was only for the WebAssembly instantiation, and was not in any way related to the amount of memory that a compiled program can use, which should be grown internally as it runs.

Perhaps there have been some breaking changes recently?

@gordyr

Interestingly, if I divide the amount of memory I am trying to allocate by WebAssembly’s page size, the call to _malloc works:

var WASM_PAGE_SIZE = 65536;
this.srcSize = input.data.length * input.data.BYTES_PER_ELEMENT / WASM_PAGE_SIZE ;
this.inputPtr = STB.Module._malloc(this.srcSize);

But then of course, when I then create the Uint8Array() view and try to set the data, the buffer isn’t large enough.

Why should the _malloc call fail when I pass in the true length of the input data in bytes? (67108864)

@gordyr

I’ve managed to fix my issue by doing the following:

  1. Compiling with -s TOTAL_MEMORY=512MB
  2. Manually editing the javascript as follows:
Module["wasmMemory"]=new WebAssembly.Memory({"initial":TOTAL_MEMORY/WASM_PAGE_SIZE, "maximum" : TOTAL_MEMORY/WASM_PAGE_SIZE});

Everything then works, but sadly performance is far worse than compiling to asm.js.

  • In Firefox nightly running my program goes from 450ms in asm.js, to around 3,500ms.
  • In Chrome Canary, is goes from 1500ms to 3,800ms.

Is there anything I can do to improve this? Or is it simply down to the fact that WebAssembly isn’t optimized to the level of asm.js yet?

@kripken

The BINARYEN_MEM_MAX option might help those build issues. Although things should work without it too, so something’s gone wrong, but I don’t remember all the details here to know what offhand — this went through a few revisions.

About perf, that is very surprising. It’s just the wasm flag changed between those? Can you put up links to asm.js and wasm versions, built with --profiling, so we can reproduce? If it does reproduce, we should file bugs on browsers, although if it makes both of those browsers slower, perhaps it’s our fault in the toolchain somehow.

@GordoRank

@kripken — After a few hours of profiling and tweaking I have been able to improve on performance to the point where the WASM version is now significantly faster then the asm.js one, in Chrome. Firefox is still slightly faster when using asm.js however. Regardless, it seems that setting the input buffer in Chrome was causing a huge bottleneck for me but witching to Uint32Array views over the input ArrayBuffer made the copy far faster and solved it.

So this is great news, but still, I have to manually edit and include a maximum property in the javascript that’s output by Emscripten.

@juj

It would appear that WebAssembly has recently changed their instantiation method to require the previously optional «maximum» option to be present.

Do you have more info about this? I believe maximum should still be optional?

@gordyr

Do you have more info about this? I believe maximum should still be optional?

The only info I can give you is that without the maximum option being explicitly stated, compilation fails for me, giving a memory import 10 has no maximum limit error. The second I provide the option and set it to the same as the already present initial value, the error goes away and everything works.

This is using the latest incoming build of Emscripten… Sorry I can’t be any more help.

@kripken

Very strange. We need a full testcase to diagnose this, I think, I’m not sure where things are going wrong.

@evanw

We’re also experiencing rare errors like this sometimes at Figma, but only in Chrome (not Firefox). I logged a bug with Chrome before I found this issue.

@evanw

I finally got SAFE_HEAP working. I verified that SAFE_HEAP works by making sure *(char*)0x3FFFFFFF triggers the heap checks (in both Firefox and Chrome). I then ran Figma with SAFE_HEAP enabled and no heap checks were triggered (in both Firefox and Chrome) but Firefox loaded fine and Chrome threw «memory access out of bounds». Does this mean the bug is in Chrome and not in emscripten? Is there anything else I should try?

@kripken

I’m pretty sure it’s a Chrome bug at this point, yeah. One last thing I’d try is to build with -s DETERMINISTIC=1 which removes timing and randomness out of the picture. Also can’t hurt to add ASSERTIONS.

@evanw

ASSERTIONS was a good idea. It looks like this is the problem:

Module.reallocBuffer: Attempted to grow from 1073741824 bytes to 1342177280 bytes, but got error: RangeError: WebAssembly.Memory.grow(): maximum memory size exceeded

Firefox allows the memory to grow over 1gb while Chrome doesn’t. Figma sometimes needs more than 1gb of memory for large documents. Is the 1gb limit something that emscripten sets? Or is this a limitation of Chrome’s implementation in particular?

@evanw

I saw your comment above about BINARYEN_MEM_MAX so I tried -s BINARYEN_MEM_MAX=2147418112 but Chrome still fails to grow to 1342177280.

@kripken

Interesting. Looks like a chrome bug, emscripten doesn’t set a 1GB limit (and it wouldn’t be browser-specific in any case). Let’s maybe move the discussion back to the chromium bug tracker to make sure they see it.

@evanw

@kripken

Another thought here (not relevant to the Chrome side, so not posting in the bug there): in Chrome the allocation failed, so your app should have seen malloc/new return NULL. Does that happen properly for you, or is emscripten messing that up somehow?

@kripken

(That’s assuming you have ABORTING_MALLOC turned off, which I think is the case? Otherwise it should have called abort and halted the app.)

@evanw

The crash is actually in malloc, not in our code. The call to malloc is still executing (i.e. at the top of the call stack) when the crash occurs so it never has the chance to return null. Here’s what the stack trace looks like:

Uncaught RuntimeError: memory access out of bounds
    at <WASM UNNAMED> (<WASM>[331 (_malloc)]+4483)
    at <WASM UNNAMED> (<WASM>[703 (_malloca)]+138)
    at <WASM UNNAMED> (<WASM>[714 (operator new(unsigned int))]+9)
    at <WASM UNNAMED> (<WASM>[6072 (Codec::encodePhaseAndPropertyChanges(MultiplayerMessage const&, Fig::Message&))]+772)
    at <WASM UNNAMED> (<WASM>[6070 (Codec::toBuffer(MultiplayerMessage const&, MessageFormat))]+1135)
    at <WASM UNNAMED> (<WASM>[6069 (MultiplayerMessage::toBuffer(ImageMode, MessageFormat) const)]+197)
    ...

For what it’s worth, I did verify that we aren’t get any calls to malloc returning NULL before the crash.

@evanw

I think I’ve figured it out. We #define MALLOC_ALIGNMENT 16 before dlmalloc.cpp. This is supposed to be safe:

MALLOC_ALIGNMENT         default: (size_t)(2 * sizeof(void *))
Controls the minimum alignment for malloc'ed chunks.  It must be a
power of two and at least 8, even on machines for which smaller
alignments would suffice. It may be defined as larger than this
though. Note however that code and data structures are optimized for
the case of 8-byte alignment.

Calling malloc crashes in Chrome if we #define MALLOC_ALIGNMENT 16 but doesn’t crash if we #define MALLOC_ALIGNMENT 8 (the default). Any idea why this might be?

@evanw

Scratch that, never mind. That just lets us load the app in barely under 1gb. We still have the same problem if I make the document slightly bigger.

@kripken

Yeah, that crashing in malloc is definitely a sign of a problem in emscripten. I debugged it and found the issues, fixes and details in #5289.

@kripken

Should be fixed by that merged PR.

@Y0QIN

I’m also experiencing this error when my program runs for a few minutes in Chrome
Uncaught RuntimeError: memory access out of bounds
at wasm-function[79]:46
at wasm-function[401]:379
at wasm-function[992]:1112
at wasm-function[338]:533
at wasm-function[1253]:704
at wasm-function[748]:707
at wasm-function[1378]:245
at wasm-function[1348]:205
at wasm-function[1356]:9

@kripken

@Y0QIN — that might be a different issue, as I think the one here was fixed. To debug it, I’d start with building with -g so that stack trace is readable, and then hopefully what’s going wrong can be figured out. If not, if you can share a testcase we can debug that here.

surikov

added a commit
to surikov/riffshare
that referenced
this issue

Nov 19, 2018

@surikov

I was getting this error when I try to load my Web game:
enter image description here

My game is loaded at this domain:
Highest Flavor Website Link

This is my project settings at the time of build export:
enter image description here

Please give me some suggestion to solve this problem.

asked Mar 12, 2020 at 14:13

Siddharth's user avatar

6

This error happened to me due to using the dynamic keyword. After replacing all my dynamic types with object the error was gone. Apparently the dynamic keyword is not supported on WebGL builds.

answered Sep 23, 2020 at 20:03

Endel Dreyer's user avatar

Endel DreyerEndel Dreyer

1,63418 silver badges27 bronze badges

I’ve finally fixed the same error in my project. Here is what i did:

  1. Narrow down the line of code that crashes WebGL.
    Go through the painfull process of pinpointing the line of code that
    is the source of the error. I had this «luck» that error occured
    when I’ve hit button and tried to load UIScene in Addition mode. So
    at first I found the script in the scene which when disabled got rid
    of the crash. Then I’ve repeated the steps and digged deeper into
    lines. After 2 hours of builds, comments etc I’ve found that the code
    was not working was setting the new color for UnityEngine.UI.Image.
    Which was pretty weird because in playmode everything worked ok.

  2. Find solution. I think my solution might not be universal but I think there is something going in the Unity gameloop/lifecycle when running WebGL. The source of the problem was that i set the field that was storing my UI.Image in the Start method and then in some function I’ve tried to change the color.
    Property was not exposed in inspector.

    public class ModuleUI : MonoBehaviour
    {
        Image qualityBG;
        void Start()
        {
           qualityBG = GetComponent<Image>();
        }
    ...
    

then this line below was the cause of the crash

public void Set(Module module)
     {
         ...
         qualityBG.color = module.Quality.ToColor();
    }
  1. Fixing — so I’ve added [SerializeField] attribute to the field and set it up in editor, dragged the Image into the inspector slot. An it fixed it. I suspect that WebGL build might perform some methods in different order, or maybe the multipleScene loaded together might to do with it. I’m not sure but the field was not set properly with previous code fot WebGL build.

Bonus:
I’ve also fixed some issues that was not critical (not crushing WebGL game) but not working properly as expected in playmode. This also has got to do with trying to set some object in Start() method and then doing something on them. The collection was not set up (probably null) in WebGL.

EffectUI[] effects;
Start()
{
    effects = GetComponentsInChildren<EffectUI>();
}
void HideAllEffects()
    {
        if (effects != null)
            for (int i = 0; i < effects.Length; ++i)
                effects[i].gameObject.SetActive(false);
    }

And this also started working when I’ve added [SerializeField] to effects and hook it up in the scene…

Hope this will help some of you guys & gals!

answered Jan 27, 2021 at 12:35

pawciu's user avatar

pawciupawciu

8259 silver badges15 bronze badges

To run this build within the web browser — I have removed all extra things related code those can’t work within the web build like in-app purchase, advertisements, leaderboard, native sharing etc…

After these things code removed from the project, I uploaded the exported build content to my hosting. Then after it started working properly.

answered Apr 5, 2020 at 17:22

Siddharth's user avatar

SiddharthSiddharth

4,1229 gold badges44 silver badges89 bronze badges

1

  • Spaces
    • Default
    • Help Room
    • META
    • Moderators
    • Topics
    • Questions
    • Users
    • Badges
  • Home /
  • Help Room /

avatar image

Hello to everybody, so im facing this error «Runtime Error: memory access out of bounds.» when i run my webGL build on some mobile browsers.


  • These are my tests:

  • Fail: Chrome for android, version 87.0.4280.101 (latest stable until now);

  • Success: Firefox for android, version 84.1.1 (latest stable until now);

  • Fail: Safari for iOS (latest stable version);

  • Fail: Chrome for iOS (latest stable version);

  • Fail: Firefox for iOS (latest stable version);

  • On desktop, works correctly on any browser.


    How do i generate this error:

Just by running a WWW or UnityWebRequest to download a file data like this:

 byte[] data = null;
 WWW www = new WWW(url);
 yield return www;
 if (www.error != null)
     throw new Exception(www.error);
 else
     data = www.bytes;
         
 print(www.text.Length);//Printing the text lenght give the error
 print(data.Length);//Printing the bytes lenght doesnt give the error


Note: it happens only with files that are like 10/20MB or heavier, i’ve tried with a file that is less than 1MB and it works correctly on all the mobile browsers. I already read that a solution should be by increasing the memory allocation / memory heap, but i didnt understand how.

i already tried this and many other ways:

  var maxHeapSize = 256 * 1024 * 1024;
  UnityEditor.PlayerSettings.WebGL.emscriptenArgs = string.Format("-s WASM_MEM_MAX={0} -s ALLOW_MEMORY_GROWTH=1", maxHeapSize);

and this too:

 UnityEditor.PlayerSettings.WebGL.memorySize = 1024;

But nothing changes, the error is still showing. And yes, i tried even with UnityWebRequest but still same error.


Ps: Actually i dont need to print the text lenght, but since i need to access to that string, the error will be given anyway even just for a simple access to the string (i guess its because the string is too large and so i should allocate memory first, BUT HOW?)


  • My Unity3D version is: 2020.1.10f1

  • Player settings > Other Settings: alt text

  • Player settings > Publishing Settings: alt text


Thanks to everyone who can help me.

I have a similar problem now. If you get an answer, then beep.

Update: i’ve tried with the latest stable version of unity (2021.1.15f1) and there is still the same problem. Someone can help me?

Thanks.

avatar image

Answer by Pawciu · Jan 27, 2021 at 12:53 PM

I’ve finally fixed the same error in my project. Here is what i did:

1. Narrow down the line of code that crashes WebGL.
Go through the painfull process of pinpointing the line of code that is the source of the error. I had this «luck» that error occured when I’ve hit button and tried to load UIScene in Addition mode. So at first I found the script in the scene which when disabled got rid of the crash. Then I’ve repeated the steps and digged deeper into lines. After 2 hours of builds, comments etc I’ve found that the code was not working was setting the new color for UnityEngine.UI.Image. Which was pretty weird because in playmode everything worked ok.

2. Find solution.
I think my solution might not be universal but I think there is something going in the Unity gameloop/lifecycle when running WebGL. The source of the problem was that i set the field that was storing my UI.Image in the Start method and then in some function I’ve tried to change the color. Property was not exposed in inspector.

 public class ModuleUI : MonoBehaviour
 {
     Image qualityBG;
 
     void Start()
     {
         qualityBG = GetComponent<Image>();
     }

then this line below was the cause of the crash

 public void Set(Module module)
     {
         ...
         qualityBG.color = module.Quality.ToColor();
    }

3. Fixing — so I’ve added [SerializeField] attribute to the field and set it up in editor, dragged the Image into the inspector slot. An it fixed it. I suspect that WebGL build might perform some methods in different order, or maybe the multipleScene loaded together might to do with it. I’m not sure but the field was not set properly with previous code fot WebGL build.

Bonus: I’ve also fixed some issues that was not critical (not crushing WebGL game) but not working properly as expected in playmode. This also has got to do with trying to set some object in Start() method and then doing something on them. The collection was not set up (probably null) in WebGL.

 EffectUI[] effects;
 Start()
 {
     effects = GetComponentsInChildren<EffectUI>();
 }
 void HideAllEffects()
     {
         if (effects != null)
             for (int i = 0; i < effects.Length; ++i)
                 effects[i].gameObject.SetActive(false);
     }

And this also started working when I’ve added [SerializeField] to effects and hook it up in the scene…

Hope this will help some of you guys & gals!

DavidLiebemann

JayadevHaddadi

Im sorry but this one doesnt fix my issue.

avatar image

Answer by CTePeoTun · Dec 24, 2020 at 12:44 PM

I have a similar problem now. If you get an answer, then beep.

avatar image

Answer by VRinteractive · Jan 07, 2021 at 10:18 AM

Up .

Update: i’ve tried with the latest stable version of unity (2021.1.15f1) and there is still the same problem. Someone can help me?

Thanks.

avatar image

Answer by astrolabe-games · Mar 21, 2022 at 10:36 AM

in my case the problem was solved with the option «full without stacktrace» in the «enable exception field» alt text

I tried Full Without Stacktrace without any luck with Unity 2021.3.1f1.

It seems that WebGL builds are throwing «out of bounds memory access» runtime errors on my iPad (9th Generation) on iOS (15.4). I tested with Chrome and Safari.

I created a bare bones repro project. It’s just an empty scene with a text label. https://github.com/tgraupmann/Unity2021TestWebGL

I did a development build for WebGL.

With Enable Exceptions: «Explicitly Thrown Exceptions Only» set https://theylovegames.com/Unity2021TestWebGL/

With Enable Exceptions: «Full Without Stacktrace» set https://theylovegames.com/Unity2021TestWebGL2/

It appears to work fine on PC and Android. The iPad is my only iOS device for testing.

I ran into other issues with the new 2021 build process that were not problems in 2020. I can’t have UTF8 characters in the WebGL file, even in commented out code. Otherwise the build will fail. I had cache issues where I’d remove C# scripts and they were still being compiled by the WebGL build process. I had to close the editor and delete the Library folder to clear the cache.

This says the issue may be fixed in 2022.1.0a14 which is available in the Unity Hub under Pre-releases… 2022.2.0a10 is currently available. https://issuetracker.unity3d.com/issues/webgl-runtimeerror-memory-access-out-of-bounds-when-building-and-running-build

The errors still reproduce on the latest alpha.

2022.2.0a10 — Explicitly Thrown Exceptions Only — https://theylovegames.com/Unity2021TestWebGL_A1/

2022.2.0a10 — Mem128 — https://theylovegames.com/Unity2021TestWebGL_A2/

2022.2.0a10 — Full No Stack Trace — https://theylovegames.com/Unity2021TestWebGL_A3/

avatar image

Answer by Damast · May 22, 2022 at 07:43 PM

Same problem Unity 2019.4.39f1 & 2022.2.0a13 «RuntimeError: Out of bounds memory access» Android, Chrome Desktop works, IOS fails

  • 1
  • 2

Unity Answers is in Read-Only mode

Unity Answers content will be migrated to a new Community platform and we are aiming to launch a public beta on June 13. Please note, Unity Answers is now in read-only so we can prepare for the final data migration.

For more information and updates, please read our full announcement thread in the Unity Forum.

Follow this Question

Related Questions

Время на прочтение
11 мин

Количество просмотров 4K

Ошибки, связанные с доступом к областям памяти, которые находятся за пределами допустимого адресного пространства (out-of-bounds memory access), в 2021 году всё ещё пребывают в списке самых опасных уязвимостей ПО CWE Top 25. Известно, что ошибочные операции записи данных (out-of-bounds write, CWE-787) с двенадцатого места, которое они занимали в 2019 году, перешли в 2020 году на второе. А неправильные операции чтения данных (out-of-bounds read, CWE-125) в тех же временных пределах сменили пятое место на четвёртое.

Понимание важности раннего выявления ошибок, приводящих к вышеозначенным проблемам, привело к тому, что в свежих релизах компиляторов GNU Compiler Collection (GCC) была значительно улучшена возможность детектирования подобных ошибок. Речь идёт об использовании ключей для проведения проверок и вывода предупреждений наподобие -Warray-bounds, -Wformat-overflow, -Wstringop-overflow и (самая свежая возможность, появившаяся в GCC 11) -Wstringop-overread. Но всем этим проверкам свойственно одно и то же ограничение, связанное с тем, что система может обнаруживать проблемные ситуации лишь в пределах отдельных функций. Получается, что, за исключением анализа небольшого набора встроенных в компилятор функций, вроде memcpy(), проверка прекращается на границе вызова функции. То есть, например, если буфер, объявленный в функции A, переполняется в функции B, вызванной из функции A, компилятор, если функция B не встроена в функцию A, на эту проблему не реагирует.

В этом материале речь пойдёт о трёх видах простых подсказок, применяемых на уровне исходного кода, которые программист может использовать для того чтобы помочь GCC выявлять операции, связанные с доступом к областям памяти, находящимся за пределами допустимого адресного пространства. Причём, эти подсказки помогают компилятору находить проблемы и при пересечении границ вызова функций, и даже тогда, когда функции определены в разных файлах с исходным кодом.

А именно, речь идёт о следующих возможностях:

  • Атрибут функций access (он появился в GCC 10, доступен и в C, и в C++).
  • Параметры функций, представленные массивами переменной длины (Variable-length array, VLA) (это — новая возможность, которая появилась в GCC 11, доступна она лишь в C).
  • Параметры функций, представленные массивами (новая возможность GCC 11, она доступна лишь в C).

Атрибут access

Применение атрибута access может оказаться полезным в функциях, которые, в виде одного из аргументов, принимают указатель на буфер, а в виде второго аргумента — размер буфера. Это, например, пара POSIX-функций read() и write(). Помимо того, что данный атрибут помогает программисту связать эти два параметра, он ещё и позволяет описать то, как именно функция работает с буфером. Атрибут применяется к объявлениям функций. Он используется и в местах вызова функций, и при анализе объявлений функций с целью выявления ошибочных операций доступа к памяти.

Этот атрибут имеет следующий синтаксис:

  • access (access-mode, ref-index)
  • access (access-mode, ref-index, size-index)

Здесь ref-index и size-index указывают на позиционные аргументы, нумерация которых начинается с 1. Они олицетворяют собой, соответственно, буфер и его размер. Аргумент, представляющий собой буфер, ref-index, может быть объявлен как обычный указатель на объект, включая void*. Он, кроме того, может быть представлен в форме массива (вроде T[] или T[N]). Ему необязательно указывать на завершённый тип. Необязательный аргумент size-index должен ссылаться на целочисленный аргумент, который задаёт количество элементов массива, к которому может обращаться функция. В случае с буферами незавершённых типов, таких, как void*, считается, что size-index содержит сведения о количестве байтов. Если аргумент size-index не указан, предполагается, что буфер содержит один элемент.

Аргумент access-mode описывает то, как именно функция будет работать с буфером, режим доступа к нему. В GCC 11 предусмотрено четыре режима доступа к буферам:

  • Режим read_only указывает на то, что функция лишь считывает данные из предоставленного ей буфера, но ничего в него не записывает. Ожидается, что буфер будет инициализирован источником вызова функции. Режим read_only обеспечивает более надёжные гарантии неизменности буфера, чем квалификатор const, использованный при объявлении буфера, так как квалификатор можно снять и буфер может быть модифицирован в корректной программе, если только сам объект буфера не является неизменным. Параметр, к которому применяется режим read_only, может быть объявлен с квалификатором const (но это необязательно). В C99 объявление параметра как read_only имеет тот же смысл, что объявление его с использованием одновременно и const, и restrict (правда, GCC 11 не распознаёт эти два варианта объявления параметров как равнозначные).
  • Режим write_only указывает на то, что функция осуществляет только запись данных в предоставленный ей буфер, но ничего из него не читает. Буфер может быть неинициализированным. Попытка применить режим write_only к const-параметру приводит к выдаче предупреждения и к игнорированию атрибута. Это, фактически, режим доступа, используемый по умолчанию для параметров, которым не назначен атрибут access.
  • Режим read_write указывает на то, что функция и читает данные из буфера, и пишет их в него. Ожидается, что функции будет предоставлен инициализированный буфер. Попытка применения режима доступа read_write к параметру, объявленному с квалификатором const, приведёт к выдаче предупреждения, а атрибут будет проигнорирован.
  • Режим none говорит о том, что функция никак не работает с буфером. Буфер может быть неинициализированным. Это — режим, который появился в GCC 11, он предназначен для функций, которые выполняют валидацию аргументов без доступа к данным, хранящимся в буфере.

В следующем примере демонстрируется использование атрибута access для аннотирования POSIX-функций read() и write():

__attribute__ ((access (write_only, 2, 3))) ssize_t
read (int fd, void *buf, size_t nbytes); 
__attribute__ ((access (read_only, 2, 3))) ssize_t
write (int fd, const void *buf, size_t nbytes);

Функция read() сохраняет данные в предоставленный ей буфер. Поэтому в качестве режима доступа используется write_only. Функция write() читает данные из буфера. Поэтому тут используется режим доступа read_only.

Атрибут access играет роль, подобную той, которую играет объявление параметра функции с использованием массива переменной длины, но использование атрибута даёт больший уровень гибкости. Помимо возможности указания режима доступа к буферу, аргумент size-index может быть связан с указателем, размер которого идёт в списке аргументов функции после него. Именно так часто и происходит. Массивам переменной длины посвящён следующий раздел.

Параметры функций, представленные массивами переменной длины

В C (но, в GCC 11, не в C++), параметр функции, объявленный в виде массива, может ссылаться на неконстантное выражение, включая предыдущие параметры той же функции, используемое для задания границ массива. Когда значение, представляющее границу, является ссылкой на другой параметр функции, объявление этого параметра должно предшествовать VLA (GCC содержит расширение, позволяющее обойти это ограничение языка; дополнительные сведения об этом можно найти в соответствующем разделе документации по GCC). Если подобным способом устанавливается лишь верхняя граница массива, то он становится обычным указателем, похожим на любой другой массив. В противном случае это — VLA. Так как это различие между двумя видами массивов в данном контексте является достаточно тонким, в диагностических сообщениях GCC все эти массивы называются VLA. Мы, в продолжение этого материала, тоже будем придерживаться такого вот упрощённого подхода к массивам. Например:

void init_array (int n, int a[n]);

Эта функция принимает обычный массив (или, если точнее, указатель) в качестве второго аргумента. Количество элементов этого массива задаёт первый аргумент. Хотя это, в общем-то, не нарушает требований языка, передача функции массива с меньшим числом элементов, чем указано в первом аргументе, почти наверняка указывает на ошибку. GCC проверяет вызовы подобных функций и выдаёт предупреждения в тех случаях, когда определяет, что переданные им массивы меньше, чем ожидается. Например, имеется следующая программа:

#include <stdlib.h>

#define N 32

void init_array (int n, int a[n]);

int* f (void)
{
    int *a = (int *)malloc (N);
    init_array (N, a);
    return a;
}

При вызове функции init_array GCC находит проблему, выдавая следующее предупреждение:

In function 'f':
warning: 'init_array' accessing 128 bytes in a region of size 32 [-Wstringop-overflow=]
   10 |     init_array (N, a);
      |     ^~~~~~~~~~~~~~~~~
note: referencing argument 2 of type 'int *'
note: in a call to function 'init_array'
    5 | void init_array (int n, int a[n]);
      |      ^~~~~~~~~~

Компилятор, выдавая это предупреждение, предполагает наличие ошибки в коде, на которую указывает то, что init_array передают массив, который меньше, чем указано в первом аргументе.

Как уже было сказано, объявление массива, когда лишь его верхняя граница задаётся переменной, на самом деле, приводит к объявлению обычного массива, а не VLA. Значение имеет лишь нижняя граница массива. Это значит, что в следующем примере все объявления корректны и равноценны:

void init_vla (int n, int[n]);
void init_vla (int, int[32]);
void init_vla (int, int*);
void init_vla (int n, int[n + 1]);

Тут, правда, возникает одна проблема. Какое из этих объявлений должно быть использовано для выдачи предупреждения, связанного с доступом к элементу, лежащему за пределами границ массива? В GCC 11 реализовано решение, опирающееся на первое объявление функции, и выдающее отдельное предупреждение, -Wvla-parameter, для любых последующих повторных объявлений, описывающих другое количество элементов в массиве. В результате при анализе кода, содержащего четыре вышеприведённых объявления функций, выдаются следующие предупреждения:

warning: argument 2 of type 'int[32]' declared as an ordinary array [-Wvla-parameter]
    2 | void init_vla (int, int[32]);
      |                     ^~~~~~~
warning: argument 2 of type 'int *' declared as a pointer [-Wvla-parameter]
    3 | void init_vla (int, int*);
      |                     ^~~~
warning: argument 2 of type 'int[n + 1]' declared with mismatched bound [-Wvla-parameter]
    4 | void init_vla (int, int[n + 1]);
      |                     ^~~~~~~~~
note: previously declared as a variable length array 'int[n]'
    1 | void init_vla (int n, int[n]);
      |                       ^~~~~~

Параметры функций, представленные массивами

Современные C-программисты, опасаясь ситуаций с выделением в стеке памяти неограниченного объёма, как правило, используют массивы переменной длины не так часто, как их можно было бы использовать. Это справедливо даже для особых случаев, вроде объявлений функций, где применение таких массивов не только безопасно, но и помогает компилятору анализировать код. В некоторых проектах, где VLA не используются, применяется более простое решение, которое заключается в объявлении параметров функций в виде обычных массивов (например — T(N)). При таком подходе ожидается, что вызывающая сторона предоставит функции доступ к некоему постоянному минимальному числу элементов (например — N). Так, стандартная C-функция tmpnam() ожидает, что её аргумент будет указывать на массив, в котором содержится, как минимум, L_tmpnam элементов. Для того чтобы выразить это ожидание в явном виде, в GNU libc 2.34 эта функция объявлена так:

char *tmpnam (char[L_tmpnam]);

GCC 11 распознаёт такие конструкции. Когда компилятор выясняет, что при вызове функции ей передаётся массив, размер которого меньше, чем указано в объявлении функции, он выдаёт предупреждение. Например, учитывая то, что в Linux L_tmpnam равно 20, испытаем следующую функцию:

void g (void)
{
  char a[16];
  if (tmpnam (a))
    puts (a);
}

GCC выдаст следующее предупреждение:

In function 'g':
warning: 'tmpnam' accessing 20 bytes in a region of size 16 [-Wstringop-overflow=]
 10 | if (tmpnam (a))
    |     ^~~~~~~~~~
note: referencing argument 1 of type 'char *'
note: in a call to function 'tmpnam'
  3 | extern char* tmpnam (char[L_tmpnam]);
    |              ^~~~~~

GCC 11, в дополнение к вызовам функций, проверяет ещё и определения функций, в которых используются параметры-массивы, и выдаёт предупреждения при нахождении операций по работе с массивами, которые направлены на элементы, выходящие за постоянные границы массивов. Например, анализ объявления функции init_array(), код которой показан ниже, приводит к выдаче предупреждения -Warray-bounds.

Вот код функции:

void init_array (int, int a[32])
{ 
  a[32] = 0;
}

А вот — предупреждение:

In function 'init_array':
warning: array subscript 32 is outside array bounds of 'int[32]' [-Warray-bounds]
    3 |   a[32] = 0;
      |   ~^~~~
note: while referencing 'a'
    1 | void init_array (int, int a[32])
      |                       ~~~~^~~~~

Аналогично, если речь идёт о повторных объявлениях функции с использованием VLA-параметров, GCC, кроме прочего, проверяет и их с учётом свойств параметра-массива первой функции, и выдаёт предупреждение -Warray-parameter в случае обнаружения несоответствий между первым объявлением и повторными объявлениями функции. Например, речь может идти о следующих объявлениях функции:

void init_array (int, int[32]);
void init_array (int, int[16]);
void init_array (int n, int[]);
void init_array (int n, int*);

Вот предупреждения, которые выдаёт GCC:

warning: argument 2 of type 'int[16]' with mismatched bound [-Warray-parameter=]
    2 | void init_array (int, int[16]);
      |                       ^~~~~~~
warning: argument 2 of type 'int[]' with mismatched bound [-Warray-parameter=]
    3 | void init_array (int n, int[]);
      |                         ^~~~~
warning: argument 2 of type 'int *' declared as a pointer [-Warray-parameter=]
    4 | void init_array (int n, int*);
      |                         ^~~~
note: previously declared as an array 'int[32]'
    1 | void init_array (int, int[32]);
      |                       ^~~~~~~

Нюансы и ограничения

Те возможности GCC, о которых мы говорили, уникальны в одном интересном аспекте. А именно, их использование предусматривает применение к коду и простого лексического анализа, и более сложного динамического анализа, опирающегося на особенности выполнения этого кода. В теории, предупреждения, опирающиеся на лексический анализ, могут быть одновременно и надёжными, и полными (то есть — они не могут быть ни ложноположительными, ни ложноотрицательными). Предупреждения -Warray-parameter и -Wvla-parameters выдаются на основе лексического анализа кода, в результате они практически не страдают от подобных проблем. А вот предупреждения, основанные на динамическом анализе кода, с другой стороны, по своей природе, не являются ни надёжными, ни полными. Такие предупреждения, напротив, могут быть и ложноположительными, и ложноотрицательными.

▍Ложноотрицательные предупреждения

Для использования атрибутов access и выявления операций, которые направлены на области памяти, находящиеся за пределами допустимого адресного пространства, функция, к которой применяются такие атрибуты, не должна быть встроенной. Если же функция встроена в источник вызова, большинство её атрибутов обычно теряется. Это может помешать GCC в детектировании ошибок тогда, когда некорректные операции доступа к памяти не могут быть надёжно выявлены, основываясь лишь на коде тела встроенной функции. Например, функция genfname() из следующего примера использует, для генерирования имени временного файла в директории /tmp, функцию getpid():

#include <stdio.h>
#include <unistd.h>
  
inline void genfname (char name[27])
{
  snprintf (name, 27, "/tmp/tmpfile%u.txt", getpid ());
}

int main (void)
{
  char name[16];
  genfname (name);
  puts (name);
}

Так как в большинстве систем POSIX-функция getpid() возвращает 32-битное целое число, самое длинное имя, которое может сгенерировать эта функция, имеет длину 26 символов (10 символов с учётом INT_MAX, плюс — ещё 16 с учётом строки /tmp/tmpfile.txt; тут надо учесть и ещё один байт для завершающего нуль-символа). Когда вызов genfname() в main() не является вызовом встроенной функции, GCC, как и ожидается, выдаёт следующее предупреждение:

In function 'main':
warning: 'f' accessing 27 bytes in a region of size 16 [-Wstringop-overflow=]
   11 |   f (a);
      |   ^~~~~
note: referencing argument 1 of type 'char *'
note: in a call to function 'f'
    3 | inline void f (char a[27])
      |             ^

Но если речь идёт о вызове встроенной функции, то никакого предупреждения не выводится. Здесь можно поэкспериментировать с обоими этими сценариями использования функций.

К слову сказать, если вам интересно, почему вызов sprintf() не приводит к выводу предупреждения -Wformat-truncation, то знайте, что дело в том, что компилятор не может что-либо узнать о результатах вызова getpid().

▍Ложноположительные предупреждения

В целом можно сказать, что выявление некорректных операций доступа к памяти на основании аннотаций исходного кода, о которых мы тут говорили, подвержено тем же ограничениям и недостаткам, которые характерны для проверок кода, основанного на анализе особенностей его выполнения. Подробности об этом можно почитать здесь. В частности, определённый интерес в этом документе могут представлять несколько часто встречающихся проблем, относящихся к механизму аннотирования функций.

Как уже было сказано, в некоторых проектах используются параметры, представленные массивами, границы которых оформлены в виде констант. Это делается для того чтобы дать тому, кто вызывает функцию, подсказку о том, что он должен предоставить этой функции массив, в котором содержится, как минимум, столько элементов, сколько совпадает с количеством элементов в массиве-параметре. Но иногда разработчики функций не особенно строго придерживаются этого подхода. Например, функции используют массив лишь тогда, когда другой параметр имеет некое значение, а в других случаях не используют его. Так как нет ничего, что сообщило бы GCC об этой «особенности», предупреждение может быть выдано даже в том случае, если функция работает корректно. Мы рекомендуем не прибегать к вышеописанному подходу с массивами-параметрами в таких случаях.

Что дальше?

В GCC атрибут access можно использовать для выявления следующих проблем:

  • Доступ к областям памяти, находящимся за пределами допустимого адресного пространства (-Warray-bounds, -Wformat-overflow, -Wstringop-overflow и -Wstringop-overread).
  • Доступ к перекрывающимся областям памяти (-Wrestrict).
  • Доступ к неинициализированным областям памяти (-Wuninitialized).

В будущем нам хотелось бы использовать этот атрибут для выявления переменных, в которые осуществляется запись, но из которых никогда ничего не считывается (-Wunused-but-set-parameter и -Wunused-but-set-variable).

Мы, кроме того, рассматриваем возможность расширения, в какой-либо форме, атрибута access, для его применения к значениям, которые возвращают функции, а так же — к переменным. Аннотирование значений, возвращаемых функциями, позволит GCC детектировать попытки модификации иммутабельных объектов через указатели, возвращённые из функций (например — из функций getenv() или localeconv()). И, аналогично, аннотирование глобальных переменных позволит выявлять попытки случайной модификации содержимого соответствующих объектов (например — это может быть массив указателей environ, хранящий сведения о переменных окружения).

Пользуетесь ли вы подсказками для компиляторов, которые помогают им проверять код?

Сообщество Программистов

Загрузка…

Понравилась статья? Поделить с друзьями:
  • Ошибка memori management виндовс 10
  • Ошибка member was no read
  • Ошибка megane 2 подушка безопасности
  • Ошибка media station x на телевизоре lg
  • Ошибка media encoder код ошибки 3