public inbox for gcc-help@gcc.gnu.org
 help / color / mirror / Atom feed
* Runtime error about undefined behavior
@ 2023-12-23 23:49 Xi Xue
  2023-12-25 14:48 ` Thomas Bleher
  0 siblings, 1 reply; 2+ messages in thread
From: Xi Xue @ 2023-12-23 23:49 UTC (permalink / raw)
  To: gcc-help


[-- Attachment #1.1: Type: text/plain, Size: 1007 bytes --]

tw.cpp:53:19: runtime error: member access within address 0x7fe7f52ff800 which does not point to an object of type 'CachedObj'
0x7fe7f52ff800: note: object has invalid vptr
 00 00 00 00  be be be be be be be be  be be be be be be be be  be be be be be be be be  be be be be
              ^~~~~~~~~~~~~~~~~~~~~~~
              invalid vptr
tw.cpp:41:39: runtime error: member access within address 0x7fe7f53ff7c0 which does not point to an object of type 'CachedObj'
0x7fe7f53ff7c0: note: object has invalid vptr
 00 00 00 00  00 00 00 00 00 00 00 00  a0 f7 3f f5 e7 7f 00 00  00 00 00 00 00 00 00 00  00 00 00 00
              ^~~~~~~~~~~~~~~~~~~~~~~
              invalid vptr
I am referring to a derived class. Access to the "next" member of the base class.Is this a runtime error?
---------------------------------------------
Compile command:
g++ -std=c++98 -O0 --ansi -fno-elide-constructors -Wall -fsanitize=address -fsanitize=undefined -g $name ; ASAN_OPTIONS=detect_leaks=1
./a.out


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: tw.cpp --]
[-- Type: text/x-c++src; name="tw.cpp", Size: 4876 bytes --]

#include <iostream>
#include <cstddef>
#include <bits/allocator.h>
#include <stdexcept>
using std::cout;
using std::endl;

template <class T> class CachedObj {
    public:
	void *operator new(std::size_t);
	void operator delete(void *, std::size_t);
	virtual ~CachedObj()
	{
	}

    protected:
	T *next;

    private:
	static void add_to_freelist(T *);
	static std::allocator<T> alloc_mem;
	static T *freeStore;
	static const std::size_t chunk;
};

template <class T> std::allocator<T> CachedObj<T>::alloc_mem;
template <typename F> F *CachedObj<F>::freeStore = 0;
template <class T> const std::size_t CachedObj<T>::chunk = 32767;
template <class T> void *CachedObj<T>::operator new(size_t sz)
{
	if (sz != sizeof(T))
		throw std::runtime_error(
			"CachedObj:wrong size object in operator new");
	if (!freeStore) {
		T *array = alloc_mem.allocate(chunk);
		for (size_t i = 0; i != chunk; ++i)
			add_to_freelist(&array[i]);
	}
	T *p = freeStore;
	freeStore = freeStore->CachedObj<T>::next;
	return p;
}

template <class T> void CachedObj<T>::operator delete(void *p, size_t)
{
	if (p != 0)
		add_to_freelist(static_cast<T *>(p));
}
template <class T> void CachedObj<T>::add_to_freelist(T *p)
{
	p->CachedObj<T>::next = freeStore;
	freeStore = p;
}

template <class Type> class Queue;
template <class T> std::ostream &operator<<(std::ostream &, const Queue<T> &);

template <class Type> class QueueItem : public CachedObj<QueueItem<Type> > {
	friend class Queue<Type>;
	friend std::ostream &operator<< <Type>(std::ostream &,
					       const Queue<Type> &);
	QueueItem(const Type &t)
		: item(t)
		, next(0)
	{
	}
	Type item;
	QueueItem *next;
};

template <class Type> class Queue {
	friend std::ostream &operator<< <Type>(std::ostream &,
					       const Queue<Type> &);

    public:
	Queue()
		: head(0)
		, tail(0)
	{
	}

    public:
	template <class It>
	Queue(It beg, It end)
		: head(0)
		, tail(0)
	{
		copy_elems(beg, end);
	}

	Queue(const Queue &Q)
		: head(0)
		, tail(0)
	{
		copy_elems(Q);
	}
	Queue &operator=(const Queue &);
	~Queue()
	{
		destroy();
	}

	template <class Iter> void assign(Iter, Iter);

	Type &front()
	{
		return head->item;
	}
	const Type &front() const
	{
		return head->item;
	}

	void push(const Type &);
	void pop();

	bool empty() const
	{
		return head == 0;
	}

    private:
	QueueItem<Type> *head;
	QueueItem<Type> *tail;
	void destroy();
	void copy_elems(const Queue &);

    private:
	template <class Iter> void copy_elems(Iter, Iter);
};

template <class Type> void Queue<Type>::copy_elems(const Queue &orig)
{
	for (QueueItem<Type> *pt = orig.head; pt; pt = pt->next)
		push(pt->item);
}

template <class Type> Queue<Type> &Queue<Type>::operator=(const Queue &rhs)
{
	if (this != &rhs) {
		destroy();
		copy_elems(rhs);
	}
	return *this;
}

template <class Type> void Queue<Type>::destroy()
{
	while (!empty())
		pop();
}

template <class Type> void Queue<Type>::push(const Type &val)
{
	QueueItem<Type> *pt = new QueueItem<Type>(val);

	if (empty())
		head = tail = pt;
	else {
		tail->next = pt;
		tail = pt;
	}
}

template <class Type> void Queue<Type>::pop()
{
	QueueItem<Type> *p = head;
	head = head->next;
	delete p;
}

template <class Type>
std::ostream &operator<<(std::ostream &os, const Queue<Type> &q)
{
	os << "< ";
	QueueItem<Type> *p;
	for (p = q.head; p; p = p->next)
		os << p->item << " ";
	os << ">";
	return os;
}

template <class T>
template <class Iter>
void Queue<T>::assign(Iter beg, Iter end)
{
	destroy();

	copy_elems(beg, end);
}

template <class Type>
template <class It>
void Queue<Type>::copy_elems(It beg, It end)
{
	while (beg != end) {
		push(*beg);
		++beg;
	}
}

int main()
{
	Queue<int> queue1;

	for (int ix = 0; ix != 32767; ++ix)
		queue1.push(ix);

	for (int ix = 0; ix != 32767; ++ix) {
		int i = queue1.front();
		if (i != ix)
			cout << "Something's wrong! i = " << i << " ix = " << ix
			     << endl;
		queue1.pop(); // and remove it
	}

	if (!queue1.empty())
		cout << "Queue is not empty but should be!" << endl;
	else
		cout << "OK, queue empty again" << endl;

	for (int ix = 0; ix != 32767; ++ix)
		queue1.push(ix);

	Queue<int> queue2(queue1); // use copy constructor

	for (int ix = 0; ix != 32767; ++ix) {
		int i = queue2.front(); // check next item
		if (i != ix)
			cout << "Something's wrong! i = " << i << " ix = " << ix
			     << endl;
		queue2.pop(); // and remove it
	}

	if (!queue2.empty())
		cout << "queue2 is not empty but should be!" << endl;
	else
		cout << "OK, queue2 empty again" << endl;

	queue2 = queue1; // use asssignment operator
	for (int ix = 0; ix != 32767; ++ix) {
		int i = queue2.front();
		if (i != ix)
			cout << "Something's wrong! i = " << i << " ix = " << ix
			     << endl;
		queue2.pop();
	}

	if (!queue2.empty())
		cout << "queue2 is not empty but should be!" << endl;
	else
		cout << "OK, queue2 empty again" << endl;
	return 0;
}

^ permalink raw reply	[flat|nested] 2+ messages in thread

* Re: Runtime error about undefined behavior
  2023-12-23 23:49 Runtime error about undefined behavior Xi Xue
@ 2023-12-25 14:48 ` Thomas Bleher
  0 siblings, 0 replies; 2+ messages in thread
