Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

gh-129346: Handle allocation errors for SQLite aggregate context #129347

Merged

Conversation

erlend-aasland
Copy link
Contributor

@erlend-aasland erlend-aasland commented Jan 27, 2025

@erlend-aasland
Copy link
Contributor Author

@serhiy-storchaka I would not bother with backporting this. It is not a bug.

@erlend-aasland
Copy link
Contributor Author

I'll let this sit until tomorrow before landing (unless Serhiy gives a thumbs up before that time).

@serhiy-storchaka
Copy link
Member

I think that there is a bug. The first call of sqlite3_aggregate_context can return NULL, but there are no checks for NULL anywhere.

https://www.sqlite.org/c3ref/aggregate_context.html

The sqlite3_aggregate_context(C,N) routine returns a NULL pointer when first called if N is less than or equal to zero or if a memory allocation error occurs.

If we can determine what the call was first, we can raise MemoryError for the first call and RuntimeError for the subsequent calls. Otherwise we should always raise MemoryError, it is simpler and safer.

@erlend-aasland
Copy link
Contributor Author

I'll go through this again; will ping you later Serhiy.

@erlend-aasland erlend-aasland marked this pull request as draft January 27, 2025 12:31
@erlend-aasland
Copy link
Contributor Author

If we can determine what the call was first, we can raise MemoryError for the first call and RuntimeError for the subsequent calls. Otherwise we should always raise MemoryError, it is simpler and safer.

The step callback is almost always the first, unless in some special circumstances where we'll go straight to the final callback (we've got a least one test case for this for aggregate functions in the test suite).

I think your suggestion makes sense, Serhiy.

@erlend-aasland erlend-aasland changed the title gh-129346: Assert SQLite aggregate context cannot be NULL in the step handler gh-129346: Handle memory errors for allocation of SQLite aggregate context Jan 27, 2025
@erlend-aasland erlend-aasland changed the title gh-129346: Handle memory errors for allocation of SQLite aggregate context gh-129346: Handle allocation errors for SQLite aggregate context Jan 27, 2025
@erlend-aasland erlend-aasland added 3.12 bugs and security fixes 3.13 bugs and security fixes 3.14 new features, bugs and security fixes needs backport to 3.12 bug and security fixes needs backport to 3.13 bugs and security fixes and removed skip news 3.12 bugs and security fixes 3.13 bugs and security fixes 3.14 new features, bugs and security fixes labels Jan 27, 2025
@erlend-aasland
Copy link
Contributor Author

[...] we can raise MemoryError for the first call and RuntimeError for the subsequent calls.

IIRC, the only subsequent callback will be the final callback if the first step callback fails.

Comment on lines +961 to +965
if (aggregate_instance == NULL) {
(void)PyErr_NoMemory();
set_sqlite_error(context, "unable to allocate SQLite aggregate context");
goto error;
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we had a way to hook into the SQLite memory allocator, we could simulate out-of-memory, but unfortunately the sqlite3 extension module does not support this (yet).

@erlend-aasland erlend-aasland marked this pull request as ready for review January 27, 2025 13:03
Copy link
Member

@serhiy-storchaka serhiy-storchaka left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should not we add checks also after other calls? Just for the case.

@erlend-aasland
Copy link
Contributor Author

erlend-aasland commented Jan 27, 2025

Should not we add checks also after other calls? Just for the case.

The query will be aborted if we set an SQLite error in step. We will go straight to final in that case; there will be no subsequent step callback (nor value nor inverse).

@erlend-aasland
Copy link
Contributor Author

Quoting the example code the SQLite docs for the inverse callback:

/*
** xInverse for sumint().
**
** This does the opposite of xStep() - subtracts the value of the argument
** from the current context value. The error checking can be omitted from
** this function, as it is only ever called after xStep() (so the aggregate
** context has already been allocated) and with a value that has already
** been passed to xStep() without error (so it must be an integer).
*/

The comment from the value callback is not as explicit, though.

Copy link
Member

@serhiy-storchaka serhiy-storchaka left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are four calls of sqlite3_aggregate_context(). One (in final_callback) is checked for NULL. Two (in inverse_callback and value_callback) have asserts. Can we guarantee that they never fail?

@erlend-aasland
Copy link
Contributor Author

There are four calls of sqlite3_aggregate_context(). One (in final_callback) is checked for NULL. Two (in inverse_callback and value_callback) have asserts. Can we guarantee that they never fail?

It is my understanding that the value and inverse callbacks will not be invoked after a failed step callback. I think it would be a breaking change if SQLite changed this behaviour. I think the asserts are enough.

@serhiy-storchaka
Copy link
Member

I worry about the case when step was not failed. Can they be called without calling step?

@erlend-aasland
Copy link
Contributor Author

erlend-aasland commented Jan 27, 2025

I worry about the case when step was not failed. Can they be called without calling step?

final can be called for the case where no rows was returned by the SQL query. If no rows was returned, the value and inverse callbacks has no meaning at all.

Copy link
Member

@serhiy-storchaka serhiy-storchaka left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then LGTM.

@erlend-aasland erlend-aasland merged commit 379ab85 into python:main Jan 27, 2025
45 checks passed
@erlend-aasland erlend-aasland deleted the sqlite/no-it-cannot-be-null branch January 27, 2025 17:16
@miss-islington-app

This comment has been minimized.

miss-islington pushed a commit to miss-islington/cpython that referenced this pull request Jan 27, 2025
@bedevere-app
Copy link

bedevere-app bot commented Jan 27, 2025

GH-129372 is a backport of this pull request to the 3.13 branch.

@bedevere-app bedevere-app bot removed the needs backport to 3.13 bugs and security fixes label Jan 27, 2025
miss-islington pushed a commit to miss-islington/cpython that referenced this pull request Jan 27, 2025
@bedevere-app
Copy link

bedevere-app bot commented Jan 27, 2025

GH-129373 is a backport of this pull request to the 3.12 branch.

@bedevere-app bedevere-app bot removed the needs backport to 3.12 bug and security fixes label Jan 27, 2025
erlend-aasland added a commit that referenced this pull request Jan 27, 2025
…xt (GH-129347) (#129373)

(cherry picked from commit 379ab85)

Co-authored-by: Erlend E. Aasland <[email protected]>
erlend-aasland added a commit that referenced this pull request Jan 27, 2025
…xt (GH-129347) (#129372)

(cherry picked from commit 379ab85)

Co-authored-by: Erlend E. Aasland <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants