Strange behaviour when using C++17 static inline members in Visual Studio - c++

Yesterday I asked a question about this problem, but I wasn't able to give a MVCE. I've managed to reproduce this with a simple program. The problem is with using an std::list as a static inline declaration in a class. Microsoft Visual Studio does support this new C++17 feature. It had some bugs as of March, but as far as I know they've been fixed since. Here are instructions of how I can get this problem, this happens in debug mode.
In main.cpp
#include <iostream>
#include "header1.h"
int main()
{
return 0;
}
In header1.h:
#include <list>
struct Boo
{
static inline std::list<int> mylist;
};
In anotherCPP.cpp
#include "Header1.h"
When the program exits main() it destroys all the static objects and throws an exception.
If this doesn't crash, maybe on your system the compiler/linker optimised some code out, so you can try making main.cpp and anotherCPP.cpp do something. In anotherCPP.cpp:
#include <iostream>
#include "Header1.h"
void aFunction()
{
std::cout << Boo::mylist.size();
}
And make main.cpp:
#include <iostream>
#include "Header1.h"
void aFunction();
int main()
{
std::cout << Boo::mylist.size();
afunction();
return 0;
}
When the program exits I get an exception here when the std::list is being cleared. Here is the Visual Studio debug code where it crashes:
for (_Nodeptr _Pnext; _Pnode != this->_Myhead(); _Pnode = _Pnext)
{ // delete an element
_Pnext = _Pnode->_Next; // Here: Exception thrown:
// read access violation.
// _Pnode was 0xFFFFFFFFFFFFFFFF.
this->_Freenode(_Pnode);
}
This happens only if I declare the static inline std::list< int > mylist in the class. If I declare it as static std::list< int > mylist in my class and then define it separately in one .cpp as std::list< int > Boo::mylist; it works fine. This problem arises when I declare the std::list static inline and I include the header for the class in two .cpp files.
In my project I have stepped through the std::list clear loop from above, I took note of the "this" pointer address. I stepped through the loop as it freed nodes in my list. It then came back to free other std::lists, including in std::unordered_map (as they also use std::lists from the looks of it). Finally when the read access exception is thrown and _Pnode is an invalid pointer address, I noticed the "this" pointer address is the same as the "this" pointer address when clearing std::list< int > mylist, which makes me think that it's trying to delete it twice, and probably why it's crashing.
I hope someone can reproduce this, I'm not sure what this is, if it's a bug or something I'm doing wrong. Also this happens for me in 32 and 64 bit, but only in debug mode, because the node freeing loop I provided is under a macro:
#if _ITERATOR_DEBUG_LEVEL == 2

This issue was filed as a bug here under the title "Multiple initializations of inline static data member in Debug mode".
This was found in Visual Studio 2017 version 15.7.
The VS compiler team has accepted this and have fixed the problem in an upcoming release.

Related

How can I allow singleton constructor re-entry/pass-over in VS2017?

I've been porting some c++ app from Visual Studio 2013 to Visual Studio 2017. Aside from the plethora of new warnings that I had to fix, the compilation and linking went okay.
However, when running the app, it 'stalled' when trying to re-enter the constructor of a singleton (when successive function calls form a loop back to the constructor). It seems that this behaviour was okay in VS2013, but is no longer valid in VS2017. There is no error message.
I'm aware of all the bad things related to singletons, and that there should at least not be loops. The question is not there.
Is there a way to tell the VS2017 compiler that I'd like to shoot myself in the foot, and allow the same behaviour that was there in VS2013?
I don't have access to the code that causes this behaviour because it comes from a third-party library, this is why I can't 'just fix it', unfortunately.
Here is an example which works in VS2013, but doesn't work in VS2017:
main.cpp
#include "Singleton.h";
int
main( void )
{
std::cout << "let's do this!" << std::endl;
int two = Singleton::GetReference().getTwo();
std::cout << "ok" << std::endl;
return 0;
}
Singleton.h
#pragma once
class Stuff;
class Singleton
{
public:
static Singleton& GetReference();
int getTwo() { return 2; }
private:
Singleton();
Stuff* stuff;
};
Singleton.cpp
#include "Singleton.h"
#include "Stuff.h"
Singleton&
Singleton::GetReference() {
static Singleton theInstance;
return theInstance;
}
Singleton::Singleton()
{
stuff = new Stuff();
}
Stuff.h
#pragma once
class Stuff
{
public:
Stuff();
private:
int two;
};
Stuff.cpp
#include "Stuff.h"
#include "Singleton.h"
Stuff::Stuff()
{
two = Singleton::GetReference().getTwo();
}
In the code above, when step-by-step debugging, the first time we get on the line static Singleton theInstance; will work as expected, but the second time, a F11 will go to the file thread_safe_statics.cpp, into the method extern "C" void __cdecl _Init_thread_header(int* const pOnce). A Shift+F11 will exit the method and the program will wait indefinitely at the line specified (observed when pausing the program from the debugger).
PS
This issue probably occurs in Visual Studio 2015 too, as the documentation linked from the accepted answer mentions VS2015.
/Zc:threadSafeInit-
The general "Conformance" page is MSDN: Conformance, which details which new features you can disable.
I needed the code for sizedDealloc, where my new compiler was creating a sized new operator for a library which broke older compiled expectations.
As this is a compile flag, at least some of the code would be in your control, and you should be able to unravel the beast.
The constructor Stuff::Stuff is calling a function on an incompletely constructed object.
That would create "Undefined behavior". If the value "2" is not set till the end of the constructor (for example).
Probably the Singleton needs to be split into 2, one which delivers the early static data (e.g. 2).
The second which delivers the held object Stuff. Stuff would only rely on the first, which would break the deadlock.
Alternatively, a second constructor to Stuff which told it which object to use, and was called from the Singleton::Singleton
The MSDN article to disable "Magic Statics" MSDN : disable threadsafe static initialization

std::any across shared library bounding in mingw

I stumbled about an issue while using libstdc++'s std::any implementation with mingw across a shared library boundary. It produces a std::bad_any_cast where it obviously should not (i believe).
I use mingw-w64, gcc-7 and compile the code with -std=c++1z.
The simplified code:
main.cpp:
#include <any>
#include <string>
// prototype from lib.cpp
void do_stuff_with_any(const std::any& obj);
int main()
{
do_stuff_with_any(std::string{"Hello World"});
}
lib.cpp:
Will be compiled into a shared library and linked with the executable from main.cpp.
#include <any>
#include <iostream>
void do_stuff_with_any(const std::any& obj)
{
std::cout << std::any_cast<const std::string&>(obj) << "\n";
}
This triggers a std::bad_any_cast although the any passed to do_stuff_with_any does contain a string. I digged into gcc's any implementation and it seems to use comparison of the address of a static inline member function (a manager chosen from a template struct depending on the type of the stored object) to check if the any holds an object of the requested type.
And the address of this function seems to change across the shared library boundary.
Isn't std::any guaranteed to work across shared library boundaries? Does this code trigger UB somewhere? Or is this a bug in the gcc implementation? I am pretty sure it works on linux so is this only a bug in mingw? Is it known or should i report it somewhere if so? Any ideas for (temporary) workarounds?
While it is true that this is an issue on how Windows DLLs work, and that as of GCC 8.2.0, the issue still remains, this can be easily worked around by changing the __any_caster function inside the any header to this:
template<typename _Tp>
void* __any_caster(const any* __any)
{
if constexpr (is_copy_constructible_v<decay_t<_Tp>>)
{
#if __cpp_rtti
if (__any->type().hash_code() == typeid(_Tp).hash_code())
#else
if (__any->_M_manager == &any::_Manager<decay_t<_Tp>>::_S_manage)
#endif
{
any::_Arg __arg;
__any->_M_manager(any::_Op_access, __any, &__arg);
return __arg._M_obj;
}
}
return nullptr;
}
Or something similar, the only relevant part is the comparison line wrapped in the #if.
To elaborate, there is 2 copies of the manager function one on the exe and one on the dll, the passed object contains the address of the exe because that's where it was created, but once it reaches the dll side, the pointer gets compared to the one in the dll address space, which will never match, so, instead type info hash_codes should be compared instead.

