More std::function woes (segfault)

Started by
19 comments, last by Storyyeller 12 years, 3 months ago

The stacktrace goes mycode -> std::function operator () -> random hex value, often sentinals like 0xdeadbeef

I may be backing up a bit here, but in my own experience, if I'm using well-tested pre-existing APIs along with my own code, and things go weird, 98% of the time the problem is in the code I wrote. To that end, does the stack trace tell you where specifically in "mycode" there the problem is occuring? Because I'd scour that whole area before I started pointing fingers at others' libraries. (not that I can make assumptions on the well-written/well-tested nature of Box2d) Can you be certain the state of the machine that your code puts it in when the call is made that generates the segfault should guaranteedly work if Box2d could be "fixed"?
Also, no offense intended.

Hazard Pay :: FPS/RTS in SharpDX (gathering dust, retained for... historical purposes)
DeviantArt :: Because right-brain needs love too (also pretty neglected these days)

Advertisement
Well in the case of the Valgrind error, I've looked at the code, both mine and Box2d, and there are no visible mistakes. Something seriously weird is going on. If anyone can explain how assignment to a (plain int) member of a struct can possibly end up writing past the memory allocated for the struct, I'm all ears.



Incidently, Box2d isn't infallible. While it is far more likely that any potential bugs are in my code, I have found bugs in Box2d before as well.
I trust exceptions about as far as I can throw them.
Like a segfault, the core problem might be higher level than the function currently running when the fault occurs. Like BCullis, I would tend to start in your code when investigating this problem, and only when you have reasonably eliminated your code as the location for the bug should you start seriously investigating a mature library for bugs.

For example, what does KidSuperClass look like? Is it correctly initialising its FixtureDef? Where does the b2Shape pointer it is passing come from? Is that correct? You might want to walk through this in a debugger and see what is going on.

If you're getting non-deterministic crashes during loading, consider using a fixed random seed* rather than using time(NULL) This may make it possible to trigger the behaviour, and thus make debugging easier.

* You might need to try many seeds. One way to save time might be to save and output the value of time(NULL), use this value as the seed, and if the program crashes, try using the saved value as the fixed seed next time around.
* The shape pointer is irrelevant as the supposedly invalid write is occuring in b2FixtureDef's constructor
* The b2FixtureDef is the sole member of FixtureDef, which is in turn allocated by new. When I print out all the adress and sizes, they appear correct.
* I already use a fixed seed. In fact, my program is entirely determinstic, apart from possible issues due to different floating point rounding modes.


[source lang='cpp']#pragma once


#include "BOOST/utility.hpp" // for noncopyable
#include "Box2D/Box2D.h"
#include "physicsConversion.h"

#include "shaperef.h"

class FixtureDef: boost::noncopyable
{
b2FixtureDef mydef;

void InitDefaultProperties();

public:
//FixtureDef does take ownership of the shape. MUST BE DYNAMICALLY ALLOCATED
FixtureDef(ShapeRefType newshape);
~FixtureDef();

b2FixtureDef* GetDef() {return &mydef;}
const b2FixtureDef* GetDef() const {return &mydef;}

FixtureDef* SetDensity(double kgpersqrpxl);
FixtureDef* SetFriction(float32 newval) {GetDef()->friction = newval; return this;}
FixtureDef* SetRestitution(float32 newval) {GetDef()->restitution = newval; return this;}
FixtureDef* SetSensor(bool val=true) {GetDef()->isSensor = val; return this;}
FixtureDef* SetFilterGroup(int16 newval) {GetDef()->filter.groupIndex = newval; return this;}
};

[/source]



[source lang='cpp']#include "physicsFixturedef.h"

void FixtureDef::InitDefaultProperties()
{
mydef.friction = 1.0f;
mydef.restitution = 0.0f;
mydef.isSensor = false;
mydef.filter.groupIndex = -1;
}

FixtureDef::FixtureDef(ShapeRefType newshape)
{
mydef.shape = newshape;//.get();
assert(mydef.shape);
InitDefaultProperties();
}

FixtureDef::~FixtureDef()
{
if (mydef.shape)
{
delete mydef.shape; mydef.shape = NULL;
}
}

FixtureDef* FixtureDef::SetDensity(double kgpersqrpxl)
{
const double newval = kgpersqrpxl * (PIXELSPERMETER * PIXELSPERMETER);
GetDef()->density = (float32) newval;
return this;
}

