vectorgraphics / asymptote

2D & 3D TeX-Aware Vector Graphics Language
https://asymptote.sourceforge.io/
GNU General Public License v3.0
533 stars 89 forks source link

`Assertion '!this->empty()' failed.` error on malformed Asymptote program #412

Closed user202729 closed 6 months ago

user202729 commented 6 months ago

To reproduce:

var arrow=draw((0, 0)--(1, 1));

Error message:

/usr/include/c++/13.2.1/bits/stl_vector.h:1232: std::vector<_Tp, _Alloc>::reference std::vector<_Tp, _Alloc>::back() [with _Tp = vm::item; _Alloc = gc_allocator_ignore_off_page<vm::item>; reference = vm::item&]: Assertion '!this->empty()' failed.
Aborted (core dumped)

Granted the code is invalid Asymptote code, but it would be nice if the interpreter can give better diagnostic than segmentation fault.

johncbowman commented 6 months ago

What platform are you on and what version of Asymptote are you using? With Asymptote version 2.86, the error message I get is:

  implicitshipout=false;
                 ^
base.old/plain.asy: 58.18: dereference of null pointer

Configuration:

asy --version
Asymptote version 2.86 [(C) 2004 Andy Hammerlindl, John C. Bowman, Tom Prince]

ENABLED OPTIONS:
V3D      3D vector graphics output
WebGL    3D HTML rendering
OpenGL   3D OpenGL rendering
SSBO     GLSL shader storage buffer objects
GSL      GNU Scientific Library (special functions)
FFTW3    Fast Fourier transforms
Eigen    Eigenvalue library
XDR      External Data Representation (portable binary file format for V3D)
CURL     URL support
LSP      Language Server Protocol
Readline Interactive history and editing
Sigsegv  Distinguish stack overflows from segmentation faults
GC       Boehm garbage collector
threads  Render OpenGL in separate thread

DISABLED OPTIONS:
johncbowman commented 6 months ago

In any case, a raised assert is not a segmentation fault and has no security implications.

user202729 commented 6 months ago
$ asy --version
Asymptote version 2.87-25 [(C) 2004 Andy Hammerlindl, John C. Bowman, Tom Prince]

ENABLED OPTIONS:
V3D      3D vector graphics output
WebGL    3D HTML rendering
OpenGL   3D OpenGL rendering
SSBO     GLSL shader storage buffer objects
GSL      GNU Scientific Library (special functions)
FFTW3    Fast Fourier transforms
XDR      External Data Representation (portable binary file format for V3D)
CURL     URL support
LSP      Language Server Protocol
Readline Interactive history and editing
Sigsegv  Distinguish stack overflows from segmentation faults
GC       Boehm garbage collector
threads  Render OpenGL in separate thread

DISABLED OPTIONS:
Eigen    Eigenvalue library

I just built from source from git latest I think.

johncbowman commented 6 months ago

I can't reproduce this behaviour.

What platform did you compile it on and what compiler did use. Also, what version of Boehm GC are you using?

user202729 commented 6 months ago

I use Arch Linux, and install the AUR package asymptote-git with the default configuration.

I'm not sure how to check Boehm GC version.

johncbowman commented 6 months ago

Sorry, it appears that I accidentally closed the issue, but in any case try compiling Asymptote directly from an official release (or from the latest git source).

user202729 commented 6 months ago

It's weird that you can't reproduce the issue, maybe you disabled assertions in your build?

Either way, I tracked down the issue a bit.

If I print out the virtual machine code of asy -s -c 'void f(){}; var a=f();', then the code is

   0 constpush <item 0x7f287c4fff60>
   1 builtin
   2 varsave 1
   3 pop
   4 ret
   0 constpush <item 0x7f287c43e570>
   1 builtin
   2 varsave 1
   3 pop
   4 ret
   0 pushclosure
   1 makefunc
   2 varsave 2
   3 pop
   4 ret
   0 ret
   0 varpush 2
   1 popcall
   2 varsave 3
   3 pop
   4 ret
/usr/include/c++/13.2.1/bits/stl_vector.h:1232: std::vector<_Tp, _Alloc>::reference std::vector<_Tp, _Alloc>::back() [with _Tp = vm::item; _Alloc = gc_allocator_ignore_off_page<vm::item>; reference = vm::item&]: Assertion '!this->empty()' failed.
Aborted (core dumped)

The issue is caused by the varsave line which tries to access the top item of the stack, but the stack is empty. The compiler should not have allowed the deduced type to have type void.