Plugins using Pluma

Overview
I am trying to develop a C++ application which allows for user-created plugins.
I found a nice library called Pluma (http://pluma-framework.sourceforge.net/) which functionally seems to be exactly what I want.
After going through their tutorial, I was able to (with a bit of difficulty) convince the plugin to compile. However, it refuses to play nice and connect with the main program; returning various errors depending on how I try to implement them.
Problem
If I comment out the line labeled 'Main problem line' (in the last file, main.cpp), the plugin compiles successfully, and the main app can recognize it, but it says that "Nothing registered by plugin 'libRNCypher'", and none of the functions can be called.
If I compile that line, the main application instead says "Failed to load library 'Plugins/libRNCypher.so'. OS returned error: 'Plugins/libRNCypher.so: undefined symbol: _ZTIN5pluma8ProviderE".
My guess is that it has something to do with the way the plugin was compiled, as compiling it initially did not work and Code::Blocks told me to compile with "-fPIC" as a flag (doing so made it compile).
Code
Code below:
Main.cpp
#include "Pluma/Pluma.hpp"
#include "CryptoBase.h"
int main()
{
pluma::Pluma manager;
manager.acceptProviderType< CryptoBaseProvider >();
manager.loadFromFolder("Plugins", true);
std::vector<CryptoBaseProvider*> providers;
manager.getProviders(providers);
return 0;
}
CryptoBase.h
#ifndef CRYPTOBASE_H_INCLUDED
#define CRYPTOBASE_H_INCLUDED
#include "Pluma/Pluma.hpp"
#include <string>
#include <vector>
#include <bitset>
//Base class from which all crypto plug-ins will derive
class CryptoBase
{
public:
CryptoBase();
~CryptoBase();
virtual std::string GetCypherName() const = 0;
virtual std::vector<std::string> GetCryptoRecApps() const = 0;
virtual void HandleData(std::vector< std::bitset<8> > _data) const = 0;
};
PLUMA_PROVIDER_HEADER(CryptoBase)
#endif // CRYPTOBASE_H_INCLUDED
RNCypher.h (This is part of the plugin)
#ifndef RNCYPHER_H_INCLUDED
#define RNCYPHER_H_INCLUDED
#include <string>
#include <vector>
#include <bitset>
#include "../Encoder/Pluma/Pluma.hpp"
#include "../Encoder/CryptoBase.h"
class RNCypher : public CryptoBase
{
public:
std::string GetCypherName() const
{
return "RNCypher";
}
std::vector<std::string> GetCryptoRecApps() const
{
std::vector<std::string> vec;
vec.push_back("Storage");
return vec;
}
void HandleData(std::vector< std::bitset<8> > _data) const
{
char letter = 'v';
_data.clear();
_data.push_back(std::bitset<8>(letter));
return;
}
};
PLUMA_INHERIT_PROVIDER(RNCypher, CryptoBase);
#endif // RNCYPHER_H_INCLUDED
main.cpp (This is part of the plugin)
#include "../Encoder/Pluma/Connector.hpp"
#include "RNCypher.h"
PLUMA_CONNECTOR
bool connect(pluma::Host& host)
{
host.add( new RNCypherProvider() ); //<- Main problem line
return true;
}
Additional Details
I'm compiling on Ubuntu 16.04, using Code::Blocks 16.01.
The second error message seems to not come from Pluma itself, but a file I also had to link, #include <dlfcn.h> (which might be a Linux file?).
I would prefer to use an existing library rather than write my own code as I would like this to be cross-platform. I am, however, open to any suggestions.
Sorry for all of the code, but I believe this is enough to reproduce the error that I am having.
Thank You
Thank you for taking the time to read this, and thank you in advance for your help!
All the best, and happy holidays!
I was not able to reproduce your problem, however looking at
http://pluma-framework.sourceforge.net/documentation/index.htm,
I've noticed that:
in your RNCypher.h file you miss something like
PLUMA_INHERIT_PROVIDER(RNCypher, CryptoBase)
it seems also that there's no file CryptoBase.cpp containing something like
#include "CryptoBase.h"
PLUMA_PROVIDER_SOURCE(CryptoBase, 1, 1);
finally, in CryptoBase.h I would declare a virtual destructor (see Why should I declare a virtual destructor for an abstract class in C++?) and provide a definition to it, while you should not declare a default constructor without providing a definition to it (see for instance Is it correct to use declaration only for empty private constructors in C++?); of course the last consideration is valid unless there's another file in which you have provided such definitions.

The weirdest bug (or something) in std::vector (or somewhere else). What's going on?

I've tried compiling this with Visual Studio 2012 RC and Intel C++ Compiler XE 12.1. I'd appreciate if you tried with some other compiler. See my comments in the code to really appreciate the weirdness of this bug. Does anyone know what's going on, and where should I file a bug report about this?
// File: NamedSameA.h
#pragma once
// File: NamedSameA.cpp
#include <vector>
#include "NamedSameA.h"
struct NamedSame // Rename this class to something else to make the program work
{
std::vector<int> data;
// Comment out the previous line or change
// the data type to int to make the program work
};
static NamedSame g_data; // Comment out this line to make the program work
// File: NamedSameB.h
#pragma once
void test();
// File: NamedSameB.cpp
#include <vector>
#include "NamedSameA.h"
#include "NamedSameB.h"
struct NamedSame
{
int data1; // Comment out this line to make the program work
std::vector<int> data2;
};
void test()
{
NamedSame namedSame;
namedSame.data2.assign(100, 42);
// The previous line produces the following runtime error:
// -------------------------------------------------------
// Debug Assertion Failed!
// Program: C:\Windows\system32\MSVCP110D.dll
// File: c:\program files (x86)\microsoft visual studio 11.0\vc\include\vector
// Line: 240
// Expression: vector iterators incompatible
}
By giving the same name to two different classes/structures you've violated the One Definition Rule. This results in undefined behavior, so any result is possible - including a crash.
I've found over the years that the more convinced I am that I've found a compiler bug, the more likely it is that my program has a fundamental flaw.

std::vector fails referencing the object to push in after code splitting

EDIT: aList was being referenced as a pointer in this version of my code, but not in my current version which still has the same problem.
I had this code perfectly working before trying to split it in an interface file and an implementation file. But when I splitted it, the compiler tells me I'm calling push_back() with incorrect parameters. So I understand it cannot reference the type of the object I'm pushing it, although it's the same (afaik, of course :D).
#ifndef _MYHEADER_HPP_
#define _MYHEADER_HPP_
class A{
public:
std::string someString;
};
class B{
public:
std::vector<A> aList;
public:
void addA();
};
#endif /* _MYHEADER_HPP_ */
//implementation file
#include <string>
#include <vector>
#include "myheader.hpp"
void B::addA(){
A a;
a.someString = "Hola";
// Here compiler says : Invalid arguments 'Candidates are: void push_back(const A &)' line 18 Semantic Error
aList.push_back(a);
}
AFAIK, std::vector always do a copy of the object to push, and that copy gets stored into the vector, so I think it's not a problem of 'a' being stack allocated, am I right?
What I'm doing wrong?
Thanks.
Ok, after more than 3 hours looking for the root of the problem, I found that Eclipse CDT IDE had a corrupted cache file about my code. So, compiling from command line was everything fine, I had to delete that cache file and then Eclipse reported no errors.