Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Stack overflow / buffer underrun in 3.4.0RC1 in debug mode (MSVC x86_64) #2221

Closed
asottile opened this issue Oct 24, 2016 · 21 comments
Closed

Comments

@asottile
Copy link
Member

asottile commented Oct 24, 2016

Appears to be a regression of #2046

I'll try and get better details when I'm at a computer.

I'm at the 3.4.0RC1 tag: sass/libsass-python#166

Reproduction

(This time with proper file paths!):

test.bat

rm *.obj
"C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\BIN\x86_amd64\cl.exe" ^
    /Zi /c /nologo /Ox /O2 /W3 /GL /DNDEBUG /MD /EHsc /MT -I.\include ^
    "-IC:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\INCLUDE" ^
    "-IC:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\ATLMFC\INCLUDE" ^
    "-IC:\Program Files (x86)\Windows Kits\10\include\10.0.10240.0\ucrt" ^
    "-IC:\Program Files (x86)\Windows Kits\NETFXSDK\4.6.1\include\um" ^
    "-IC:\Program Files (x86)\Windows Kits\8.1\include\\shared" ^
    "-IC:\Program Files (x86)\Windows Kits\8.1\include\\um" ^
    "-IC:\Program Files (x86)\Windows Kits\8.1\include\\winrt" ^
    src/*.c

"C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\BIN\x86_amd64\cl.exe" ^
    /Zi /c /nologo /Ox /O2 /W3 /GL /DNDEBUG /MD /EHsc /MT -I.\include ^
    "-IC:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\INCLUDE" ^
    "-IC:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\ATLMFC\INCLUDE" ^
    "-IC:\Program Files (x86)\Windows Kits\10\include\10.0.10240.0\ucrt" ^
    "-IC:\Program Files (x86)\Windows Kits\NETFXSDK\4.6.1\include\um" ^
    "-IC:\Program Files (x86)\Windows Kits\8.1\include\\shared" ^
    "-IC:\Program Files (x86)\Windows Kits\8.1\include\\um" ^
    "-IC:\Program Files (x86)\Windows Kits\8.1\include\\winrt" ^
    src/*.cpp main.cpp

"C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\BIN\x86_amd64\link.exe" ^
    /nologo /INCREMENTAL:NO /LTCG ^
    "/LIBPATH:C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\LIB\amd64" ^
    "/LIBPATH:C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\ATLMFC\LIB\amd64" ^
    "/LIBPATH:C:\Program Files (x86)\Windows Kits\10\lib\10.0.10240.0\ucrt\x64" ^
    "/LIBPATH:C:\Program Files (x86)\Windows Kits\NETFXSDK\4.6.1\lib\um\x64" ^
    "/LIBPATH:C:\Program Files (x86)\Windows Kits\8.1\lib\winv6.3\um\x64" ^
    /OUT:main.exe /DEBUG *.obj
main.exe

main.cpp

#include <sass/context.h>
#include <iostream>

int main() {
    std::cout << "Making data context" << std::endl;
    struct Sass_Data_Context* context = sass_make_data_context(sass_copy_c_string("a { b { color: blue; }"));
    std::cout << "Compiling data context" << std::endl;
    sass_compile_data_context(context);
    std::cout << "Getting output context" << std::endl;
    struct Sass_Context* ctx = sass_data_context_get_context(context);
    if (sass_context_get_error_status(ctx)) {
        std::cout << "Printing error status" << std::endl;
        std::cout << sass_context_get_error_status(ctx) << std::endl;
        std::cout << "Printing error message" << std::endl;
        std::cout << sass_context_get_error_message(ctx) << std::endl;
    } else {
        std::cout << sass_context_get_output_string(ctx) << std::endl;
    }
    sass_delete_data_context(context);
    return 0;
}

Commands to reproduce

"C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\bin\x86_amd64\vcvarsx86_amd64.bat"
test.bat

Output

C:\Users\IEUser\libsass-python\libsass>test.bat

C:\Users\IEUser\libsass-python\libsass>rm *.obj

C:\Users\IEUser\libsass-python\libsass>"C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\BIN\x86_amd64\cl.exe"     /Zi /c /nologo /Ox /O2 /W3 /GL /DNDEBUG /MD /EHsc /MT -I.\include     "-IC:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\INCLUDE"     "-IC:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\ATLMFC\INCLUDE"     "-IC:\Program Files (x86)\Windows Kits\10\include\10.0.10240.0\ucrt"     "-IC:\Program Files (x86)\Windows Kits\NETFXSDK\4.6.1\include\um"     "-IC:\Program Files (x86)\Windows Kits\8.1\include\\shared"     "-IC:\Program Files (x86)\Windows Kits\8.1\include\\um"     "-IC:\Program Files (x86)\Windows Kits\8.1\include\\winrt"     src/*.c
cl : Command line warning D9025 : overriding '/MD' with '/MT'
c99func.c
cencode.c

C:\Users\IEUser\libsass-python\libsass>"C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\BIN\x86_amd64\cl.exe"     /Zi /c /nologo /Ox /O2 /W3 /GL /DNDEBUG /MD /EHsc /MT -I.\include     "-IC:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\INCLUDE"     "-IC:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\ATLMFC\INCLUDE"     "-IC:\Program Files (x86)\Windows Kits\10\include\10.0.10240.0\ucrt"     "-IC:\Program Files (x86)\Windows Kits\NETFXSDK\4.6.1\include\um"     "-IC:\Program Files (x86)\Windows Kits\8.1\include\\shared"     "-IC:\Program Files (x86)\Windows Kits\8.1\include\\um"     "-IC:\Program Files (x86)\Windows Kits\8.1\include\\winrt"     src/*.cpp main.cpp
cl : Command line warning D9025 : overriding '/MD' with '/MT'
ast.cpp
base64vlq.cpp
bind.cpp
check_nesting.cpp
color_maps.cpp
constants.cpp
context.cpp
cssize.cpp
emitter.cpp
environment.cpp
error_handling.cpp
eval.cpp
expand.cpp
extend.cpp
file.cpp
functions.cpp
inspect.cpp
json.cpp
lexer.cpp
listize.cpp
Compiling...
memory_manager.cpp
node.cpp
output.cpp
parser.cpp
plugins.cpp
position.cpp
prelexer.cpp
remove_placeholders.cpp
sass.cpp
sass2scss.cpp
sass_context.cpp
sass_functions.cpp
sass_util.cpp
sass_values.cpp
source_map.cpp
to_c.cpp
to_value.cpp
units.cpp
utf8_string.cpp
util.cpp
Compiling...
values.cpp
main.cpp

C:\Users\IEUser\libsass-python\libsass>"C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\BIN\x86_amd64\link.exe"     /nologo /INCREMENTAL:NO /LTCG     "/LIBPATH:C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\LIB\amd64"     "/LIBPATH:C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\ATLMFC\LIB\amd64"     "/LIBPATH:C:\Program Files (x86)\Windows Kits\10\lib\10.0.10240.0\ucrt\x64"     "/LIBPATH:C:\Program Files (x86)\Windows Kits\NETFXSDK\4.6.1\lib\um\x64"     "/LIBPATH:C:\Program Files (x86)\Windows Kits\8.1\lib\winv6.3\um\x64"     /OUT:main.exe /DEBUG *.obj
Generating code
Finished generating code

C:\Users\IEUser\libsass-python\libsass>main.exe
Making data context
Compiling data context

Where it breaks at

Puzzlingly, it appears to crash at the same point as the fix from last time: https://github.com/sass/libsass/blob/3.4.0-RC1/src/sass_context.cpp#L128

@asottile asottile changed the title Stack overflow / buffer underrun in 3.4.0RC1 in debug mode (MSVC) Stack overflow / buffer underrun in 3.4.0RC1 in debug mode (MSVC x86_64) Oct 24, 2016
@asottile
Copy link
Member Author

I'm debugging away at this, having a difficult time getting the link flags correct (I seem to be tickling some other unrelated compiler bug which is preventing me from properly linking)

@asottile
Copy link
Member Author

1 in the morning here, I'll look at this tomorrow. Sorry for the incomplete bug report, thought I'd be able to reuse my test harness from last time though I was unable to which has delayed my debugging significantly :(

@asottile
Copy link
Member Author

I've updated with my reproduction, I'm more confused than last time :S

@asottile
Copy link
Member Author

Stupidly this fixes it:

$ git diff -w
diff --git a/src/sass_context.cpp b/src/sass_context.cpp
index df92021..dc6f353 100644
--- a/src/sass_context.cpp
+++ b/src/sass_context.cpp
@@ -125,7 +125,8 @@ extern "C" {
       json_append_member(json_err, "message", json_mkstring(e.what()));
       json_append_member(json_err, "formatted", json_mkstream(msg_stream));
       try { c_ctx->error_json = json_stringify(json_err, "  "); } catch(...) {}
-      c_ctx->error_message = sass_copy_string(msg_stream.str());
+      std::string msg_stream_str = msg_stream.str();
+      c_ctx->error_message = sass_copy_string(msg_stream_str);
       c_ctx->error_text = sass_copy_c_string(e.what());
       c_ctx->error_status = 1;
       c_ctx->error_file = sass_copy_c_string(e.pstate.path);

Doesn't feel correct though, are we fighting inlining maybe?

@mgreter
Copy link
Contributor

mgreter commented Oct 24, 2016

Well, it looked like a compiler bug to me from the begining. You might be right about the inlining though. Somewhat didn't think of it when I created the "sass_copy_string" function. Maybe you can change it somehow to not expose this issue (eg. __declspec(noinline))? I somewhat suspect this strange behavior to be related to try catch, as I think I've only see this in error cases (similar stuff happened when we got an exception in overloaded new operator).

@asottile
Copy link
Member Author

I tried poking it some more and couldn't really get anything to work (tried __declspec(noinline), tried making the function take the stringstream instead, couldn't really think of a way to write a macro to do this. I wonder if trying to support /DEBUG is entirely sadness and I should look into hacking that out from our end...

@asottile
Copy link
Member Author

asottile commented Oct 24, 2016

I'm opting to disable /DEBUG under msvc: sass/libsass-python#167 (via the /Od flag)

@asottile
Copy link
Member Author

Unless you guys are interested in supporting /DEBUG I think this can be closed (perhaps documented somewhere that it is not supported?).

@xzyfer
Copy link
Contributor

xzyfer commented Nov 2, 2016

@am11 I'm not familiar with the use case for /DEBUG in VS. What are your thoughts on supporting it? Is it a common thing to do in the VS community?

@am11
Copy link
Contributor

am11 commented Nov 2, 2016

With Debug flag, compiler adds additional debug info to the generated program database (PDB) file (like source-map) containing all the symbols and their line/column infos etc. for source to source mapping (mach-code -> C[++] in this case).

Some people only generate PDBs in Debug Configuration and disable it for Release Configuration. But mostly it is considered a good practice to have it generated for all configurations.

If disabling fixes this issue, we can unset it in vcxproj file (GenerateDebugInformation under 'Release|Win32' condition). Although, it might hide the real issue either in compiler codegen or in our codebase, which will eventually crop up somewhere else IMO.

@xzyfer
Copy link
Contributor

xzyfer commented Nov 2, 2016

Thanks for your input @am11. If it's not actively causing harm I'd prefer to keep it as is. I'll leave this issue open as a reminder to look into the root cause.

@mgreter
Copy link
Contributor

mgreter commented Dec 6, 2016

If the fix by @asottile works (#2221 (comment)) we should just do that. I (and I'm pretty sure @asottile too) have tried quite some time to understand why this is happening. I fail to see why this even should/could happen (as statet, it looks like a compiler bug to me). Maybe a MSVC guru could enlighten us, but so far none has shown up.

@am11 IMHO debug mode does not only output pdb files, but also enables i.e. assertions in STL containers. Therefore I highly recommend to use release mode for actual releases, due to possible massive performance decrease by debug builds.

@am11
Copy link
Contributor

am11 commented Dec 6, 2016

@mgreter, I am all in for release-only builds. My point was merely this; if possible, we should make steps to repro and report the compiler bug upstream during the process of patching it the hacky whacky way. This way we will have a better/definite closure and some link to track this bug by. Generally speaking, sometimes during the process of separating out the steps to repro we rather find the bug in our code but this seems very unlikely in this case. :)

@mgreter
Copy link
Contributor

mgreter commented Dec 6, 2016

@am11 I agree and actually like the debug assertion checks for testing, but unfortunately this seems to be quite a heisenbug. It's hard to trigger reliably and even harder to do so in a reduced test case. I myself am more familiar with GCC, so we pretty much lack the expertise here. Not sure if it is a compiler bug, just saying that my gut feeling sofar tells me that :)

My educated guesses for this issue are based on that they tend to crop up when some error is catched and I've sofar (AFAIR) only saw it in relation to sass_context, which could indicate some issue with "extern C" and/or dll boundaries (I don't see how, but that at least would somewhat explain the strange behavior). But in the end this doesn't really make sense to me. I would expect a lot of other places where this should have cropped up.

Anyway, we did similar fixes in the past. Although I was able to see the logic in the ones we've made before. So we should at least change the pattern in said cpp file to take an explicit stack variable for the string from a stringstream before passing it into a function.

@asottile
Copy link
Member Author

asottile commented Dec 6, 2016

For what it's worth, my patch above worked in one part of the codebase but failed in another so it isn't really a solution. I wonder what the maximum codesize msvc bug reports can be, heh.

We've turned off debug builds so this is much lower priority than it was when I created it (I didn't think that setuptools had enough control over distribution-set compile flags, I was wrong!).

@am11
Copy link
Contributor

am11 commented Dec 6, 2016

I have found the reason why this is happening. The issue is that somewhere in VCBlog or VS2015 release notes (RTM: https://www.visualstudio.com/en-us/news/releasenotes/vs2015-rtm-vs#visualc also see Update 1, 2 and/or update 3 in left-hand nav), there was a detail on breaking changes/improvement in switches they made which causes some non-msbuild build systems pain bit more to figure out, unless we adapt to the new recommendations.

For example in your test.bat @asottile, if you replace /Ox /O2 with just /O1 for both *.c and *.cpp cl-commands, the issue disappears. I am not 100% sure if /O1 is the right switch, but here is how I figured it out:

  • I read about the breaking changes in VC with VS2015 last year and vaguely remembered what those were, so I carried on with my doubt that everything that's not working after upgrading to VS2015 is not necessarily a compiler bug nor result of ABI incompatibility etc. VCRT in VS2015 till date supports Windows XP. :)

  • Downloaded libsass master zip in c:\temp and ran msbuild /v:diag libsass/win/libsass.sln > dump.txt (msbuild in diagnostics mode).

  • Opened dump.txt and searched for cl.exe and found these lines:

    C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\bin\CL.exe /c /I..\include /ZI /nologo /W3 /WX- /Od /Oy- /D WIN32 /D _DEBUG /D _CONSOLE /D _LIB /D ADD_EXPORTS /D "LIBSASS_VERSION=""" /Gm /EHsc /RTC1 /MDd /GS /fp:precise /Zc:wchar_t /Zc:forScope /Zc:inline /Fo"C:\temp\test2\libsass-master\win\bin\Debug\obj\" /Fd"C:\temp\test2\libsass-master\win\bin\Debug\obj\vc140.pdb" /Gd /TC /analyze- /errorReport:queue ..\src\cencode.c (TaskId:25)

    C:\Program Files (x86)\MSBuild\14.0\bin\Tracker.exe /d "C:\Program Files (x86)\MSBuild\14.0\bin\FileTracker32.dll" /i C:\temp\test2\libsass-master\win\bin\Debug\obj\libsass.tlog /r C:\TEMP\TEST2\LIBSASS-MASTER\SRC\CENCODE.C /b MSBuildConsole_CancelEvent18e324c0cc49415a81195045638026e0 /c "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\bin\CL.exe" /c /I..\include /ZI /nologo /W3 /WX- /Od /Oy- /D WIN32 /D _DEBUG /D _CONSOLE /D _LIB /D ADD_EXPORTS /D "LIBSASS_VERSION=""" /Gm /EHsc /RTC1 /MDd /GS /fp:precise /Zc:wchar_t /Zc:forScope /Zc:inline /Fo"C:\temp\test2\libsass-master\win\bin\Debug\obj\" /Fd"C:\temp\test2\libsass-master\win\bin\Debug\obj\vc140.pdb" /Gd /TC /analyze- /errorReport:queue

Note that project system is not emitting /O1, /O2 or /O3 but rather /Od and negating /Oy. Docs might have more information and warning what combination of these compiler switches may yield false-positives/negatives.

I am running VS2015 Update 3.

@am11
Copy link
Contributor

am11 commented Dec 6, 2016

Forgot to mention, I also added:

  <ItemGroup>
    <ClCompile Include="..\main.cpp" />
  </ItemGroup>

in win/libsass.vcxproj and replaced <ConfigurationType>DynamicLibrary</ConfigurationType> with <ConfigurationType>Application</ConfigurationType> after creating main.cpp in project root (to test a runnable console app). In debug config build, there were no errors when I ran the application. :)

@mgreter
Copy link
Contributor

mgreter commented Dec 6, 2016

@am11 👏 I have confirmed with 4 compiles (2 with /Ox /O2, 2 with /O1) and can confirm your observations. Really appreciate your effort. I really started to doubt my understanding of how C++ is supposed to work. So thanks for saving my sanity by finding the culprit. Still strange/interesting to find out why it manifests in the issue we see here.

Edit: Running VS2015 Update 3.

@am11
Copy link
Contributor

am11 commented Dec 6, 2016

Thanks @mgreter! Glad I could contribute. 🎉 Separately, speaking of C++, there is this 🆕 Ranges proposal for STL: https://github.com/ericniebler/range-v3, which might be useful (server as source of inspiration) for this project. One downside concerning LibSass is it doesn't support GCC < v4.8.5. :(
MSFT also forked this project under their org account and made it work in VS2015. Read about it here: https://blogs.msdn.microsoft.com/vcblog/2016/11/16/visual-studio-2017-rc-now-available/.

@mgreter
Copy link
Contributor

mgreter commented Dec 6, 2016

I have probed some more compiler option variations:

fails:     /Zi /c /O2 /W3 /GL /DNDEBUG /EHsc /MT
works:     /Zi /c /O2 /W3 /GL /Ob0 /DNDEBUG /EHsc /MT
works:     /Zi /c /O2 /W3 /DNDEBUG /EHsc /MT
works:     /Zi /c /O2 /W3 /GL /EHsc /MT

From the pattern above it seems that it has to do with function inlining (/Ob0) but can also be fixed by keeping debug assertions (/DNDEBUG), which seems to make sense when we also generate debugging information (/Zi). It also works if global optimization accross compile units is removed (/GL).

The conclusion for me is that you should not optimize debug builds too much. This can be usefull if you i.e. want to do performance profiling for actual release builds. But for regular debug builds I normally disable optimization explicitly, as I want to be able to step through every function call and not have certain calls optimized out.

@mgreter mgreter closed this as completed Dec 6, 2016
@xzyfer
Copy link
Contributor

xzyfer commented Dec 6, 2016

Incredible work y'all

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants