qgis / QGIS

QGIS is a free, open source, cross platform (lin/win/mac) geographical information system (GIS)
https://qgis.org
GNU General Public License v2.0
10.66k stars 3.02k forks source link

QGIS 3.34.13-Prizren crashes when attempting to open custom QT form with Python script. #59602

Open arielsbecker opened 4 days ago

arielsbecker commented 4 days ago

What is the bug or the crash?

User Feedback

I have a custom QT form in place for a vector layer. It's loaded from a .ui file. It also has a .py code associated.

On QGIS 3.22 it worked just fine.

I compiled this new version (3.34.13-Prizren) following the instructions on the INSTALL.md file. Everything else seems to work fine. It is only custom Python code that fails this way.

Report Details

Python Stack Trace

Fatal Python error: Segmentation fault

Current thread 0x00007bc9b36efac0 (most recent call first):
  <no Python frame>

Extension modules: PyQt5.QtCore, PyQt5.QtGui, PyQt5.QtWidgets, PyQt5.QtPrintSupport, PyQt5.Qsci, PyQt5.QtNetwork, PyQt5.QtMultimedia, PyQt5.QtXml, PyQt5.QtPositioning, PyQt5.QtSql, qgis._core, qgis._gui, qgis._analysis, yaml._yaml, osgeo._gdal, osgeo._gdalconst, osgeo._ogr, osgeo._osr, psycopg2._psycopg, lxml._elementpath, lxml.etree, _brotli, simplejson._speedups, markupsafe._speedups, numpy.core._multiarray_umath, numpy.core._multiarray_tests, numpy.linalg.lapack_lite, numpy.linalg._umath_linalg, numpy.fft._pocketfft_internal, numpy.random._common, numpy.random.bit_generator, numpy.random._bounded_integers, numpy.random._mt19937, numpy.random.mtrand, numpy.random._philox, numpy.random._pcg64, numpy.random._sfc64, numpy.random._generator (total: 38)

Stack Trace No stack trace is available.

QGIS Info QGIS Version: 3.34.13-Prizren QGIS code branch: Release 3.34 Compiled against Qt: 5.15.3 Running against Qt: 5.15.3 Compiled against GDAL: 3.4.1 Running against GDAL: 3.4.1

System Info CPU Type: x86_64 Kernel Type: linux Kernel Version: 6.8.0-49-generic

Steps to reproduce the issue

Add this sample vector layer. It's EPSG:4326.

{ "type": "FeatureCollection", "name": "conurbaguessr", "features": [ { "type": "Feature", "properties": { "categoryColor": "#FF0000", "categoryId": 1, "categoryName": "Rotondas", "createdAt": "2024-11-22T23:06:08", "description": "Rotonda de Alpargatas", "id": 1 }, "geometry": { "type": "Point", "coordinates": [ -58.189191888998657, -34.840859582770449 ] } }, { "type": "Feature", "properties": { "categoryColor": "#FFFF00", "categoryId": 3, "categoryName": "Puentes", "createdAt": "2024-11-22T23:06:08", "description": "Puente 12", "id": 2 }, "geometry": { "type": "Point", "coordinates": [ -58.513435744846767, -34.722463364053183 ] } }, { "type": "Feature", "properties": { "categoryColor": "#0000FF", "categoryId": 2, "categoryName": "Cruces", "createdAt": "2024-11-22T23:06:08", "description": "Cruce de Varela", "id": 3 }, "geometry": { "type": "Point", "coordinates": [ -58.260453305725385, -34.782107900845403 ] } }, { "type": "Feature", "properties": { "categoryColor": "#0000FF", "categoryId": 2, "categoryName": "Cruces", "createdAt": "2024-11-22T23:06:08", "description": "Cruce de Castelar", "id": 4 }, "geometry": { "type": "Point", "coordinates": [ -58.748751983872666, -34.584687891203686 ] } }, { "type": "Feature", "properties": { "categoryColor": "#FFFF00", "categoryId": 3, "categoryName": "Puentes", "createdAt": "2024-11-22T23:06:08", "description": "Puente Alsina", "id": 5 }, "geometry": { "type": "Point", "coordinates": [ -58.416692175731029, -34.65961634454046 ] } } ] }