[/source]



[source lang='cpp']#include "Pickory/kidsuper.h"

#include "entitymanagerinterface.h"
#include "Physics/shapeFactory.h"
#include "CommonEntities/defaultgun.h"
#include "Pickory/platformdata.h"
#include "Pickory/interaction.h"
#include "bloodsplatter.h"
#include "portaltransform.h"

enum KidAnimation{ STAND, WALK, JUMP, FALL, INVALID};
const Uint32 MSG_DEATH = 0;
////////////////////////////////////////////////////////

double KidSuperclass::XSPEED() const {return 150.0;}
Uint32 KidSuperclass::NUMJUMPS() const {return 1;}
double KidSuperclass::AIRINERTIA() const {return .97;}

void KidSuperclass::SetAnimIfDifferent()
{
if (!anim.Changed()) {return;}

Uint32 r = 0;
Uint32 fc = 0;

switch (anim.State())
{
case STAND: r = 0; fc = 4; break;
case WALK: r = 1; fc = 5; break;
case JUMP: r = 2; fc = 2; break;
case FALL: r = 3; fc = 2; break;
default: assert(false);
}

if (anim.Facing() == -1) {r+=4;}

ChangeAnimation(r,fc, anim.Facing());
}

void KidSuperclass::Jump(bool airjump, double newvy)
{
assert(newvy < 0);

SetVelocity(VX(), newvy);
ResetPlatformData();

if (airjump)
{
assert(jumpsleft>0);
--jumpsleft;
PlaySound(airjumpsound);
}
else
{
Move(0,-0.5);
ResetAirJumps();
PlaySound("jump.ogg");
}
}

void KidSuperclass::Bounce(Vec2d newv)
{
SetVelocity(newv);
ResetPlatformData();
Move(0,-0.5);
ResetAirJumps();
momentumtimer = 999999;
}

void KidSuperclass::EnterLadder()
{
ld->active = true;
ResetAirJumps(); //Make sure that it is possible to jump off the ladder later
}

void KidSuperclass::ExitLadder()
{
ld->active = false;
momentumtimer = 0; //Should never be an issue but just in case
anim.SetState(INVALID);
}

void KidSuperclass::ResetAirJumps() {jumpsleft = NUMJUMPS();}
bool KidSuperclass::Airborne() const {return pd->airborne;}
void KidSuperclass::ResetPlatformData() {*pd = PlatformData();}

KidSuperclass::KidSuperclass(entitymanager_ptr newmanager, const PickoryLoadState& data, double guny)
:Entity(newmanager, data.pos.x, data.pos.y, BodyType::DYNAMIC), WIDTH(13), HEIGHT(21), GUNY(guny),
OnDeath(data.deathcb), anim(INVALID,1), pd(new PlatformData()),
doycorrect(false),lasty(data.pos.y), momentumtimer(0), timesinceteleport(0xFFFFFFFF), jumpsleft(data.jumpsleft),
mygun(new Pickory_NormalGun(newmanager, 0)),
airjumpsound("airjump.ogg")
{
assert(OnDeath);
SetVelocity(data.vel);
SetAttribute(AttributeList::KID);
myBody().AddShape((new FixtureDef(NewCenteredOctagon(WIDTH,HEIGHT,0.3)))
->SetDensity(.127)->SetFriction(0.0)->SetFilterGroup(0));
}

KidSuperclass::~KidSuperclass() {} //Dtor required for PIMPL

//#include "BOOST/lexical_cast.hpp"
void KidSuperclass::drawMe(RenderManager& renderer, double x, double y) const
{
mygun->draw(renderer,x,y);
if (iman) {iman->draw(renderer, x, y);}

/*ImgDataStruct h(boost::lexical_cast<std::string>(X()), "Fonts/verdana.ttf",24, MakeColor(0,0,0));
renderer.AddRequest(h, CreateOptSdlRect(), 200, 567, zlOVERLAY2, 255);*/
}

void KidSuperclass::ProcessMessagesMe(DelayedQueue::msgT msg)
{
// assert(msg == MSG_DEATH);
// Manager()->AddEntity(new BloodSplatter(Manager(), X(), Y()));
//
// assert(OnDeath);
// if (OnDeath)
// {
// OnDeath(Manager());
// OnDeath.clear(); //To make sure we don't accidently call the callback twice somehow
// }
//
// Deactivate();
}