johncbowman commented 6 months ago

Asserts are forced to be enabled in common.h:

/****
 * common.h
 *
 * Definitions common to all files.
 *****/

#ifndef COMMON_H
#define COMMON_H

#undef NDEBUG

Can you reproduce the issue by building from https://sourceforge.net/projects/asymptote/files/2.86/asymptote-2.86.src.tgz/download?

The code void f(){}; var a=f(); should assign void to a silently, without any error. There surely isn't any use for this but it is considered an innocuous error. An assert if it arises in the official code would be something to fix (unless it is a compiler or library bug on your system).

johncbowman commented 6 months ago

For the record here is the virtual machine code for Asymptote version 2.86 produced by asy -s -c 'void f(){}; var a=f();'

   0 constpush <item 0x7fb3ea57cea0>
   1 builtin
   2 varsave 1
   3 pop
   4 ret
   0 constpush <item 0x7fb3ea0b7750>
   1 builtin
   2 varsave 1
   3 pop
   4 ret
   0 constpush <item 0x7fb3e913b300>
   1 builtin
   2 varsave 2
   3 pop
   4 ret
   0 pushclosure
   1 makefunc
   2 varsave 3
   3 pop
   4 ret
   0 ret
   0 varpush 3
   1 popcall
   2 varsave 4
   3 pop
   4 ret
   0 ret
   0 ret
johncbowman commented 6 months ago

Correction: on disabling the garbage collector, I was finally able to reproduce a segmentation fault, so we have now have something to fix:

valgrind asy -c 'void f(){}; var a=f();'
==2911906== Memcheck, a memory error detector
==2911906== Copyright (C) 2002-2022, and GNU GPL'd, by Julian Seward et al.
==2911906== Using Valgrind-3.22.0 and LibVEX; rerun with -h for copyright info
==2911906== Command: ./asy -dir base -s -c void\ f(){};\ var\ a=f();
==2911906== 
   0 constpush <item 0x159be1b0>
   1 builtin
   2 varsave 1
   3 pop
   4 ret
   0 constpush <item 0x17882490>
   1 builtin
   2 varsave 1
   3 pop
   4 ret
   0 constpush <item 0x17c8f960>
   1 builtin
   2 varsave 2
   3 pop
   4 ret
   0 pushclosure
   1 makefunc
   2 varsave 3
   3 pop
   4 ret
   0 ret
   0 varpush 3
   1 popcall
   2 varsave 4
   3 pop
   4 ret
==2911906== Invalid read of size 8
==2911906==    at 0x67CB60: top (stack.h:122)
==2911906==    by 0x67CB60: vm::stack::runWithOrWithoutClosure(vm::lambda*, vm::frame*, vm::frame*) (stack.cc:376)
==2911906==    by 0x6A7D69: runRunnable(absyntax::runnable*, trans::coenv&, vm::interactiveStack&, transMode) (process.cc:167)
==2911906==    by 0x6A80B5: itree::run(trans::coenv&, vm::interactiveStack&, transMode) (process.cc:293)
==2911906==    by 0x6AA293: icore::doRun(bool, transMode) (process.cc:224)
==2911906==    by 0x6AA97A: doExec (process.cc:301)
==2911906==    by 0x6AA97A: doExec (process.cc:298)
==2911906==    by 0x6AA97A: runString(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, bool) (process.cc:856)
==2911906==    by 0x67CA61: vm::stack::runWithOrWithoutClosure(vm::lambda*, vm::frame*, vm::frame*) (stack.cc:465)
==2911906==    by 0x67D6B6: run (stack.cc:209)
==2911906==    by 0x67D6B6: vm::stack::load(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >) (stack.cc:566)
==2911906==    by 0x58E244: run::loadModule(vm::stack*) (runtime.in:766)
==2911906==    by 0x67CA61: vm::stack::runWithOrWithoutClosure(vm::lambda*, vm::frame*, vm::frame*) (stack.cc:465)
==2911906==    by 0x6A7D69: runRunnable(absyntax::runnable*, trans::coenv&, vm::interactiveStack&, transMode) (process.cc:167)
user202729 commented 6 months ago

Maybe the reason why you can't reproduce the assertion error is that accessing back() of an empty vector is undefined behavior, my version of libcxx just happen to raise an error, but your version doesn't.

Defining _GLIBCXX_DEBUG would turn that undefined behavior into "definitely an error", I think. (but that also worsen performance of lower_bound to O(n), among other things)