Use this custom QT form (minified here so it doesn't occupy the entire screen).

<?xml version="1.0" encoding="UTF-8"?> <ui version="4.0"><class>Dialog</class><widget class="QDialog" name="Dialog"><property name="geometry"><rect><x>0</x><y>0</y><width>400</width><height>119</height></rect></property><property name="windowTitle"><string>Conurbaguessr</string></property><widget class="QLabel" name="lblID"><property name="geometry"><rect><x>20</x><y>13</y><width>72</width><height>16</height></rect></property><property name="text"><string>ID</string></property></widget><widget class="QLineEdit" name="id"><property name="enabled"><bool>true</bool></property><property name="geometry"><rect><x>100</x><y>10</y><width>291</width><height>21</height></rect></property><property name="readOnly"><bool>true</bool></property></widget><widget class="QLabel" name="lblDescription"><property name="geometry"><rect><x>20</x><y>33</y><width>71</width><height>16</height></rect></property><property name="text"><string>Descripción</string></property></widget><widget class="QLineEdit" name="description"><property name="geometry"><rect><x>100</x><y>30</y><width>291</width><height>21</height></rect></property></widget><widget class="QLabel" name="lblCategory"><property name="geometry"><rect><x>20</x><y>53</y><width>71</width><height>16</height></rect></property><property name="text"><string>Categoría</string></property></widget><widget class="QComboBox" name="categoryId"><property name="geometry"><rect><x>100</x><y>50</y><width>291</width><height>21</height></rect></property></widget><widget class="QDialogButtonBox" name="buttonBox"><property name="geometry"><rect><x>50</x><y>80</y><width>341</width><height>32</height></rect></property><property name="orientation"><enum>Qt::Horizontal</enum></property><property name="standardButtons"><set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set></property></widget></widget><resources><connections><connection><sender>buttonBox</sender><signal>accepted()</signal><receiver>Dialog</receiver><slot>accept()</slot><hints><hint type="sourcelabel"><x>248</x><y>254</y></hint><hint type="destinationlabel"><x>157</x><y>274</y></hint></hints></connection><connection><sender>buttonBox</sender><signal>rejected()</signal><receiver>Dialog</receiver><slot>reject()</slot><hints><hint type="sourcelabel"><x>316</x><y>260</y></hint><hint type="destinationlabel"><x>286</x><y>274</y></hint></hints></connection></connections></ui>

Now associate this Python code

from qgis.PyQt.QtWidgets import QWidget, QDialogButtonBox
import datetime

def my_form_open(dialog, layer, feature):
    global myDialog, myFeature, myLayer, id, nuevo

    myDialog = dialog
    myLayer = layer
    myFeature = feature
    nuevo = 0

    if not myFeature['id']:
        #El elemento no existe; debemos colocar el combobox en la posición Otros, que es por defecto.
        nuevo = 1
        comboBox = myDialog.findChild(QWidget,"categoryId")
        txtid = myDialog.findChild(QWidget, "id")
        idx = myLayer.fields().indexFromName('id')
        id = myLayer.maximumValue(idx) + 1 #id
        txtid.setText(str(id))
        comboBox.setCurrentIndex(6)

    buttonBox = myDialog.findChild(QDialogButtonBox,"buttonBox")
    buttonBox.button(QDialogButtonBox.Ok).setToolTip('Guarda los cambios.')
    buttonBox.button(QDialogButtonBox.Cancel).setToolTip('Descarta los cambios.')
    buttonBox.accepted.connect(validate)

def categoriaNombre(indice):
    match indice:
        case 1:
            return 'Rotondas'
        case 2:
            return 'Cruces'
        case 3:
            return 'Puentes'
        case 4:
            return 'Estaciones'
        case 5:
            return 'Plazas'
        case 6:
            return 'Parques'
        case _:
            return 'Otros'

def categoriaColor(indice):
    match indice:
        case 1:
            return '#FF0000'
        case 2:
            return '#0000FF'
        case 3:
            return '#FFFF00'
        case 4:
            return '#FF00FF'
        case 5:
            return '#13AD00'
        case 6:
            return '#0E5605'
        case _:
            return '#484848'

def CrearGeometria():
    # Esta función ya hace bien lo que se le pide.
    geom = myFeature.geometry()
    xy = geom.asPoint()
    categoryId = myDialog.findChild(QWidget, "categoryId").currentIndex() + 1 #categoryId
    description = myDialog.findChild(QWidget, "description").text() #description
    categoryName = categoriaNombre(categoryId) #categoryName
    categoryColor = categoriaColor(categoryId) #categoryColor
    fecha = datetime.datetime.now().isoformat() #createdAt
    createdAt = fecha[:-7]
    boolSuccess = 1

    field_idx = myLayer.fields().indexOf("id")
    myFeature.setAttribute(field_idx, id)

    field_idx = myLayer.fields().indexOf("id")
    myFeature.setAttribute(field_idx, id)

    field_idx = myLayer.fields().indexOf("categoryColor")
    myFeature.setAttribute(field_idx, categoryColor)

    field_idx = myLayer.fields().indexOf("categoryId")
    myFeature.setAttribute(field_idx, categoryId)

    field_idx = myLayer.fields().indexOf("categoryName")
    myFeature.setAttribute(field_idx, categoryName)

    field_idx = myLayer.fields().indexOf("createdAt")
    myFeature.setAttribute(field_idx, createdAt)

    field_idx = myLayer.fields().indexOf("description")
    myFeature.setAttribute(field_idx, description)

    if myFeature.isValid():
        boolSuccess = 1
    else:
        boolSuccess = 0

    if not boolSuccess:
        myLayer.rollBack()
        raise Exception("¡Error al crear el nuevo pin!")

def ActualizarGeometria():
    # No hace falta modificar esta función; ya está perfecta.
    categoryId = myDialog.findChild(QWidget, "categoryId").currentIndex() + 1 #categoryId
    description = myDialog.findChild(QWidget, "description").text() #description
    categoryName = categoriaNombre(categoryId) #categoryName
    categoryColor = categoriaColor(categoryId) #categoryColor
    fecha = datetime.datetime.now().isoformat() #createdAt
    createdAt = fecha[:-7]
    boolSuccess = 1

    id = myFeature['id'] #id

    # Cambiamos sólo lo que hace falta cambiar: categoryId, description, categoryName, categoryColor, createdAt
    boolSuccess = 1

    field_idx = myLayer.fields().indexOf("categoryId")
    attribute_changed = myLayer.changeAttributeValue(myFeature.id(), field_idx, categoryId)
    if not attribute_changed:
        boolSuccess = 0

    field_idx = myLayer.fields().indexOf("description")
    attribute_changed = myLayer.changeAttributeValue(myFeature.id(), field_idx, description)
    if not attribute_changed:
        boolSuccess = 0

    field_idx = myLayer.fields().indexOf("categoryName")
    attribute_changed = myLayer.changeAttributeValue(myFeature.id(), field_idx, categoryName)
    if not attribute_changed:
        boolSuccess = 0

    field_idx = myLayer.fields().indexOf("categoryColor")
    attribute_changed = myLayer.changeAttributeValue(myFeature.id(), field_idx, categoryColor)
    if not attribute_changed:
        boolSuccess = 0

    field_idx = myLayer.fields().indexOf("createdAt")
    attribute_changed = myLayer.changeAttributeValue(myFeature.id(), field_idx, createdAt)
    if not attribute_changed:
        boolSuccess = 0

    if not boolSuccess:
        raise Exception("¡Error al actualizar el marcador!")
    else:
        myLayer.commitChanges()

def validate():
    if nuevo:
        CrearGeometria()
    else:
        ActualizarGeometria()

Open the vector layer in edit mode. Try to identify a feature, or create a new one. Either way, it crashes. On 3.22 it works perfectly fine.

Versions

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">

QGIS version | 3.34.13-Prizren | QGIS code branch | Release 3.34 -- | -- | -- | -- Qt version | 5.15.3 Python version | 3.10.12 GDAL/OGR version | 3.4.1 PROJ version | 8.2.1 EPSG Registry database version | v10.041 (2021-12-03) GEOS version | 3.10.2-CAPI-1.16.0 SQLite version | 3.37.2 PDAL version | 2.3.0 PostgreSQL client version | 14.13 (Ubuntu 14.13-0ubuntu0.22.04.1) SpatiaLite version | 5.0.1 QWT version | 6.1.4 QScintilla2 version | 2.11.6 OS version | Ubuntu 22.04.3 LTS   |   |   |   Active Python plugins digitizr | 1.2.0 grassprovider | 2.12.99 MetaSearch | 0.3.6 db_manager | 0.1.20 processing | 2.12.99 QGIS version 3.34.13-Prizren QGIS code branch [Release 3.34](https://github.com/qgis/QGIS/tree/release-3_34) Qt version 5.15.3 Python version 3.10.12 GDAL/OGR version 3.4.1 PROJ version 8.2.1 EPSG Registry database version v10.041 (2021-12-03) GEOS version 3.10.2-CAPI-1.16.0 SQLite version 3.37.2 PDAL version 2.3.0 PostgreSQL client version 14.13 (Ubuntu 14.13-0ubuntu0.22.04.1) SpatiaLite version 5.0.1 QWT version 6.1.4 QScintilla2 version 2.11.6 OS version Ubuntu 22.04.3 LTS Active Python plugins digitizr 1.2.0 grassprovider 2.12.99 MetaSearch 0.3.6 db_manager 0.1.20 processing 2.12.99 ### Supported QGIS version - [X] I'm running a supported QGIS version according to [the roadmap](https://www.qgis.org/en/site/getinvolved/development/roadmap.html#release-schedule). ### New profile - [X] I tried with a new [QGIS profile](https://docs.qgis.org/latest/en/docs/user_manual/introduction/qgis_configuration.html#working-with-user-profiles) ### Additional context My QGIS installation is compiled from source. No errors were found during compilation.
arielsbecker commented 4 days ago

Adding this as it might be helpful.

Console output:

QGIS died on signal 11Extra Info File: /tmp/qgis-crash-info-90541  
Could not attach to process.  If your uid matches the uid of the target  
process, check the setting of /proc/sys/kernel/yama/ptrace_scope, or try  
again as the root user.  For more details, see /etc/sysctl.d/10-ptrace.conf  
ptrace: Operation not permitted.  
No thread selected  
No stack.  
gdb returned 256  
Aborted (core dumped)

That qgis-crash-info-90541 file isn't of much help, but I'm pasting here its contents anyway.

/dev/cpp/QGIS/build-master$ cat /tmp/qgis-crash-info-90541
90541
0

/tmp/qgis-python-crash-info-90541
./output/bin/qgis "/home/ariel/Documents/Projects/SIG/SIG personal/sig-personal.qgs"
QGIS Version: 3.34.13-Prizren
QGIS code branch: Release 3.34
Compiled against Qt: 5.15.3
Running against Qt: 5.15.3
Compiled against GDAL: 3.4.1
Running against GDAL: 3.4.1
arielsbecker commented 4 days ago

Update: I think it crashes due to the fact I compiled without Python support.

However, now that I'm trying to ccmake with DWITH_PY_COMPILE = ON, I'm experiencing the same as reported in #35440 (WITH_PY_COMPILE option is broken).

Here's the last output of ccmake:

CMake Error at python/CMakeLists.txt:38 (add_custom_command):
  No TARGET 'pyutils' has been created in this directory.
Call Stack (most recent call first):
  python/processing/CMakeLists.txt:22 (PY_COMPILE)
nicogodet commented 4 days ago

WITH_BINDINGS ?