void KidSuperclass::UpdateMe()
{
if (!pd->hitplatform && doycorrect)
{
double newy = lasty + .016 * VY();
SetPos(X(), newy);
}

//Input stuff
if (GetInput().suicide) {Die(); return;}
const int inputx = GetInput().xmove;
const int inputy = GetInput().ymove;
const bool dojump = GetInput().firstjump;
const bool isjumping = GetInput().jumping;
bool doshoot = GetInput().firstshoot; //Nonconst because it is changed if we do a callback so gun doesnt get it too
const bool docb = doshoot || (GetInput().ymove < 0);

if (ld)
{
if (!ld->active && ld->TestPoint(Pos())) // If touching ladder but not on it, add callback handler to get on it
{
RegisterCB_pressZ(&KidSuperclass::EnterLadder, Pos() + Vec2d(0,-33.3));
}
else if (ld->active) //If we're on ladder, do ladder update stuff and don't do normal update stuff
{
SetVelocity(inputx * 50.0, inputy * 62.5);

Uint32 frame = (Uint32)floor((Y() + X()) / 4.0) % 8;
SetAnimation(new PlainImage(zlPICKORY, ImgDataStruct("PickoryMansion/climbanim.png",0,255,0),
CreateOptSdlRect(frame*15,0,15,29)), -7.5, -14.5);

//If we're near top of ladder, don't try to go off the top
if (inputy < 0 && (Y() < ld->bounds.y1 + 1.0)) {SetVelocity(VX(), 0.0);}

//Check if we left the ladder
if (dojump || !ld->TestPoint(Pos()))
{
ExitLadder();
}

if (dojump)
{
Jump(true, JUMPV2());
}

if (iman) {iman->Update();}
return;
}
}

if (docb && iman && iman->Ready())
{
assert(iman->cb);
iman->cb(this);
doshoot = false;
}

//Update orientation - if inputx = 0, don't change orientation
if (inputx < 0) { anim.SetFacing(-1); }
if (inputx > 0) { anim.SetFacing(+1); }

if (mygun)
{
mygun->Update(doshoot, GetInput().special, X(), Y()+GUNY, anim.Facing());
}

//Jump if possible
if (dojump)
{
if (Airborne() && jumpsleft>0)
{
Jump(true, JUMPV2());
}
else if (!Airborne() && pd->pvel.y > JUMPV())
//can't jump if the platform is going faster then normal jump speed anyway
{
Jump(false, JUMPV());
}
}

if (pd->hitplatform)
{
momentumtimer = 0;
}

//Movement
if (!pd->hitplatform || pd->platformdynamic)
{
double newvx = XSPEED() * inputx;

if (momentumtimer > 0)
{
newvx = (1-AIRINERTIA()) * newvx + AIRINERTIA() * VX();
--momentumtimer;
}

SetVelocity(newvx, VY());

if (pd->platformdynamic && fabs(VY()) < 25.0)
{
if (inputx == 0) {anim.SetState(STAND);}
else {anim.SetState(WALK);}
}
else if (VY()<0) {anim.SetState(JUMP);}
else {anim.SetState(FALL);}
}
else
{
SetVelocity( pd->pvel + Vec2d(inputx, 0) * XSPEED() );
if (inputx == 0) {anim.SetState(STAND);}
else {anim.SetState(WALK);}
}

DoGravity(isjumping); //virtual function overridden by Pickory and The Kid
SetAnimIfDifferent();

// Airborne is not necessarily the opposite of hitplatform. There is a delay of one tick before it becomes true
// So possible states are TF, FT, and FF, where FF occurs one tick after FT
const bool oldval = !pd->hitplatform;
ResetPlatformData();
pd->airborne = oldval;

doycorrect = true;
lasty = Y();
++timesinceteleport;

if (iman) {iman->Update();}
}

void KidSuperclass::RegisterPlatformCollision(Entity* other, double normy)
{
if (normy != 0) {doycorrect=false;} //Disable y correction for this frame, because there may be a valid collision

if (!pd->hitplatform && other->Active() && normy > .01)
{
if ((VY() - other->VY() > -.1) || other->IsDynamic())
{
pd->airborne = false;
pd->hitplatform = true;
ResetAirJumps();
pd->platformdynamic = other->IsDynamic();
pd->pvel = other->Vel();
}
}
}

