Merge pull request #10146 from marioalexis84/app-string_hasher_py

App: Fix crash and undefined behavior in StringHasherPy and StringIDPy
This commit is contained in:
Chris Hennes
2023-08-13 13:45:06 -07:00
committed by GitHub
6 changed files with 189 additions and 103 deletions

View File

@@ -114,6 +114,8 @@
#include "PropertyFile.h"
#include "PropertyLinks.h"
#include "PropertyPythonObject.h"
#include "StringHasherPy.h"
#include "StringIDPy.h"
#include "TextDocument.h"
#include "Transactions.h"
#include "VRMLObject.h"
@@ -311,6 +313,9 @@ void Application::setupPythonTypes()
Base::Interpreter().addType(&App::MaterialPy::Type, pAppModule, "Material");
Base::Interpreter().addType(&App::MetadataPy::Type, pAppModule, "Metadata");
Base::Interpreter().addType(&App::StringHasherPy::Type, pAppModule, "StringHasher");
Base::Interpreter().addType(&App::StringIDPy::Type, pAppModule, "StringID");
// Add document types
Base::Interpreter().addType(&App::PropertyContainerPy::Type, pAppModule, "PropertyContainer");
Base::Interpreter().addType(&App::ExtensionContainerPy::Type, pAppModule, "ExtensionContainer");
@@ -2109,6 +2114,10 @@ void Application::initTypes()
App::RangeExpression ::init();
App::PyObjectExpression ::init();
// Topological naming classes
App::StringHasher ::init();
App::StringID ::init();
// register transaction type
new App::TransactionProducer<TransactionDocumentObject>
(DocumentObject::getClassTypeId());

View File