From: Thomas Bleher @ 2023-12-25 14:48 UTC (permalink / raw)
  To: Xi Xue; +Cc: gcc-help

* Xi Xue via Gcc-help <gcc-help@gcc.gnu.org> [2023-12-24 00:50]:
> tw.cpp:53:19: runtime error: member access within address 0x7fe7f52ff800 which does not point to an object of type 'CachedObj'
> 0x7fe7f52ff800: note: object has invalid vptr
>  00 00 00 00  be be be be be be be be  be be be be be be be be  be be be be be be be be  be be be be
>               ^~~~~~~~~~~~~~~~~~~~~~~
>               invalid vptr
> tw.cpp:41:39: runtime error: member access within address 0x7fe7f53ff7c0 which does not point to an object of type 'CachedObj'
> 0x7fe7f53ff7c0: note: object has invalid vptr
>  00 00 00 00  00 00 00 00 00 00 00 00  a0 f7 3f f5 e7 7f 00 00  00 00 00 00 00 00 00 00  00 00 00 00
>               ^~~~~~~~~~~~~~~~~~~~~~~
>               invalid vptr
> I am referring to a derived class. Access to the "next" member of the base class.Is this a runtime error?

I think you are missing a constructor call:

> template <class T> void *CachedObj<T>::operator new(size_t sz)
> {
> 	if (sz != sizeof(T))
> 		throw std::runtime_error(
> 			"CachedObj:wrong size object in operator new");
> 	if (!freeStore) {
> 		T *array = alloc_mem.allocate(chunk);
> 		for (size_t i = 0; i != chunk; ++i)
> 			add_to_freelist(&array[i]);

Here add_to_freelist is called with a pointer to uninitialized storage
(see https://en.cppreference.com/w/cpp/memory/allocator/allocate:
"Allocates n * sizeof(T) bytes of uninitialized storage")

The function stores the pointer to the uninitalized memory in freeStore.
> 	}
> 	T *p = freeStore;
> 	freeStore = freeStore->CachedObj<T>::next;

This treats freeStore as an initialized object and accesses its member
(but there doesn't seem to be any constructor call to actually
initialize the object).

So I think UBSan is correct here.

> 	return p;
> }
>
> template <class T> void CachedObj<T>::operator delete(void *p, size_t)
> {
> 	if (p != 0)
> 		add_to_freelist(static_cast<T *>(p));
> }
> template <class T> void CachedObj<T>::add_to_freelist(T *p)
> {
> 	p->CachedObj<T>::next = freeStore;
> 	freeStore = p;
> }

Best regards,
Thomas


^ permalink raw reply	[flat|nested] 2+ messages in thread

end of thread, other threads:[~2023-12-25 14:48 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2023-12-23 23:49 Runtime error about undefined behavior Xi Xue
2023-12-25 14:48 ` Thomas Bleher

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).