void KidSuperclass::AddGun(PickoryGunInterface* newgun)
{
assert(newgun);
mygun.reset(newgun);
}

PickoryLoadState KidSuperclass::GetLoadState() const
{
PickoryLoadState ldata;
ldata.pos = Pos();
ldata.vel = Vel();
ldata.facing = anim.Facing();
ldata.jumpsleft = jumpsleft;
ldata.deathcb = OnDeath;
return ldata;
}

void KidSuperclass::Die()
{
// AddMessage(MSG_DEATH,0);
if(!Active()) {return;}
Manager()->AddEntity(new BloodSplatter(Manager(), X(), Y()));

assert(OnDeath);
if (OnDeath)
{
OnDeath(Manager());
OnDeath = 0; //To make sure we don't accidently call the callback twice somehow
}

Deactivate();
}

void KidSuperclass::DoTransform(transform_ptr_const M)
{
Entity::DefaultPortalTransform(M, 1562.5);

//If the portal is pointing mostly upwards, give them a little boost to make sure they don't immediately fallback in
//Limit the boost so they can't jump infinitely higher

if (M->dest_normal.y < -.8)
{
double boostfactor = (M->dest_normal.y * M->dest_normal.y * M->dest_normal.y * M->dest_normal.y);

if (Vel() * M->dest_normal < 444)
{
double boostamount = (444 - Vel() * M->dest_normal) * boostfactor;
SetVelocity(Vel() + boostamount * M->dest_normal);
}
}

if (timesinceteleport > 42) //Try to limit frequency sound is played at to avoid overlap.
{
PlaySound("Portal/portalteleport.ogg");
timesinceteleport = 0;
}

//Make momentum last longer if they are comming out of a horizontally facing portal, where they have horizontal momentum
momentumtimer = (Uint32) (42.0 * (0.6 + fabs(M->dest_normal.x)));
}

void KidSuperclass::RegisterCB_pressZ(std::function<void (KidSuperclass*)> newcb, const Vec2d& drawpos)
{
if (!iman) {iman.reset(new InteractionCallbackManager());}
iman->RegisterCB(newcb, drawpos);
}

void KidSuperclass::RegisterLadder(double x1, double y1, double x2, double y2)
{
assert(!ld);
ld.reset(new LadderData(x1,y1,x2,y2));
}

[/source]
I trust exceptions about as far as I can throw them.
What is a ShapeRefType? In particular what happens when it is copied? What does NewCenteredOctagon() look like?
It's just a typedef for a raw pointer, so not much

[source lang='cpp']#pragma once

struct b2Shape;
typedef b2Shape* ShapeRefType;

[/source]

And here are my shape creation functions

[source lang='cpp']#include "shapeFactory.h"

#include "Box2D/Box2D.h"
#include "physicsConversion.h"
#include "customshape.h" //for octagon

ShapeRefType NewCenteredRect(double width, double height)
{
assert(width>0); assert(height>0);
b2PolygonShape* poly = new b2PolygonShape;
poly->SetAsBox( ToMeters(width/2.0), ToMeters(height/2.0) );
return ShapeRefType(poly);
}

ShapeRefType NewCenteredCircle(double radius)
{
assert(radius>0);
b2CircleShape* shape = new b2CircleShape;
shape->m_radius = ToMeters(radius);
return ShapeRefType(shape);
}

ShapeRefType NewEdge(Vec2d v1, Vec2d v2)
{
b2EdgeShape* edge = new b2EdgeShape;
edge->Set( ToMetersV(v1), ToMetersV(v2) );
return ShapeRefType(edge);
}

ShapeRefType NewCenteredOctagon(double width, double height, double cc)
{
double hw = width/2.0;
double hh = height/2.0;
assert(hw>0);
assert(hh>0);
assert(cc < hw);
assert(cc < hh);

SimplePointList newshape;
newshape.AddPoint(-hw, -hh+cc).AddPoint(-hw+cc, -hh).AddPoint(hw-cc, -hh).AddPoint(hw, -hh+cc); //Top half
newshape.AddPoint(hw, hh-cc).AddPoint(hw-cc, hh).AddPoint(-hw+cc, hh).AddPoint(-hw, hh-cc); //Bottom half
return newshape.CreateNewShape();
}