@@ -32,117 +32,135 @@ using namespace App;
// returns a string which represent the object e.g. when printed in python
std::string StringHasherPy::representation() const
{
std::ostringstream str;
str << "<StringHasher at " << getStringHasherPtr() << ">";
return str.str();
std::ostringstream str;
str << "<StringHasher at " << getStringHasherPtr() << ">";
return str.str();
}
PyObject *StringHasherPy::PyMake(struct _typeobject *, PyObject *, PyObject *) // Python wrapper
{
return new StringHasherPy(new StringHasher);
return new StringHasherPy(new StringHasher);
}
// constructor method
int StringHasherPy::PyInit(PyObject* , PyObject* )
int StringHasherPy::PyInit(PyObject* args, PyObject* kwds)
{
return 0;
char* kw[] = {nullptr};
if (!PyArg_ParseTupleAndKeywords(args, kwds, "", kw)) {
return -1;
}
return 0;
}
PyObject* StringHasherPy::isSame(PyObject *args)
{
PyObject *other;
if (!PyArg_ParseTuple(args, "O!", &StringHasherPy::Type, &other)){ // convert args: Python->C
return Py::new_reference_to(Py::False());
}
auto otherHasher = static_cast<StringHasherPy*>(other)->getStringHasherPtr();
return Py::new_reference_to(Py::Boolean(getStringHasherPtr() == otherHasher));
PyObject *other;
if (!PyArg_ParseTuple(args, "O!", &StringHasherPy::Type, &other)) {
return nullptr;
}
auto otherHasher = static_cast<StringHasherPy*>(other)->getStringHasherPtr();
bool same = getStringHasherPtr() == otherHasher;
return PyBool_FromLong(same ? 1 : 0);
}
PyObject* StringHasherPy::getID(PyObject *args)
{
long id = -1;
int index = 0;
PyObject *value = nullptr;
PyObject *base64 = Py_False;
if (!PyArg_ParseTuple(args, "l|i",&id,&index)) {
PyErr_Clear();
if (!PyArg_ParseTuple(args, "O|O",&value,&base64))
return nullptr;
}
if(id>0) {
PY_TRY {
auto sid = getStringHasherPtr()->getID(id, index);
if(!sid) Py_Return;
return sid.getPyObject();
}PY_CATCH;
}
std::string txt;
#if PY_MAJOR_VERSION >= 3
if (PyUnicode_Check(value)) {
txt = PyUnicode_AsUTF8(value);
}
#else
if (PyUnicode_Check(value)) {
PyObject* unicode = PyUnicode_AsLatin1String(value);
txt = PyString_AsString(unicode);
Py_DECREF(unicode);
}
else if (PyString_Check(value)) {
txt = PyString_AsString(value);
}
#endif
else
throw Py::TypeError("expect argument of type string");
PY_TRY {
QByteArray data;
StringIDRef sid;
if(PyObject_IsTrue(base64)) {
data = QByteArray::fromBase64(QByteArray::fromRawData(txt.c_str(),txt.size()));
sid = getStringHasherPtr()->getID(data,true);
}else
sid = getStringHasherPtr()->getID(txt.c_str(),txt.size());
return sid.getPyObject();
}PY_CATCH;
long id;
int index = 0;
if (PyArg_ParseTuple(args, "l|i", &id, &index)) {
if (id > 0) {
PY_TRY {
auto sid = getStringHasherPtr()->getID(id, index);
if (!sid) {
Py_Return;
}
return sid.getPyObject();
}
PY_CATCH;
}
else {
PyErr_SetString(PyExc_ValueError, "Id must be positive integer");
return nullptr;
}
}
PyErr_Clear();
PyObject *value = nullptr;
PyObject *base64 = Py_False;
if (PyArg_ParseTuple(args, "O!|O!", &PyUnicode_Type, &value, &PyBool_Type, &base64)) {
PY_TRY {
std::string txt = PyUnicode_AsUTF8(value);
QByteArray data;
StringIDRef sid;
if (PyObject_IsTrue(base64)) {
data = QByteArray::fromBase64(QByteArray::fromRawData(txt.c_str(),txt.size()));
sid = getStringHasherPtr()->getID(data,true);
}
else {
sid = getStringHasherPtr()->getID(txt.c_str(),txt.size());
}
return sid.getPyObject();
}
PY_CATCH;
}
PyErr_SetString(PyExc_TypeError, "Positive integer and optional integer or "
"string and optional boolean is required");
return nullptr;
}
Py::Int StringHasherPy::getCount() const {
return Py::Int((long)getStringHasherPtr()->count());
Py::Long StringHasherPy::getCount() const
{
return Py::Long(PyLong_FromSize_t(getStringHasherPtr()->count()), true);
}
Py::Int StringHasherPy::getSize() const {
return Py::Int((long)getStringHasherPtr()->size());
Py::Long StringHasherPy::getSize() const
{
return Py::Long(PyLong_FromSize_t(getStringHasherPtr()->size()), true);
}
Py::Boolean StringHasherPy::getSaveAll() const {
return Py::Boolean(getStringHasherPtr()->getSaveAll());
Py::Boolean StringHasherPy::getSaveAll() const
{
return Py::Boolean(getStringHasherPtr()->getSaveAll());
}
void StringHasherPy::setSaveAll(Py::Boolean value) {
getStringHasherPtr()->setSaveAll(value);
void StringHasherPy::setSaveAll(Py::Boolean value)
{
getStringHasherPtr()->setSaveAll(value);
}
Py::Int StringHasherPy::getThreshold() const {
return Py::Int((long)getStringHasherPtr()->getThreshold());
Py::Long StringHasherPy::getThreshold() const
{
return Py::Long(getStringHasherPtr()->getThreshold());
}
void StringHasherPy::setThreshold(Py::Int value) {
getStringHasherPtr()->setThreshold(value);
void StringHasherPy::setThreshold(Py::Long value)
{
getStringHasherPtr()->setThreshold(value);
}
Py::Dict StringHasherPy::getTable() const {
Py::Dict dict;
for(auto &v : getStringHasherPtr()->getIDMap())
dict.setItem(Py::Int(v.first),Py::String(v.second.dataToText()));
return dict;
Py::Dict StringHasherPy::getTable() const
{
Py::Dict dict;
for (const auto &v : getStringHasherPtr()->getIDMap()) {
dict.setItem(Py::Long(v.first), Py::String(v.second.dataToText()));
}
return dict;
}
PyObject *StringHasherPy::getCustomAttributes(const char* /*attr*/) const
{
return nullptr;
return nullptr;
}
int StringHasherPy::setCustomAttributes(const char* /*attr*/, PyObject* /*obj*/)
{
return 0;
return 0;
}

View File

@@ -32,59 +32,69 @@ using namespace App;
// returns a string which represent the object e.g. when printed in python
std::string StringIDPy::representation() const
{
return getStringIDPtr()->toString(_index);
return getStringIDPtr()->toString(this->_index);
}
PyObject* StringIDPy::isSame(PyObject *args)
{
PyObject *other = nullptr;
if (PyArg_ParseTuple(args, "O!", &StringIDPy::Type, &other) == 0) { // convert args: Python->C
return Py::new_reference_to(Py::False());
}
auto *otherPy = static_cast<StringIDPy*>(other);
return Py::new_reference_to(Py::Boolean(
otherPy->getStringIDPtr() == this->getStringIDPtr()
&& otherPy->_index == this->_index));
PyObject *other = nullptr;
if (!PyArg_ParseTuple(args, "O!", &StringIDPy::Type, &other)) {
return nullptr;
}
auto *otherPy = static_cast<StringIDPy*>(other);
bool same = (otherPy->getStringIDPtr() == this->getStringIDPtr())
&& (otherPy->_index == this->_index);
return PyBool_FromLong(same ? 1 : 0);
}
Py::Int StringIDPy::getValue() const {
return Py::Int(getStringIDPtr()->value());
Py::Long StringIDPy::getValue() const
{
return Py::Long(getStringIDPtr()->value());
}
Py::List StringIDPy::getRelated() const {
Py::List list;
for (const auto &id : getStringIDPtr()->relatedIDs()) {
list.append(Py::Long(id.value()));
}
return list;
Py::List StringIDPy::getRelated() const
{
Py::List list;
for (const auto &id : getStringIDPtr()->relatedIDs()) {
list.append(Py::Long(id.value()));
}
return list;
}
Py::String StringIDPy::getData() const {
return {Py::String(getStringIDPtr()->dataToText(this->_index))};
Py::String StringIDPy::getData() const
{
return Py::String(getStringIDPtr()->dataToText(this->_index));
}
Py::Boolean StringIDPy::getIsBinary() const {
return {getStringIDPtr()->isBinary()};
Py::Boolean StringIDPy::getIsBinary() const
{
return Py::Boolean(getStringIDPtr()->isBinary());
}
Py::Boolean StringIDPy::getIsHashed() const {
return {getStringIDPtr()->isHashed()};
Py::Boolean StringIDPy::getIsHashed() const
{
return Py::Boolean(getStringIDPtr()->isHashed());
}
Py::Int StringIDPy::getIndex() const {
return Py::Int(this->_index);
Py::Long StringIDPy::getIndex() const
{
return Py::Long(this->_index);
}
void StringIDPy::setIndex(Py::Int index) {
this->_index = index;
void StringIDPy::setIndex(Py::Long index)
{
this->_index = index;
}
PyObject *StringIDPy::getCustomAttributes(const char* /*attr*/) const
{
return nullptr;
return nullptr;
}
int StringIDPy::setCustomAttributes(const char* /*attr*/, PyObject* /*obj*/)
{
return 0;
return 0;
}

View File

@@ -5,6 +5,7 @@ SET(Test_SRCS
BaseTests.py
Document.py
Metadata.py
StringHasher.py
Menu.py
TestApp.py
TestGui.py

View File

@@ -28,5 +28,6 @@ FreeCAD.__unit_test__ += [ "BaseTests",
"UnitTests",
"Document",
"Metadata",
"StringHasher",
"UnicodeTests",
"TestPythonSyntax" ]

View File

@@ -0,0 +1,47 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
#***************************************************************************
#* Copyright (c) 2023 Mario Passaglia <mpassaglia[at]cbc.uba.ar> *
#* *
#* This file is part of FreeCAD. *
#* *
#* FreeCAD is free software: you can redistribute it and/or modify it *
#* under the terms of the GNU Lesser General Public License as *
#* published by the Free Software Foundation, either version 2.1 of the *
#* License, or (at your option) any later version. *
#* *
#* FreeCAD is distributed in the hope that it will be useful, but *
#* WITHOUT ANY WARRANTY; without even the implied warranty of *
#* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
#* Lesser General Public License for more details. *
#* *
#* You should have received a copy of the GNU Lesser General Public *
#* License along with FreeCAD. If not, see *
#* <https://www.gnu.org/licenses/>. *
#* *
#**************************************************************************/
import FreeCAD
import unittest
class TestStringHasher(unittest.TestCase):
def setUp(self):
self.strHash = FreeCAD.StringHasher()
self.strID = self.strHash.getID("A")
def testInit(self):
with self.assertRaises(TypeError):
FreeCAD.StringHasher(0)
def testGetID(self):
with self.assertRaises(ValueError):
self.strHash.getID(0)
def testStringHasherIsSame(self):
with self.assertRaises(TypeError):
self.strHash.isSame(0)
def testStringIDIsSame(self):
with self.assertRaises(TypeError):
self.strID.isSame(0)