[/source]

[source lang='cpp']#pragma once

#include <vector>
#include "vec2d.h"
#include "shaperef.h"
#include "Box2D/Box2D.h"

class SimplePointList
{
std::vector<b2Vec2> mypoints;

public:
SimplePointList& AddPoint(Vec2d P);
SimplePointList& AddPoint(double px, double py);

SimplePointList& Rotate(double angle);
SimplePointList& Transform(double a, double b, double c, double d); // (x',y') = (ax+by, cx+dy)

// x -> v1, y -> v2
SimplePointList& Transform(Vec2d v1, Vec2d v2) {return Transform(v1.x, v2.x, v1.y, v2.y);}

ShapeRefType CreateNewShape() const;
};

[/source]

[source lang='cpp']#include "customshape.h"

#include "BOOST/foreach.hpp"
#include "physicsConversion.h"

SimplePointList& SimplePointList::AddPoint(Vec2d P)
{
mypoints.push_back( ToMetersV(P) );
return *this;
}

SimplePointList& SimplePointList::AddPoint(double px, double py)
{
return AddPoint(Vec2d(px,py));
}

SimplePointList& SimplePointList::Rotate(double angle)
{
return Transform(cos(angle), -sin(angle), sin(angle), cos(angle));
/*
BOOST_FOREACH(b2Vec2& v, mypoints)
{
v = b2Vec2(v.x * cos(angle) - v.y * sin(angle), v.x * sin(angle) + v.y * cos(angle));
}
return *this;*/
}

SimplePointList& SimplePointList::Transform(double a, double b, double c, double d)
{
BOOST_FOREACH(b2Vec2& v, mypoints)
{
v = b2Vec2(a*v.x + b*v.y, c*v.x + d*v.y);
}
return *this;
}

ShapeRefType SimplePointList::CreateNewShape() const
{
assert(mypoints.size() >= 2);
assert(mypoints.size() <= 8); //A setting in Box2d. Can be changed if absolutely necessary
b2PolygonShape* poly = new b2PolygonShape;

if(0>b2Cross((mypoints.at(1)-mypoints.at(0)),(mypoints.at(2)-mypoints.at(1))))
{
Debug("Reversing points");
//The points are going in the wrong direction, so reverse them
std::vector<b2Vec2> reversedpoints;

for(size_t j = mypoints.size(); j>0; --j)
{
reversedpoints.push_back( mypoints.at(j-1) );
}

assert( mypoints.size() == reversedpoints.size());

poly->Set( &(reversedpoints[0]), mypoints.size() );
}
else
{
//Since std vectors are garuenteed to be stored in blcoks of contiguous memory, we can
//take the address of the first element and treat it like a plain array, such as the
//Box2D API expects
poly->Set( &(mypoints[0]), mypoints.size() );
}

return ShapeRefType(poly);
}

[/source]
I trust exceptions about as far as I can throw them.
To make things even weirder, when I added debugging code and recompoiled, the Valgrind problems went away. I guess this is the fabled heisenbug.
I trust exceptions about as far as I can throw them.
Bizarre. It appears the "this" pointer of the b2FixtureDef seems to have been subtly corrupted, or more specifically, moved a couple of bytes down (2, it would appear?). The writes that should be going to the end of the structure are going off the end. Nothing obviously jumps at me in the code though.

If you were to follow (e.g. via printing if you still cannot trigger the bug) the addresses of the shape pointer from its creation on line 41 of SimplePointList::CreateNewShape all the way to the point where these invalid accesses are taking place. As you said though, adding in such code might cause the bug to apparently disappear.

Is the sequence in the valgrind output typical, or does it occur in different places?
Well the first time, Valgrind reported the error in the b2fixture constructor. After some debugging, it moved to FixtureDef::SetFiltergroup. And after some more debugging, the Valgrind errors disappeared entirely.
I trust exceptions about as far as I can throw them.
The last time I saw symptoms like that it was because my IDE wasn't rebuilding all the files a change affected, so I would try a full rebuild if you haven't already. Another time I had symptoms like that it was because some of my source files were getting built with different options than others and the subtle differences were causing functions calls to go haywire.

This topic is closed to new replies.

Advertisement