Honor generator close, throw context, and docs (#8701)

* Honor close unraisable and deallocator messages

A failed close lookup on a yield-from target is unraisable.
Ignored GeneratorExit from finalize gets a traceback and the
closing-generator err_msg. __del__ uses the deallocator message.

Assisted-by: Grok:grok-4.6

* address review: claim, ag_running, frame steal

Keep the running claim through close() cleanup. ag_running
reads running_async. aclose ignore-yield does not mark the
generator closed. Steal the frame slot before uniqueness
and expose it with try_to_owned.

Assisted-by: Grok:grok-4.6

* Honor wrapper docs, yield assign, throw context

method-wrapper exposes the slot __doc__. Parenthesized yield
assignment uses the invalid-target messages. throw() restores
without overwriting __context__, then chains only from the
generator's own exc_info slot.

Assisted-by: Grok:grok-4.6

* Remove passing expectedFailure markers

method-wrapper docs, deallocator messages, and help pager
output now match, so drop the leftover markers.

Assisted-by: Grok:grok-4.6

* Honor wrapper signatures and assignment diagnostics

Slot docs include text signatures. method-wrapper compares and
hashes by identity. Keyword genexp and if/while assignment use
the invalid-target messages.

Assisted-by: Grok:grok-4.6

* address review: aclose, unraisable, frame publish

aclose ignore-yield does not close the generator. Format
unraisable object is None. close() leaves traceback to the
caller; finalize attaches the generator frame. Publish the
iframe before the lock-free slot.

Assisted-by: Grok:grok-4.6

* Honor member descriptors and wrapper annotations

Assisted-by: Grok:grok-4.6

* address review: getitem, bool doc, if assign

Assisted-by: Grok:grok-4.6

* Honor eval-mode assignment invalid syntax

Assisted-by: Grok:grok-4.6

* Supply getset and member docs from rustpython-doc

Assisted-by: Grok:grok-4.6

* Enable rustls SSL on WASI targets

Compile the rustls _ssl module for WASI and stub
socket waits plus the native certificate store.

Assisted-by: Grok:grok-4.6

* Add browser TLS _ssl in rustpython-wasm

Register rustls _ssl and a MemoryBIO-oriented _socket
from rustpython-wasm. Use rustls-rustcrypto because
ring cannot compile for wasm32-unknown-unknown.

Assisted-by: Grok:grok-4.6

* Define SSL protocol and cert constants as enums

Keep OpenSSL bitmask defines as integer constants.

Assisted-by: Grok:grok-4.6

* Ignore rustls deps cargo-shear cannot see

The rustpython-wasm _ssl sources are compiled through
a symlink, so shear does not count their crate uses.

Assisted-by: Grok:grok-4.6

* Replace wasm ssl symlink with a BIO module

Drop the stdlib ssl.rs link and keep a rustpython-wasm
_ssl that only covers MemoryBIO wrap_bio TLS.

Assisted-by: Grok:grok-4.6
This commit is contained in:
Jeong, YunWon authored and GitHub committed 2026-09-14 22:52:15 +09:00
1 parent d7e41ebff5
commit 73e547a9c3
46 files changed
+3334 -460

No files matched your search

Generated
+701 -61
View File
File diff suppressed because it is too large. Load diff
-1
View File
@@ -1014,7 +1014,6 @@ class TestBaseExitStack:
else:
self.fail("Expected KeyError, but no exception was raised")
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_exit_exception_with_correct_context(self):
# http://bugs.python.org/issue20317
@contextmanager
-7
View File
@@ -1553,7 +1553,6 @@ class ClassPropertiesAndMethods(unittest.TestCase):
else:
self.fail("finding the most derived metaclass should have failed")
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_classmethods(self):
# Testing class methods...
class C(object):
@@ -1615,7 +1614,6 @@ class ClassPropertiesAndMethods(unittest.TestCase):
del cm.x
self.assertNotHasAttr(cm, "x")
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_classmethod_staticmethod_annotations(self):
for deco in (classmethod, staticmethod):
@deco
@@ -2355,7 +2353,6 @@ class ClassPropertiesAndMethods(unittest.TestCase):
self.assertEqual(r(), None)
del r
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_properties(self):
# Testing property...
class C(object):
@@ -3329,7 +3326,6 @@ class ClassPropertiesAndMethods(unittest.TestCase):
eval("x %s y" % op),
"x=%d, y=%d" % (x, y))
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_descrdoc(self):
# Testing descriptor doc strings...
from _io import FileIO
@@ -4084,7 +4080,6 @@ class ClassPropertiesAndMethods(unittest.TestCase):
y = x ** 2
self.assertIn('unsupported operand type(s) for **', str(cm.exception))
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_pow_wrapper_error_messages(self):
self.assertRaisesRegex(TypeError,
'expected 1 or 2 arguments, got 0',
@@ -4654,7 +4649,6 @@ class ClassPropertiesAndMethods(unittest.TestCase):
with self.assertRaises(TypeError):
a >= b
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_method_wrapper(self):
# Testing method-wrapper objects...
# <type 'method-wrapper'> did not support any reflection before 2.5
@@ -4981,7 +4975,6 @@ class ClassPropertiesAndMethods(unittest.TestCase):
self.assertIn("cannot delete '__doc__' attribute of immutable type 'X'", str(cm.exception))
self.assertEqual(X.__doc__, "banana")
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_qualname(self):
descriptors = [str.lower, complex.real, float.real, int.__add__]
types = ['method', 'member', 'getset', 'wrapper']
-1
View File
@@ -1724,7 +1724,6 @@ class ExceptionTests(unittest.TestCase):
os.listdir(__file__)
self.assertEqual(cm.exception.errno, errno.ENOTDIR, cm.exception)
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: None != 'Exception ignored while calling dealloca[83 chars]200>'
def test_unraisable(self):
# Issue #22836: PyErr_WriteUnraisable() should give sensible reports
class BrokenDel:
+5 -5
View File
@@ -1369,7 +1369,7 @@ From the Iterators list, about the types of these things.
>>> [s for s in dir(i) if not s.startswith('_')]
['close', 'gi_code', 'gi_frame', 'gi_running', 'gi_suspended', 'gi_yieldfrom', 'send', 'throw']
>>> from test.support import HAVE_DOCSTRINGS
>>> print(i.__next__.__doc__ if HAVE_DOCSTRINGS else 'Implement next(self).') # TODO: RUSTPYTHON # doctest: +EXPECTED_FAILURE
>>> print(i.__next__.__doc__ if HAVE_DOCSTRINGS else 'Implement next(self).')
Implement next(self).
>>> iter(i) is i
True
@@ -2526,12 +2526,12 @@ Traceback (most recent call last):
...
SyntaxError: assignment to yield expression not possible
>>> def f(): (yield bar) = y # TODO: RUSTPYTHON # doctest: +EXPECTED_FAILURE
>>> def f(): (yield bar) = y
Traceback (most recent call last):
...
SyntaxError: cannot assign to yield expression here. Maybe you meant '==' instead of '='?
>>> def f(): (yield bar) += y # TODO: RUSTPYTHON # doctest: +EXPECTED_FAILURE
>>> def f(): (yield bar) += y
Traceback (most recent call last):
...
SyntaxError: 'yield expression' is an illegal expression for augmented assignment
@@ -2727,7 +2727,7 @@ RuntimeError: generator ignored GeneratorExit
Our ill-behaved code should be invoked during GC:
>>> with support.catch_unraisable_exception() as cm: # TODO: RUSTPYTHON # doctest: +EXPECTED_FAILURE
>>> with support.catch_unraisable_exception() as cm:
... g = f()
... next(g)
... gen_repr = repr(g)
@@ -2845,7 +2845,7 @@ to test.
... raise RuntimeError(message)
... invoke("del failed")
...
>>> with support.catch_unraisable_exception() as cm: # TODO: RUSTPYTHON # doctest: +EXPECTED_FAILURE
>>> with support.catch_unraisable_exception() as cm:
... leaker = Leaker()
... del_repr = repr(type(leaker).__del__)
... del leaker
+2 -2
View File
@@ -105,7 +105,7 @@ Verify that parenthesis are required in a statement
Verify that parenthesis are required when used as a keyword argument value
>>> dict(a = i for i in range(10)) # TODO: RUSTPYTHON # doctest: +EXPECTED_FAILURE
>>> dict(a = i for i in range(10))
Traceback (most recent call last):
...
SyntaxError: invalid syntax. Maybe you meant '==' or ':=' instead of '='?
@@ -242,7 +242,7 @@ Check that generator attributes are present
True
>>> from test.support import HAVE_DOCSTRINGS
>>> print(g.__next__.__doc__ if HAVE_DOCSTRINGS else 'Implement next(self).') # TODO: RUSTPYTHON # doctest: +EXPECTED_FAILURE
>>> print(g.__next__.__doc__ if HAVE_DOCSTRINGS else 'Implement next(self).')
Implement next(self).
>>> import types
>>> isinstance(g, types.GeneratorType)
-1
View File
@@ -3954,7 +3954,6 @@ class TestSignatureObject(unittest.TestCase):
('b', ..., ..., "positional_or_keyword")),
...))
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_signature_on_class(self):
class C:
def __init__(self, a):
-1
View File
@@ -787,7 +787,6 @@ class PydocDocTest(unittest.TestCase):
run_pydoc_for_request(pydoc.Helper.help, 'Help on function help in module pydoc:')
# test for pydoc.Helper() instance skipped because it is always meant to be interactive
@unittest.expectedFailure # TODO: RUSTPYTHON
@unittest.skipIf(hasattr(sys, 'gettrace') and sys.gettrace(),
'trace function introduces __locals__ unexpectedly')
@requires_docstrings
-1
View File
@@ -730,7 +730,6 @@ class StructTest(ComplexesAreIdenticalMixin, unittest.TestCase):
s2 = struct.Struct(s.format.encode())
self.assertEqual(s2.format, s.format)
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_struct_cleans_up_at_runtime_shutdown(self):
code = """if 1:
import struct
+10 -10
View File
@@ -1264,22 +1264,22 @@ Missing ':' before suites:
Traceback (most recent call last):
SyntaxError: cannot assign to __debug__
>>> if x = 3: # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE
>>> if x = 3:
... pass
Traceback (most recent call last):
SyntaxError: invalid syntax. Maybe you meant '==' or ':=' instead of '='?
>>> while x = 3: # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE
>>> while x = 3:
... pass
Traceback (most recent call last):
SyntaxError: invalid syntax. Maybe you meant '==' or ':=' instead of '='?
>>> if x.a = 3: # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE
>>> if x.a = 3:
... pass
Traceback (most recent call last):
SyntaxError: cannot assign to attribute here. Maybe you meant '==' instead of '='?
>>> while x.a = 3: # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE
>>> while x.a = 3:
... pass
Traceback (most recent call last):
SyntaxError: cannot assign to attribute here. Maybe you meant '==' instead of '='?
@@ -1885,19 +1885,19 @@ Traceback (most recent call last):
...
SyntaxError: keyword argument repeated: a
>>> {1, 2, 3} = 42 # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE
>>> {1, 2, 3} = 42
Traceback (most recent call last):
SyntaxError: cannot assign to set display here. Maybe you meant '==' instead of '='?
>>> {1: 2, 3: 4} = 42 # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE
>>> {1: 2, 3: 4} = 42
Traceback (most recent call last):
SyntaxError: cannot assign to dict literal here. Maybe you meant '==' instead of '='?
>>> f'{x}' = 42 # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE
>>> f'{x}' = 42
Traceback (most recent call last):
SyntaxError: cannot assign to f-string expression here. Maybe you meant '==' instead of '='?
>>> f'{x}-{y}' = 42 # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE
>>> f'{x}-{y}' = 42
Traceback (most recent call last):
SyntaxError: cannot assign to f-string expression here. Maybe you meant '==' instead of '='?
@@ -1973,11 +1973,11 @@ SyntaxError: 'u' and 'b' prefixes are incompatible
Traceback (most recent call last):
SyntaxError: 'b' and 'f' prefixes are incompatible
>>> t'{x}' = 42 # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE
>>> t'{x}' = 42
Traceback (most recent call last):
SyntaxError: cannot assign to t-string expression here. Maybe you meant '==' instead of '='?
>>> t'{x}-{y}' = 42 # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE
>>> t'{x}-{y}' = 42
Traceback (most recent call last):
SyntaxError: cannot assign to t-string expression here. Maybe you meant '==' instead of '='?
-1
View File
@@ -635,7 +635,6 @@ class TypesTests(unittest.TestCase):
self.assertIsInstance(object.__lt__, types.WrapperDescriptorType)
self.assertIsInstance(int.__lt__, types.WrapperDescriptorType)
@unittest.expectedFailure # TODO: RUSTPYTHON; ValueError: no signature found for builtin <method-wrapper '__get__' of wrapper_descriptor object
@unittest.skipIf(MISSING_C_DOCSTRINGS,
"Signature information for builtins requires docstrings")
def test_dunder_get_signature(self):
-1
View File
@@ -538,7 +538,6 @@ class TestPEP380Operation(unittest.TestCase):
"finishing g",
])
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_broken_getattr_handling(self):
"""
Test subiterator with a broken getattr implementation
+427 -35
View File
@@ -326,29 +326,39 @@ fn cpython_parse_diagnostic_override(
}
source_error!(invalid_dict_error(source_text));
source_error!(invalid_collection_assignment_error(source_text));
if !matches!(mode, Mode::Eval) {
source_error!(invalid_collection_assignment_error(source_text));
}
source_error!(invalid_group_error(source_text));
source_error!(invalid_def_type_params_error(source_text));
source_error!(invalid_expression_error(source_text));
source_error!(invalid_named_expression_error(source_text));
source_error!(invalid_plain_assignment_error(source_text));
source_error!(expression_assignment_error(source_text));
source_error!(invalid_annotation_target_error(source_text));
source_error!(invalid_assignment_target_error(source_text));
source_error!(invalid_augassign_target_error(source_text));
source_error!(invalid_for_target_error(source_text));
source_error!(invalid_with_target_error(source_text));
source_error!(invalid_delete_target_error(source_text));
source_error!(invalid_standalone_except_error(source_text));
source_error!(invalid_import_statement_error(source_text));
source_error!(invalid_import_target_error(source_text));
source_error!(invalid_except_as_target_error(source_text));
source_error!(invalid_match_mapping_rest_wildcard_error(source_text));
source_error!(invalid_match_as_target_error(source_text));
source_error!(invalid_for_if_clause_error(source_text));
source_error!(invalid_if_expression_statement_error(source_text));
source_error!(invalid_else_elif_error(source_text));
source_error!(mixed_except_handlers_error(source_text));
// Assignment and other statements are not expressions. Eval keeps the
// generic parse error rather than a statement-target rewrite.
if !matches!(mode, Mode::Eval) {
source_error!(invalid_plain_assignment_error(source_text));
source_error!(expression_assignment_error(source_text));
source_error!(invalid_annotation_target_error(source_text));
source_error!(invalid_assignment_target_error(source_text));
source_error!(invalid_condition_assignment_error(
source_text,
error.location.start().to_usize()
));
source_error!(invalid_augassign_target_error(source_text));
source_error!(invalid_for_target_error(source_text));
source_error!(invalid_with_target_error(source_text));
source_error!(invalid_delete_target_error(source_text));
source_error!(invalid_standalone_except_error(source_text));
source_error!(invalid_import_statement_error(source_text));
source_error!(invalid_import_target_error(source_text));
source_error!(invalid_except_as_target_error(source_text));
source_error!(invalid_match_mapping_rest_wildcard_error(source_text));
source_error!(invalid_match_as_target_error(source_text));
source_error!(invalid_for_if_clause_error(source_text));
source_error!(invalid_if_expression_statement_error(source_text));
source_error!(invalid_else_elif_error(source_text));
source_error!(mixed_except_handlers_error(source_text));
}
if matches!(
&error.error,
@@ -387,7 +397,11 @@ fn cpython_parse_diagnostic_override(
// into the generic "invalid syntax" message. rustpython-vm's `vm_new.rs`
// does this same collapse for its own callers; rustpython-compiler has no
// vm dependency, so mirror it here.
if matches!(&error.error, parser::ParseErrorType::ExpectedExpression) {
if matches!(
&error.error,
parser::ParseErrorType::ExpectedExpression
| parser::ParseErrorType::UnexpectedExpressionToken
) {
let (loc, end_loc) = adjusted_error_locations(source_file, error.location);
return Some(NormalizedParseDiagnostic::new(
parser::ParseErrorType::OtherError("invalid syntax".into()),
@@ -436,6 +450,10 @@ fn invalid_assignment_target_diagnostic(
ast::Expr::BinOp(_) => "cannot assign to expression".into(),
ast::Expr::If(_) => "cannot assign to conditional expression".into(),
ast::Expr::Generator(_) => "cannot assign to generator expression".into(),
ast::Expr::Yield(_) | ast::Expr::YieldFrom(_) => {
"cannot assign to yield expression here. Maybe you meant '==' instead of '='?"
.into()
}
ast::Expr::FString(_) => "invalid syntax".into(),
ast::Expr::StringLiteral(_)
| ast::Expr::BytesLiteral(_)
@@ -1893,6 +1911,14 @@ fn invalid_call_argument_assignment_error(
}
}
if is_simple_keyword_name(bytes, target_start, target_end) {
// NAME '=' expression for_if_clauses
if unparenthesized_comprehension(bytes, equal + 1) {
return Some(CpythonDiagnostic::new(
"invalid syntax. Maybe you meant '==' or ':=' instead of '='?".to_owned(),
target_start,
equal + 1,
));
}
return None;
}
Some(CpythonDiagnostic::new(
@@ -1902,6 +1928,37 @@ fn invalid_call_argument_assignment_error(
))
}
fn unparenthesized_comprehension(bytes: &[u8], mut index: usize) -> bool {
index = next_non_horizontal_whitespace(bytes, index);
if index >= bytes.len() {
return false;
}
let start = index;
let mut level = 0usize;
while index < bytes.len() {
match bytes[index] {
b'\'' | b'"' => index = skip_quoted_string(bytes, index),
b'(' | b'[' | b'{' => {
level += 1;
index += 1;
}
b')' | b']' | b'}' => {
if level == 0 {
return false;
}
level -= 1;
index += 1;
}
b',' | b':' if level == 0 => return false,
_ if level == 0 && index > start && starts_identifier(bytes, index, b"for") => {
return true;
}
_ => index += 1,
}
}
false
}
fn invalid_call_star_expression_error(
bytes: &[u8],
arg_start: usize,
@@ -2711,6 +2768,30 @@ fn parenthesized_single_starred_delete_target(bytes: &[u8], start: usize, end: u
false
}
fn assignment_target_expr_range(source: &str, start: usize, end: usize) -> Option<(usize, usize)> {
let bytes = source.as_bytes();
let (target_start, target_end) = trim_target_range(bytes, start, end);
if target_start >= target_end {
return None;
}
if parser::parse(
&source[target_start..target_end],
parser::Mode::Expression.into(),
)
.is_ok()
{
return Some((target_start, target_end));
}
// `def f(): (yield bar)` — skip the suite header so the remaining
// text is the assignment target expression.
let colon = top_level_colon(bytes, target_start, target_end)?;
let after = skip_horizontal_whitespace(bytes, colon + 1);
if after >= target_end {
return None;
}
Some((after, target_end))
}
fn trim_target_range(bytes: &[u8], mut start: usize, mut end: usize) -> (usize, usize) {
while start < end
&& matches!(
@@ -2745,10 +2826,7 @@ fn assignment_target_error_for_slice(
end: usize,
) -> Option<CpythonDiagnostic> {
let bytes = source.as_bytes();
let (target_start, target_end) = trim_target_range(bytes, start, end);
if target_start >= target_end {
return None;
}
let (target_start, target_end) = assignment_target_expr_range(source, start, end)?;
if starts_identifier(bytes, target_start, b"yield") {
return Some(CpythonDiagnostic::new(
"assignment to yield expression not possible".to_owned(),
@@ -2766,13 +2844,6 @@ fn assignment_target_error_for_slice(
let invalid_target = invalid_assignment_target(&expression.body)?;
let invalid_start = target_start + invalid_target.range().start().to_usize();
let invalid_end = target_start + invalid_target.range().end().to_usize();
if matches!(invalid_target, ast::Expr::FString(_)) {
return Some(CpythonDiagnostic::new(
"invalid syntax".to_owned(),
invalid_start,
invalid_end,
));
}
let name = delete_target_expr_name(invalid_target);
let top_level = invalid_target.range() == expression.body.range();
let bitwise_like = matches!(
@@ -2785,6 +2856,12 @@ fn assignment_target_error_for_slice(
| ast::Expr::StringLiteral(_)
| ast::Expr::BytesLiteral(_)
| ast::Expr::EllipsisLiteral(_)
| ast::Expr::Yield(_)
| ast::Expr::YieldFrom(_)
| ast::Expr::Set(_)
| ast::Expr::Dict(_)
| ast::Expr::FString(_)
| ast::Expr::TString(_)
);
Some(CpythonDiagnostic::new(
invalid_assignment_message(name, top_level && bitwise_like),
@@ -2919,6 +2996,161 @@ fn top_level_plain_assignment_offsets(bytes: &[u8]) -> Vec<usize> {
offsets
}
fn invalid_condition_assignment_error(
source: &str,
parse_error_offset: usize,
) -> Option<CpythonDiagnostic> {
let bytes = source.as_bytes();
let mut index = 0usize;
let mut line_start = 0usize;
let mut level = 0usize;
while index < bytes.len() {
match bytes[index] {
b'#' if level == 0 => {
while index < bytes.len() && bytes[index] != b'\n' {
index += 1;
}
}
b'\n' => {
line_start = index + 1;
index += 1;
}
b'\'' | b'"' => index = skip_quoted_string(bytes, index),
b'(' | b'[' | b'{' => {
level += 1;
index += 1;
}
b')' | b']' | b'}' => {
level = level.saturating_sub(1);
index += 1;
}
_ if level == 0
&& index == skip_horizontal_whitespace(bytes, line_start)
&& (starts_identifier(bytes, index, b"if")
|| starts_identifier(bytes, index, b"elif")
|| starts_identifier(bytes, index, b"while")) =>
{
let keyword_len = if starts_identifier(bytes, index, b"while") {
5
} else if starts_identifier(bytes, index, b"elif") {
4
} else {
2
};
let cond_start = skip_horizontal_whitespace(bytes, index + keyword_len);
let Some(colon) = top_level_colon(bytes, cond_start, bytes.len()) else {
index += keyword_len;
continue;
};
if parse_error_offset < index || parse_error_offset > colon {
index += keyword_len;
continue;
}
let Some(equal) = condition_plain_assignment(bytes, cond_start, colon) else {
index += keyword_len;
continue;
};
let (target_start, target_end) = trim_target_range(bytes, cond_start, equal);
if target_start >= target_end {
index += keyword_len;
continue;
}
if is_simple_keyword_name(bytes, target_start, target_end) {
return Some(CpythonDiagnostic::new(
"invalid syntax. Maybe you meant '==' or ':=' instead of '='?".to_owned(),
target_start,
equal + 1,
));
}
if let Some((expr_name, start, end, _)) =
expression_name_and_range(&source[target_start..target_end])
{
return Some(CpythonDiagnostic::new(
format!(
"cannot assign to {expr_name} here. Maybe you meant '==' instead of '='?"
),
target_start + start,
target_start + end,
));
}
return Some(CpythonDiagnostic::new(
"invalid syntax. Maybe you meant '==' or ':=' instead of '='?".to_owned(),
target_start,
equal + 1,
));
}
_ => index += 1,
}
}
None
}
fn condition_plain_assignment(bytes: &[u8], start: usize, end: usize) -> Option<usize> {
let mut index = start;
let mut nest = Vec::new();
while index < end {
match bytes[index] {
b'\'' | b'"' => index = skip_quoted_string(bytes, index),
b'(' => {
nest.push(if is_call_open(bytes, start, index) {
b'c'
} else {
b'g'
});
index += 1;
}
b'[' => {
nest.push(b'[');
index += 1;
}
b'{' => {
nest.push(b'{');
index += 1;
}
b')' | b']' | b'}' => {
nest.pop();
index += 1;
}
b'=' if is_plain_assignment_operator(bytes, index) && !nest.contains(&b'c') => {
return Some(index);
}
_ => index += 1,
}
}
None
}
fn is_call_open(bytes: &[u8], start: usize, open: usize) -> bool {
let mut index = open;
while index > start {
index -= 1;
match bytes[index] {
b' ' | b'\t' | b'\n' | b'\r' | b'\x0c' => {}
b')' | b']' => return true,
byte if byte >= 0x80 || is_ascii_identifier_char(byte) => {
let mut ident_start = index;
while ident_start > start
&& bytes
.get(ident_start - 1)
.is_some_and(|b| *b >= 0x80 || is_ascii_identifier_char(*b))
{
ident_start -= 1;
}
return !is_condition_keyword(&bytes[ident_start..=index]);
}
_ => return false,
}
}
false
}
fn is_condition_keyword(word: &[u8]) -> bool {
matches!(
word,
b"not" | b"and" | b"or" | b"in" | b"is" | b"if" | b"elif" | b"while" | b"await" | b"lambda"
)
}
fn invalid_assignment_target_error(source: &str) -> Option<CpythonDiagnostic> {
let bytes = source.as_bytes();
let offsets = top_level_plain_assignment_offsets(bytes);
@@ -2987,10 +3219,7 @@ fn top_level_augassign_offset(bytes: &[u8]) -> Option<(usize, usize)> {
fn invalid_augassign_target_error(source: &str) -> Option<CpythonDiagnostic> {
let bytes = source.as_bytes();
let (operator, _) = top_level_augassign_offset(bytes)?;
let (target_start, target_end) = trim_target_range(bytes, 0, operator);
if target_start >= target_end {
return None;
}
let (target_start, target_end) = assignment_target_expr_range(source, 0, operator)?;
let target_text = &source[target_start..target_end];
let Ok(parsed) = parser::parse(target_text, parser::Mode::Expression.into()) else {
return None;
@@ -5383,6 +5612,20 @@ fn missing_comma_expression_error(source: &str) -> Option<CpythonDiagnostic> {
index = identifier_end(bytes, index, bytes.len());
}
byte if !stack.is_empty() && expression_atom_start_byte(byte) => {
// `yield x` / `await x` are prefix expressions, not two
// adjacent atoms missing a comma.
if starts_identifier(bytes, index, b"yield") {
index = identifier_end(bytes, index, bytes.len());
let after_yield = skip_ascii_whitespace(bytes, index, bytes.len());
if starts_identifier(bytes, after_yield, b"from") {
index = identifier_end(bytes, after_yield, bytes.len());
}
continue;
}
if starts_identifier(bytes, index, b"await") {
index = identifier_end(bytes, index, bytes.len());
continue;
}
let atom_end = adjacent_atom_end(bytes, index).unwrap_or(index + 1);
let next = skip_ascii_whitespace(bytes, atom_end, bytes.len());
if next > atom_end
@@ -7486,6 +7729,155 @@ mod tests {
assert_eq!(span("[\u{3b1} \u{3b2}]"), (2, 5));
}
#[test]
fn parenthesized_yield_assignment_uses_invalid_target_message() {
let err = compile(
"def f(): (yield bar) = y\n",
Mode::Exec,
"<yield>",
CompileOpts::default(),
)
.expect_err("parenthesized yield is not an assignment target");
assert_eq!(
err.to_string(),
"cannot assign to yield expression here. Maybe you meant '==' instead of '='?"
);
}
#[test]
fn parenthesized_yield_augassign_uses_illegal_expression_message() {
let err = compile(
"def f(): (yield bar) += y\n",
Mode::Exec,
"<yield>",
CompileOpts::default(),
)
.expect_err("parenthesized yield is not an augmented assignment target");
assert_eq!(
err.to_string(),
"'yield expression' is an illegal expression for augmented assignment"
);
}
#[test]
fn kwarg_unparenthesized_genexp_uses_eq_or_walrus_message() {
let err = compile(
"dict(a = i for i in range(10))\n",
Mode::Exec,
"<kwarg>",
CompileOpts::default(),
)
.expect_err("unparenthesized genexp after '=' is invalid");
assert_eq!(
err.to_string(),
"invalid syntax. Maybe you meant '==' or ':=' instead of '='?"
);
}
#[test]
fn eval_fstring_assignment_keeps_invalid_syntax() {
for source in ["f'' = 3", "f'{0}' = x", "f'{x}' = x"] {
let err = compile(source, Mode::Eval, "<eval>", CompileOpts::default())
.expect_err("assignment is invalid in eval");
assert_eq!(err.to_string(), "invalid syntax", "{source}");
}
let err = compile("f'' = 3", Mode::Exec, "<exec>", CompileOpts::default())
.expect_err("f-string is not an assignment target");
assert_eq!(
err.to_string(),
"cannot assign to f-string expression here. Maybe you meant '==' instead of '='?"
);
}
#[test]
fn if_assignment_uses_eq_or_walrus_message() {
let err = compile(
"if x = 3: pass\n",
Mode::Exec,
"<if>",
CompileOpts::default(),
)
.expect_err("assignment in if condition is invalid");
assert_eq!(
err.to_string(),
"invalid syntax. Maybe you meant '==' or ':=' instead of '='?"
);
}
#[test]
fn parenthesized_if_assignment_uses_eq_or_walrus_message() {
for source in [
"if (x = 3): pass\n",
"if ((x = 3)): pass\n",
"if (x = 3) and y: pass\n",
] {
let err = compile(source, Mode::Exec, "<if>", CompileOpts::default())
.expect_err("parenthesized assignment in if condition is invalid");
assert_eq!(
err.to_string(),
"invalid syntax. Maybe you meant '==' or ':=' instead of '='?"
);
}
}
#[test]
fn earlier_syntax_error_is_not_replaced_by_later_condition() {
let err = compile(
"@@@\nif x = 3: pass\n",
Mode::Exec,
"<if>",
CompileOpts::default(),
)
.expect_err("the first invalid token is the syntax error");
assert_eq!(err.to_string(), "invalid syntax");
assert_eq!(err.python_location().0, 1);
}
#[test]
fn if_attribute_assignment_uses_invalid_target_hint() {
let err = compile(
"if x.a = 3: pass\n",
Mode::Exec,
"<if>",
CompileOpts::default(),
)
.expect_err("attribute assignment in if condition is invalid");
assert_eq!(
err.to_string(),
"cannot assign to attribute here. Maybe you meant '==' instead of '='?"
);
}
#[test]
fn parenthesized_yield_from_assignment_uses_invalid_target_message() {
let err = compile(
"def f(): (yield from value) = target\n",
Mode::Exec,
"<yield>",
CompileOpts::default(),
)
.expect_err("parenthesized yield from is not an assignment target");
assert_eq!(
err.to_string(),
"cannot assign to yield expression here. Maybe you meant '==' instead of '='?"
);
}
#[test]
fn set_display_assignment_uses_invalid_target_hint() {
let err = compile(
"{1, 2, 3} = 42\n",
Mode::Exec,
"<set>",
CompileOpts::default(),
)
.expect_err("set display is not an assignment target");
assert_eq!(
err.to_string(),
"cannot assign to set display here. Maybe you meant '==' instead of '='?"
);
}
#[test]
fn obsolete_not_equal_diagnostic_spans_the_whole_operator() {
let err = compile("2 <> 3\n", Mode::Exec, "<obsolete>", CompileOpts::default())
+63 -5
View File
@@ -168,6 +168,8 @@ pub(crate) fn impl_pyclass_impl(attr: PunctuatedNestedMeta, item: Item) -> Resul
itemsize,
} = extract_impl_attrs(attr, &impl_ty)?;
let payload_ty = attr_payload.unwrap_or(payload_guess);
context.getset_items.type_name = Some(payload_ty.to_string());
context.member_items.type_name = Some(payload_ty.to_string());
let method_def = &context.method_items;
let getset_impl = &context.getset_items;
let member_impl = &context.member_items;
@@ -265,6 +267,8 @@ pub(crate) fn impl_pyclass_impl(attr: PunctuatedNestedMeta, item: Item) -> Resul
..
} = extract_impl_attrs(attr, &trai.ident)?;
context.getset_items.type_name = Some(trai.ident.to_string());
context.member_items.type_name = Some(trai.ident.to_string());
let method_def = &context.method_items;
let getset_impl = &context.getset_items;
let member_impl = &context.member_items;
@@ -415,6 +419,21 @@ fn type_matches_path(ty: &syn::Type, path: &syn::Path) -> bool {
type_last.ident == path_last.ident
}
fn cpython_attr_doc(rust_type: &str, attr: &str) -> Option<String> {
let stripped = rust_type.strip_prefix("Py").unwrap_or(rust_type);
let lower = stripped.to_ascii_lowercase();
let underscored = format!("_{stripped}");
let class_names = [rust_type, stripped, lower.as_str(), underscored.as_str()];
for (key, doc) in &DB {
if class_names.iter().any(|class| {
*key == format!("{class}.{attr}") || key.ends_with(&format!(".{class}.{attr}"))
}) {
return Some((*doc).to_owned());
}
}
None
}
fn generate_class_def(
ident: &Ident,
name: &str,
@@ -978,10 +997,12 @@ where
let item_meta = MethodItemMeta::from_attr(ident.clone(), &item_attr)?;
let py_name = item_meta.method_name()?;
let coexist = item_meta.coexist()?;
// Disallow slot methods - they should be defined via trait implementations
// These are exposed as wrapper_descriptor via add_operators from SLOT_DEFS
if !args.context.is_trait {
// unless the method is METH_COEXIST.
if !args.context.is_trait && !coexist {
const FORBIDDEN_SLOT_METHODS: &[(&str, &str)] = &[
// Constructor/Initializer traits
("__new__", "Constructor"),
@@ -1123,6 +1144,7 @@ where
ident: ident.to_owned(),
doc,
raw,
coexist,
attr_name: self.inner.attr_name,
call_flags,
});
@@ -1328,11 +1350,13 @@ where
args.attrs.push(allow_attr);
}
let doc = args.attrs.doc();
args.context.member_items.add_item(
&py_name,
member_item_kind,
member_kind,
ident.clone(),
doc,
)?;
Ok(())
}
@@ -1348,6 +1372,7 @@ struct MethodNurseryItem {
cfgs: Vec<Attribute>,
ident: Ident,
raw: bool,
coexist: bool,
doc: Option<String>,
attr_name: AttrName,
call_flags: TokenStream,
@@ -1394,9 +1419,14 @@ impl ToTokens for MethodNursery {
_ => unreachable!(),
};
let call_flags = &item.call_flags;
let coexist_flags = if item.coexist {
quote! { | rustpython_vm::function::PyMethodFlags::COEXIST.bits() }
} else {
quote! {}
};
let flags = quote! {
rustpython_vm::function::PyMethodFlags::from_bits_retain(
(#binding_flags).bits() | (#call_flags).bits()
(#binding_flags).bits() | (#call_flags).bits() #coexist_flags
)
};
// TODO: intern
@@ -1437,6 +1467,7 @@ struct GetSetEntry {
struct GetSetNursery {
map: HashMap<(String, Vec<Attribute>), GetSetEntry>,
validated: bool,
type_name: Option<String>,
}
#[derive(Clone, Copy, Eq, PartialEq)]
@@ -1510,7 +1541,12 @@ impl ToTokens for GetSetNursery {
Some(setter) => quote_spanned! { setter.span() => .with_set(Self::#setter)},
None => quote! {},
};
let doc = match &entry.doc {
let doc = entry.doc.clone().or_else(|| {
self.type_name
.as_deref()
.and_then(|ty| cpython_attr_doc(ty, name))
});
let doc = match &doc {
Some(doc) => quote! { .with_doc(#doc) },
None => quote! {},
};
@@ -1540,12 +1576,14 @@ type MemberKindStr = Option<String>;
struct MemberNursery {
map: HashMap<String, MemberNurseryEntry>,
validated: bool,
type_name: Option<String>,
}
struct MemberNurseryEntry {
kind: MemberKindStr,
getter: Option<Ident>,
setter: Option<Ident>,
doc: Option<String>,
}
#[derive(Clone, Copy, Eq, PartialEq)]
@@ -1561,6 +1599,7 @@ impl MemberNursery {
kind: MemberItemKind,
member_kind: MemberKindStr,
item_ident: Ident,
doc: Option<String>,
) -> Result<()> {
assert!(!self.validated, "new item is not allowed after validation");
let entry = self
@@ -1570,6 +1609,7 @@ impl MemberNursery {
kind: member_kind,
getter: None,
setter: None,
doc: None,
});
let func = match kind {
MemberItemKind::Get => &mut entry.getter,
@@ -1579,6 +1619,11 @@ impl MemberNursery {
bail_span!(item_ident, "Multiple member accessors with name '{}'", name);
}
*func = Some(item_ident);
if matches!(kind, MemberItemKind::Get)
&& let Some(doc) = doc
{
entry.doc = Some(doc);
}
Ok(())
}
@@ -1625,10 +1670,19 @@ impl ToTokens for MemberNursery {
}
};
let getter = entry.getter.as_ref().unwrap();
let doc = entry.doc.clone().or_else(|| {
self.type_name
.as_deref()
.and_then(|ty| cpython_attr_doc(ty, name))
});
let doc = match &doc {
Some(doc) => quote! { Some(#doc) },
None => quote! { None },
};
quote_spanned! { getter.span() =>
class.set_str_attr(
#name,
ctx.new_member(#name, #member_kind, Self::#getter, #setter, class),
ctx.new_member(#name, #member_kind, Self::#getter, #setter, class, #doc),
ctx,
);
}
@@ -1640,7 +1694,7 @@ impl ToTokens for MemberNursery {
struct MethodItemMeta(ItemMetaInner);
impl ItemMeta for MethodItemMeta {
const ALLOWED_NAMES: &'static [&'static str] = &["name", "raw"];
const ALLOWED_NAMES: &'static [&'static str] = &["name", "raw", "coexist"];
fn from_inner(inner: ItemMetaInner) -> Self {
Self(inner)
@@ -1656,6 +1710,10 @@ impl MethodItemMeta {
self.inner()._bool("raw")
}
fn coexist(&self) -> Result<bool> {
self.inner()._bool("coexist")
}
fn method_name(&self) -> Result<String> {
let inner = self.inner();
let name = inner._optional_str("name")?;
+1 -1
View File
@@ -96,7 +96,7 @@ impl Drop for Owned {
fn drop(&mut self) {}
}
impl<'fd> Borrowed<'fd> {
impl Borrowed<'_> {
/// Create a `crt_fd::Borrowed` from a raw file descriptor.
///
/// # Safety
+1 -2
View File
@@ -26,11 +26,10 @@ pub mod crt_fd;
#[cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))]
pub mod fileutils;
#[cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))]
pub mod fs;
#[cfg(any(unix, windows))]
pub mod locale;
#[cfg(all(feature = "native-certs", not(target_arch = "wasm32")))]
#[cfg(feature = "native-certs")]
pub mod native_certs;
pub mod readline;
#[cfg(feature = "ssl")]
+9
View File
@@ -42,6 +42,7 @@ pub fn default_verify_paths() -> (String, String) {
(cafile, capath)
}
#[cfg(not(target_arch = "wasm32"))]
pub fn load() -> LoadResult {
let result = rustls_native_certs::load_native_certs();
LoadResult {
@@ -58,6 +59,14 @@ pub fn load() -> LoadResult {
}
}
#[cfg(target_arch = "wasm32")]
pub fn load() -> LoadResult {
LoadResult {
certs: Vec::new(),
errors: vec!["native certificate store is unavailable".to_owned()],
}
}
#[cfg(test)]
mod tests {
use super::first_existing;
+74 -12
View File
@@ -1,17 +1,56 @@
//! Protocol, verification, and option values published by `_ssl`.
pub const PROTOCOL_TLS: i32 = 2;
pub const PROTOCOL_TLS_CLIENT: i32 = 16;
pub const PROTOCOL_TLS_SERVER: i32 = 17;
pub const PROTOCOL_TLSV1: i32 = 3;
pub const PROTOCOL_TLSV1_1: i32 = 4;
pub const PROTOCOL_TLSV1_2: i32 = 5;
pub const PROTOCOL_TLSV1_3: i32 = 6;
/// `_SSLContext` constructor protocol. Exclusive selector, not an `SSL_OP_*` mask.
#[repr(i32)]
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum SslVersion {
Tls = 2,
Tls1 = 3,
Tls1_1 = 4,
Tls1_2 = 5,
Tls1_3 = 6,
TlsClient = 0x10,
TlsServer = 0x11,
}
pub const CERT_NONE: i32 = 0;
pub const CERT_OPTIONAL: i32 = 1;
pub const CERT_REQUIRED: i32 = 2;
pub const PROTOCOL_TLS: i32 = SslVersion::Tls as i32;
pub const PROTOCOL_TLS_CLIENT: i32 = SslVersion::TlsClient as i32;
pub const PROTOCOL_TLS_SERVER: i32 = SslVersion::TlsServer as i32;
pub const PROTOCOL_TLSV1: i32 = SslVersion::Tls1 as i32;
pub const PROTOCOL_TLSV1_1: i32 = SslVersion::Tls1_1 as i32;
pub const PROTOCOL_TLSV1_2: i32 = SslVersion::Tls1_2 as i32;
pub const PROTOCOL_TLSV1_3: i32 = SslVersion::Tls1_3 as i32;
/// `verify_mode`. Exclusive selector, not an `SSL_VERIFY_*` bitmask.
#[repr(i32)]
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum CertRequirements {
None = 0,
Optional = 1,
Required = 2,
}
pub const CERT_NONE: i32 = CertRequirements::None as i32;
pub const CERT_OPTIONAL: i32 = CertRequirements::Optional as i32;
pub const CERT_REQUIRED: i32 = CertRequirements::Required as i32;
/// TLS record version (`SSL3_VERSION` / `TLS1_*_VERSION` in `prov_ssl.h`).
#[repr(i32)]
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum ProtoVersion {
MinSupported = -2,
Ssl3 = 0x0300,
Tls1 = 0x0301,
Tls1_1 = 0x0302,
Tls1_2 = 0x0303,
Tls1_3 = 0x0304,
MaxSupported = -1,
}
pub const PROTO_TLSV1_2: i32 = ProtoVersion::Tls1_2 as i32;
pub const PROTO_TLSV1_3: i32 = ProtoVersion::Tls1_3 as i32;
/// `X509_V_FLAG_*` bitmasks from `x509_vfy.h`.
pub const VERIFY_DEFAULT: i32 = 0;
pub const VERIFY_CRL_CHECK_LEAF: i32 = 4;
pub const VERIFY_CRL_CHECK_CHAIN: i32 = 12;
@@ -24,7 +63,30 @@ pub const VERIFY_X509_PARTIAL_CHAIN: i32 = 0x80000;
/// use SAN only and never fall back to the certificate Common Name.
pub const HOSTFLAG_NEVER_CHECK_SUBJECT: i32 = 0x20;
pub const PROTO_TLSV1_2: i32 = 0x0303;
pub const PROTO_TLSV1_3: i32 = 0x0304;
/// `SSL_OP_NO_TLSv1_2` / `SSL_OP_NO_TLSv1_3` from `ssl.h`.
pub const OP_NO_TLSV1_2: i32 = 0x0800_0000;
pub const OP_NO_TLSV1_3: i32 = 0x2000_0000;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn protocol_and_cert_discriminants_match_published_consts() {
assert_eq!(PROTOCOL_TLS, 2);
assert_eq!(PROTOCOL_TLSV1, 3);
assert_eq!(PROTOCOL_TLSV1_1, 4);
assert_eq!(PROTOCOL_TLSV1_2, 5);
assert_eq!(PROTOCOL_TLSV1_3, 6);
assert_eq!(PROTOCOL_TLS_CLIENT, 0x10);
assert_eq!(PROTOCOL_TLS_SERVER, 0x11);
assert_eq!(CERT_NONE, 0);
assert_eq!(CERT_OPTIONAL, 1);
assert_eq!(CERT_REQUIRED, 2);
assert_eq!(PROTO_TLSV1_2, 0x0303);
assert_eq!(PROTO_TLSV1_3, 0x0304);
assert_eq!(ProtoVersion::Ssl3 as i32, 0x0300);
assert_eq!(ProtoVersion::Tls1 as i32, 0x0301);
assert_eq!(ProtoVersion::Tls1_1 as i32, 0x0302);
}
}
+3 -3
View File
@@ -2,7 +2,7 @@
use rustls::version::{TLS12, TLS13};
use super::constants::{OP_NO_TLSV1_2, OP_NO_TLSV1_3, PROTO_TLSV1_2, PROTO_TLSV1_3};
use super::constants::{OP_NO_TLSV1_2, OP_NO_TLSV1_3, PROTO_TLSV1_2, PROTO_TLSV1_3, ProtoVersion};
/// Why `server_hostname` was rejected.
#[derive(Debug, Clone, PartialEq, Eq)]
@@ -52,12 +52,12 @@ pub fn rustls_versions(
static TLS12_ONLY: &[&rustls::SupportedProtocolVersion] = &[&TLS12];
static TLS13_ONLY: &[&rustls::SupportedProtocolVersion] = &[&TLS13];
let min = if minimum == -2 {
let min = if minimum == ProtoVersion::MinSupported as i32 {
PROTO_TLSV1_2
} else {
minimum
};
let max = if maximum == -1 {
let max = if maximum == ProtoVersion::MaxSupported as i32 {
PROTO_TLSV1_3
} else {
maximum
+3 -4
View File
@@ -79,15 +79,14 @@ widestring = { workspace = true, optional = true }
[target.'cfg(not(any(target_os = "ios", target_os = "android", target_os = "windows", target_arch = "wasm32", target_os = "redox")))'.dependencies]
uuid = { workspace = true, features = ["v1"] }
# mmap + socket dependencies
# mmap + socket + OpenSSL dependencies
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
# OpenSSL dependencies (optional, for ssl-openssl feature)
openssl = { workspace = true, optional = true }
openssl-sys = { workspace = true, optional = true }
foreign-types-shared = { workspace = true, optional = true }
# Rustls dependencies (optional, for ssl-rustls feature)
# Rustls is the WASI TLS engine as well as the native one.
[target.'cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))'.dependencies]
rustls = { workspace = true, default-features = false, features = ["std", "tls12"], optional = true }
rustls-pemfile = { workspace = true, optional = true }
x509-parser = { workspace = true, optional = true }
+4 -4
View File
@@ -129,8 +129,8 @@ mod openssl;
#[cfg(all(
feature = "host_env",
not(target_arch = "wasm32"),
feature = "__ssl-rustls"
feature = "__ssl-rustls",
any(not(target_arch = "wasm32"), target_os = "wasi"),
))]
pub mod ssl;
@@ -255,8 +255,8 @@ pub fn stdlib_module_defs(ctx: &Context) -> Vec<&'static builtins::PyModuleDef>
_sqlite3::module_def(ctx),
#[cfg(all(
feature = "host_env",
not(target_arch = "wasm32"),
feature = "__ssl-rustls"
feature = "__ssl-rustls",
any(not(target_arch = "wasm32"), target_os = "wasi"),
))]
ssl::module_def(ctx),
statistics::module_def(ctx),
+61 -22
View File
@@ -27,6 +27,23 @@ mod msg;
pub use rustpython_host_env::ssl::{chain, cipher, oid, providers};
#[cfg(target_arch = "wasm32")]
mod sock_wait_stub {
use rustpython_vm::{PyRef, VirtualMachine, builtins::PyOSError};
#[derive(Copy, Clone)]
pub(crate) enum SockWaitKind {
Read,
Write,
#[allow(dead_code, reason = "kept in lockstep with the native wait kinds")]
Connect,
}
pub(crate) fn timeout_error_msg(vm: &VirtualMachine, msg: String) -> PyRef<PyOSError> {
vm.new_os_subtype_error(vm.ctx.exceptions.timeout_error.to_owned(), None, msg)
}
}
pub(crate) use _ssl::module_def;
#[allow(non_snake_case)]
@@ -38,7 +55,6 @@ mod _ssl {
hash::PyHash,
lock::{PyMutex, PyRwLock},
},
socket::{PySocket, SockWaitKind, sock_wait, timeout_error_msg},
vm::{
AsObject, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, TryFromObject,
VirtualMachine,
@@ -56,6 +72,11 @@ mod _ssl {
},
};
#[cfg(target_arch = "wasm32")]
use super::sock_wait_stub::{SockWaitKind, timeout_error_msg};
#[cfg(not(target_arch = "wasm32"))]
use crate::socket::{PySocket, SockWaitKind, sock_wait, timeout_error_msg};
// Import error types used in this module (others are exposed via pymodule(with(...)))
use super::error::{
PySSLError, PySSLWantReadError, PySSLWantWriteError, create_ssl_eof_error,
@@ -2484,24 +2505,33 @@ mod _ssl {
return Ok(false);
}
// Get timeout
let timeout = self.get_socket_timeout(vm)?;
// Check for non-blocking mode (timeout = 0)
if let Some(t) = timeout
&& t.is_zero()
#[cfg(target_arch = "wasm32")]
{
// Non-blocking mode - don't use select
let _ = (wait_kind, vm);
return Ok(false);
}
// Use select with the effective timeout
let py_socket: PyRef<PySocket> = self.io.socket_object(vm).try_into_value(vm)?;
let socket = py_socket
.sock()
.map_err(|e| vm.new_os_error(format!("Failed to get socket: {e}")))?;
#[cfg(not(target_arch = "wasm32"))]
{
// Get timeout
let timeout = self.get_socket_timeout(vm)?;
sock_wait(&socket, wait_kind, timeout, vm)
// Check for non-blocking mode (timeout = 0)
if let Some(t) = timeout
&& t.is_zero()
{
// Non-blocking mode - don't use select
return Ok(false);
}
// Use select with the effective timeout
let py_socket: PyRef<PySocket> = self.io.socket_object(vm).try_into_value(vm)?;
let socket = py_socket
.sock()
.map_err(|e| vm.new_os_error(format!("Failed to get socket: {e}")))?;
sock_wait(&socket, wait_kind, timeout, vm)
}
}
// Internal implementation with explicit timeout override
@@ -2516,19 +2546,28 @@ mod _ssl {
return Ok(false);
}
if let Some(t) = timeout
&& t.is_zero()
#[cfg(target_arch = "wasm32")]
{
// Non-blocking mode - don't use select
let _ = (wait_kind, timeout, vm);
return Ok(false);
}
let py_socket: PyRef<PySocket> = self.io.socket_object(vm).try_into_value(vm)?;
let socket = py_socket
.sock()
.map_err(|e| vm.new_os_error(format!("Failed to get socket: {e}")))?;
#[cfg(not(target_arch = "wasm32"))]
{
if let Some(t) = timeout
&& t.is_zero()
{
// Non-blocking mode - don't use select
return Ok(false);
}
sock_wait(&socket, wait_kind, timeout, vm).map_err(|e| e.into_pyexception(vm))
let py_socket: PyRef<PySocket> = self.io.socket_object(vm).try_into_value(vm)?;
let socket = py_socket
.sock()
.map_err(|e| vm.new_os_error(format!("Failed to get socket: {e}")))?;
sock_wait(&socket, wait_kind, timeout, vm).map_err(|e| e.into_pyexception(vm))
}
}
fn handshake_completed(&self) -> bool {
+3
View File
@@ -15,6 +15,9 @@
#[path = "../openssl/ssl_data_31.rs"]
mod ssl_data;
#[cfg(target_arch = "wasm32")]
use super::sock_wait_stub::{SockWaitKind, timeout_error_msg};
#[cfg(not(target_arch = "wasm32"))]
use crate::socket::{SockWaitKind, timeout_error_msg};
use crate::vm::VirtualMachine;
use rustpython_host_env::ssl::{TlsConnection, TlsError};
+17 -18
View File
@@ -144,7 +144,7 @@ impl PyAsyncGen {
}
#[pygetset]
fn ag_running(&self, _vm: &VirtualMachine) -> bool {
self.inner.running()
self.running_async.load()
}
#[pygetset]
fn ag_code(&self, _vm: &VirtualMachine) -> PyRef<PyCode> {
@@ -501,12 +501,11 @@ impl PyAsyncGenAThrow {
let (ty, val, tb) = self.value.clone();
let ret = self.ag.inner.throw(self.ag.as_object(), ty, val, tb, vm);
if self.aclose && self.ignored_close(&ret) {
return Err(self.yield_close(vm));
}
let ret = if self.aclose {
if self.ignored_close(&ret) {
Err(self.yield_close(vm))
} else {
ret.and_then(|o| o.into_async_pyresult(vm))
}
ret.and_then(|o| o.into_async_pyresult(vm))
} else {
PyAsyncGenWrappedValue::unbox(&self.ag, ret, vm)
};
@@ -572,12 +571,11 @@ impl PyAsyncGenAThrow {
exc_tb.unwrap_or_none(vm),
vm,
);
if self.aclose && self.ignored_close(&ret) {
return Err(self.yield_close(vm));
}
let res = if self.aclose {
if self.ignored_close(&ret) {
Err(self.yield_close(vm))
} else {
ret.and_then(|o| o.into_async_pyresult(vm))
}
ret.and_then(|o| o.into_async_pyresult(vm))
} else {
PyAsyncGenWrappedValue::unbox(&self.ag, ret, vm)
};
@@ -616,7 +614,6 @@ impl PyAsyncGenAThrow {
}
fn yield_close(&self, vm: &VirtualMachine) -> PyBaseExceptionRef {
self.ag.running_async.store(false);
self.ag.inner.closed.store(true);
self.state.store(AwaitableState::Closed);
vm.new_runtime_error("async generator ignored GeneratorExit")
}
@@ -829,14 +826,16 @@ impl IterNext for PyAnextAwaitable {
/// _PyGen_Finalize for async generators
impl Destructor for PyAsyncGen {
fn del(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<()> {
// Generator is already closed, nothing to do
if zelf.inner.closed.load() {
if zelf.inner.closed() {
return Ok(());
}
// Call the async generator finalizer hook if set.
Self::call_finalizer(zelf, vm);
if zelf.ag_finalizer.lock().clone().is_some() {
Self::call_finalizer(zelf, vm);
return Ok(());
}
if let Err(e) = zelf.inner.close(zelf.as_object(), vm) {
crate::coroutine::unraisable_while_closing(zelf.as_object(), &zelf.inner, e, vm);
}
Ok(())
}
}
+110 -53
View File
@@ -1,6 +1,8 @@
use super::{PyBoundMethod, PyGenericAlias, PyStr, PyType, PyTypeRef};
use super::{
PyBoundMethod, PyGenericAlias, PyStr, PyStrInterned, PyType, PyTypeRef, object_get_dict,
};
use crate::{
AsObject, Context, Py, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine,
AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine,
class::{PyClassDef, PyClassImpl},
common::lock::PyMutex,
function::{FuncArgs, PySetterValue},
@@ -88,20 +90,7 @@ impl Initializer for PyClassMethod {
fn init(zelf: PyRef<Self>, callable: Self::Args, vm: &VirtualMachine) -> PyResult<()> {
*zelf.callable.lock() = callable.clone();
// Copy wrapper attributes from the callable, mirroring functools.wraps.
let dict = zelf.as_object().dict().expect("classmethod has __dict__");
for attr in [
identifier!(vm.ctx, __doc__),
identifier!(vm.ctx, __name__),
identifier!(vm.ctx, __qualname__),
identifier!(vm.ctx, __module__),
identifier!(vm.ctx, __annotations__),
] {
if let Ok(value) = callable.get_attr(attr, vm) {
dict.set_item(attr, value, vm)?;
}
}
Ok(())
functools_wraps(zelf.as_object(), &callable, vm)
}
}
@@ -117,55 +106,68 @@ impl PyClassMethod {
flags(BASETYPE, HAS_DICT, HAS_WEAKREF)
)]
impl PyClassMethod {
#[pygetset]
fn __func__(&self) -> PyObjectRef {
self.callable.lock().clone()
#[pymember]
fn __func__(vm: &VirtualMachine, zelf: PyObjectRef) -> PyResult {
let zelf: &Py<Self> = zelf.try_to_value(vm)?;
Ok(zelf.callable.lock().clone())
}
#[pymember]
fn __wrapped__(vm: &VirtualMachine, zelf: PyObjectRef) -> PyResult {
let zelf: &Py<Self> = zelf.try_to_value(vm)?;
Ok(zelf.callable.lock().clone())
}
#[pygetset]
fn __wrapped__(&self) -> PyObjectRef {
self.callable.lock().clone()
}
#[pygetset]
fn __module__(&self, vm: &VirtualMachine) -> PyResult {
self.callable.lock().get_attr("__module__", vm)
}
#[pygetset]
fn __qualname__(&self, vm: &VirtualMachine) -> PyResult {
self.callable.lock().get_attr("__qualname__", vm)
}
#[pygetset]
fn __name__(&self, vm: &VirtualMachine) -> PyResult {
self.callable.lock().get_attr("__name__", vm)
}
#[pygetset]
fn __annotations__(&self, vm: &VirtualMachine) -> PyResult {
self.callable.lock().get_attr("__annotations__", vm)
fn __annotations__(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult {
let callable = zelf.callable.lock().clone();
descriptor_get_wrapped_attribute(
callable,
zelf.as_object(),
identifier!(vm.ctx, __annotations__),
vm,
)
}
#[pygetset(setter)]
fn set___annotations__(&self, value: PySetterValue, vm: &VirtualMachine) -> PyResult<()> {
match value {
PySetterValue::Assign(v) => self.callable.lock().set_attr("__annotations__", v, vm),
PySetterValue::Delete => Ok(()), // Silently ignore delete like CPython
}
fn set___annotations__(
zelf: &Py<Self>,
value: PySetterValue,
vm: &VirtualMachine,
) -> PyResult<()> {
descriptor_set_wrapped_attribute(
zelf.as_object(),
identifier!(vm.ctx, __annotations__),
value,
"classmethod",
vm,
)
}
#[pygetset]
fn __annotate__(&self, vm: &VirtualMachine) -> PyResult {
self.callable.lock().get_attr("__annotate__", vm)
fn __annotate__(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult {
let callable = zelf.callable.lock().clone();
descriptor_get_wrapped_attribute(
callable,
zelf.as_object(),
identifier!(vm.ctx, __annotate__),
vm,
)
}
#[pygetset(setter)]
fn set___annotate__(&self, value: PySetterValue, vm: &VirtualMachine) -> PyResult<()> {
match value {
PySetterValue::Assign(v) => self.callable.lock().set_attr("__annotate__", v, vm),
PySetterValue::Delete => Ok(()), // Silently ignore delete like CPython
}
fn set___annotate__(
zelf: &Py<Self>,
value: PySetterValue,
vm: &VirtualMachine,
) -> PyResult<()> {
descriptor_set_wrapped_attribute(
zelf.as_object(),
identifier!(vm.ctx, __annotate__),
value,
"classmethod",
vm,
)
}
#[pygetset]
@@ -225,3 +227,58 @@ impl Representable for PyClassMethod {
pub(crate) fn init(context: &'static Context) {
PyClassMethod::extend_class(context, context.types.classmethod_type);
}
pub(crate) fn functools_wraps(
wrapper: &PyObject,
wrapped: &PyObject,
vm: &VirtualMachine,
) -> PyResult<()> {
for attr in [
identifier!(vm.ctx, __module__),
identifier!(vm.ctx, __name__),
identifier!(vm.ctx, __qualname__),
identifier!(vm.ctx, __doc__),
] {
if let Some(value) = vm.get_attribute_opt(wrapped.to_owned(), attr)? {
wrapper.set_attr(attr, value, vm)?;
}
}
Ok(())
}
pub(crate) fn descriptor_get_wrapped_attribute(
wrapped: PyObjectRef,
obj: &PyObject,
name: &'static PyStrInterned,
vm: &VirtualMachine,
) -> PyResult {
let dict = object_get_dict(obj.to_owned(), vm)?;
if let Some(res) = dict.get_item_opt(name, vm)? {
return Ok(res);
}
let res = wrapped.get_attr(name, vm)?;
dict.set_item(name, res.clone(), vm)?;
Ok(res)
}
pub(crate) fn descriptor_set_wrapped_attribute(
obj: &PyObject,
name: &'static PyStrInterned,
value: PySetterValue,
type_name: &str,
vm: &VirtualMachine,
) -> PyResult<()> {
let dict = object_get_dict(obj.to_owned(), vm)?;
match value {
PySetterValue::Delete => match dict.del_item(name, vm) {
Ok(()) => Ok(()),
Err(e) if e.fast_isinstance(vm.ctx.exceptions.key_error) => Err(vm
.new_attribute_error(format!(
"'{type_name}' object has no attribute '{}'",
name.as_str()
))),
Err(e) => Err(e),
},
PySetterValue::Assign(value) => dict.set_item(name, value, vm),
}
}
+8 -6
View File
@@ -539,14 +539,16 @@ impl PyComplex {
with(PyRef, Comparable, Hashable, Constructor, AsNumber, Representable)
)]
impl PyComplex {
#[pygetset]
const fn real(&self) -> f64 {
self.value.re
#[pymember]
fn real(vm: &VirtualMachine, zelf: PyObjectRef) -> PyResult {
let zelf: &Py<Self> = zelf.try_to_value(vm)?;
Ok(vm.ctx.new_float(zelf.value.re).into())
}
#[pygetset]
const fn imag(&self) -> f64 {
self.value.im
#[pymember]
fn imag(vm: &VirtualMachine, zelf: PyObjectRef) -> PyResult {
let zelf: &Py<Self> = zelf.try_to_value(vm)?;
Ok(vm.ctx.new_float(zelf.value.im).into())
}
#[pymethod]
+1 -1
View File
@@ -185,7 +185,7 @@ impl Destructor for PyCoroutine {
return Ok(());
}
if let Err(e) = zelf.inner.close(zelf.as_object(), vm) {
vm.run_unraisable(e, None, zelf.as_object().to_owned());
crate::coroutine::unraisable_while_closing(zelf.as_object(), &zelf.inner, e, vm);
}
Ok(())
}
+60 -14
View File
@@ -762,16 +762,12 @@ impl SlotFunc {
func(&other, &obj, vm) // Swapped: other op obj
}
Self::NumTernary(func) => {
let (y, z): (PyObjectRef, crate::function::OptionalArg<PyObjectRef>) =
args.bind(vm)?;
let z = z.unwrap_or_else(|| vm.ctx.none());
let (y, z) = pow_args(args, vm)?;
func(&obj, &y, &z, vm)
}
Self::NumTernaryRight(func) => {
let (y, z): (PyObjectRef, crate::function::OptionalArg<PyObjectRef>) =
args.bind(vm)?;
let z = z.unwrap_or_else(|| vm.ctx.none());
func(&y, &obj, &z, vm) // Swapped: y ** obj % z
let (y, z) = pow_args(args, vm)?;
func(&y, &obj, &z, vm)
}
// Buffer protocol
Self::GetBuffer(func) => {
@@ -792,6 +788,24 @@ impl SlotFunc {
}
}
/// wrap_ternaryfunc / check_pow_args
fn pow_args(args: FuncArgs, vm: &VirtualMachine) -> PyResult<(PyObjectRef, PyObjectRef)> {
if let Some(err) = args.check_kwargs_empty(vm) {
return Err(err);
}
let size = args.args.len();
if !(1..=2).contains(&size) {
return Err(vm.new_type_error(format!("expected 1 or 2 arguments, got {size}")));
}
let y = args.args[0].clone();
let z = if size == 2 {
args.args[1].clone()
} else {
vm.ctx.none()
};
Ok((y, z))
}
/// Parse the `flags` argument of `__buffer__`. wrap_buffer
fn parse_buffer_flags(
arg: PyObjectRef,
@@ -884,7 +898,16 @@ impl PyWrapper {
#[pygetset]
fn __doc__(&self) -> Option<&'static str> {
self.doc
let doc = self.doc?;
type_::get_doc_from_internal_doc(self.name.as_str(), doc)
}
#[pygetset]
fn __text_signature__(&self) -> Option<String> {
self.doc.and_then(|doc| {
type_::get_text_signature_from_internal_doc(self.name.as_str(), doc)
.map(|signature| signature.to_string())
})
}
}
@@ -954,6 +977,25 @@ impl PyMethodWrapper {
self.wrapper.typ.to_owned()
}
#[pygetset]
fn __qualname__(&self) -> String {
format!("{}.{}", self.wrapper.typ.name(), self.wrapper.name)
}
#[pygetset]
fn __doc__(&self) -> Option<&'static str> {
let doc = self.wrapper.doc?;
type_::get_doc_from_internal_doc(self.wrapper.name.as_str(), doc)
}
#[pygetset]
fn __text_signature__(&self) -> Option<String> {
self.wrapper.doc.and_then(|doc| {
type_::get_text_signature_from_internal_doc(self.wrapper.name.as_str(), doc)
.map(|signature| signature.to_string())
})
}
#[pymethod]
fn __reduce__(zelf: PyRef<Self>, vm: &VirtualMachine) -> PyResult {
let builtins_getattr = vm.builtins.get_attr("getattr", vm)?;
@@ -985,10 +1027,14 @@ impl Representable for PyMethodWrapper {
}
impl Hashable for PyMethodWrapper {
fn hash(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<PyHash> {
let obj_hash = zelf.obj.hash(vm)?;
let wrapper_hash = zelf.wrapper.as_object().get_id() as PyHash;
Ok(obj_hash ^ wrapper_hash)
fn hash(zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<PyHash> {
// wrapperobject_hash: pointer hash of descr xor self
let mut hash =
(zelf.wrapper.as_object().get_id() as PyHash) ^ (zelf.obj.get_id() as PyHash);
if hash == -1 {
hash = -2;
}
Ok(hash)
}
}
@@ -997,11 +1043,11 @@ impl Comparable for PyMethodWrapper {
zelf: &Py<Self>,
other: &PyObject,
op: PyComparisonOp,
vm: &VirtualMachine,
_vm: &VirtualMachine,
) -> PyResult<crate::function::PyComparisonValue> {
op.eq_only(|| {
let other = class_or_notimplemented!(Self, other);
let eq = zelf.wrapper.is(&other.wrapper) && vm.bool_eq(&zelf.obj, &other.obj)?;
let eq = zelf.wrapper.is(&other.wrapper) && zelf.obj.is(&other.obj);
Ok(eq.into())
})
}
+2
View File
@@ -579,7 +579,9 @@ impl Py<PyDict> {
Ok(PyArithmeticValue::Implemented(true))
}
/// Return self[key].
#[cfg_attr(feature = "flame-it", flame("PyDictRef"))]
#[pymethod(coexist)]
fn __getitem__(&self, key: PyObjectRef, vm: &VirtualMachine) -> PyResult {
self.inner_getitem(&*key, vm)
}
+1 -1
View File
@@ -160,7 +160,7 @@ impl Destructor for PyGenerator {
return Ok(());
}
if let Err(e) = zelf.inner.close(zelf.as_object(), vm) {
vm.run_unraisable(e, None, zelf.as_object().to_owned());
crate::coroutine::unraisable_while_closing(zelf.as_object(), &zelf.inner, e, vm);
}
Ok(())
}
+4 -2
View File
@@ -288,8 +288,10 @@ impl PyList {
}
}
fn __getitem__(&self, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult {
self._getitem(&needle, vm)
/// Return self[index].
#[pymethod(coexist)]
fn __getitem__(&self, index: PyObjectRef, vm: &VirtualMachine) -> PyResult {
self._getitem(&index, vm)
}
fn _setitem(&self, needle: &PyObject, value: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
+21 -10
View File
@@ -135,22 +135,33 @@ impl PyProperty {
// Access functions
#[pygetset]
fn fget(&self) -> Option<PyObjectRef> {
self.getter.read().clone()
#[pymember]
fn fget(vm: &VirtualMachine, zelf: PyObjectRef) -> PyResult {
let zelf: &Py<Self> = zelf.try_to_value(vm)?;
Ok(vm.unwrap_or_none(zelf.getter.read().clone()))
}
pub(crate) fn get_fget(&self) -> Option<PyObjectRef> {
self.getter.read().clone()
}
#[pygetset]
fn fset(&self) -> Option<PyObjectRef> {
#[pymember]
fn fset(vm: &VirtualMachine, zelf: PyObjectRef) -> PyResult {
let zelf: &Py<Self> = zelf.try_to_value(vm)?;
Ok(vm.unwrap_or_none(zelf.setter.read().clone()))
}
fn get_fset(&self) -> Option<PyObjectRef> {
self.setter.read().clone()
}
#[pygetset]
fn fdel(&self) -> Option<PyObjectRef> {
#[pymember]
fn fdel(vm: &VirtualMachine, zelf: PyObjectRef) -> PyResult {
let zelf: &Py<Self> = zelf.try_to_value(vm)?;
Ok(vm.unwrap_or_none(zelf.deleter.read().clone()))
}
fn get_fdel(&self) -> Option<PyObjectRef> {
self.deleter.read().clone()
}
@@ -215,9 +226,9 @@ impl PyProperty {
// Create property args with updated values
let args = PropertyArgs {
fget: new_getter.or_else(|| zelf.fget()),
fset: new_setter.or_else(|| zelf.fset()),
fdel: new_deleter.or_else(|| zelf.fdel()),
fget: new_getter.or_else(|| zelf.get_fget()),
fset: new_setter.or_else(|| zelf.get_fset()),
fdel: new_deleter.or_else(|| zelf.get_fdel()),
doc,
};
+57 -42
View File
@@ -1,4 +1,9 @@
use super::{PyGenericAlias, PyStr, PyType, PyTypeRef};
use super::{
PyGenericAlias, PyStr, PyType, PyTypeRef,
classmethod::{
descriptor_get_wrapped_attribute, descriptor_set_wrapped_attribute, functools_wraps,
},
};
use crate::{
AsObject, Context, Py, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine,
class::{PyClassDef, PyClassImpl},
@@ -80,10 +85,7 @@ impl Initializer for PyStaticMethod {
fn init(zelf: PyRef<Self>, callable: Self::Args, vm: &VirtualMachine) -> PyResult<()> {
*zelf.callable.lock() = callable.clone();
if let Ok(doc) = callable.get_attr("__doc__", vm) {
zelf.as_object().set_attr("__doc__", doc, vm)?;
}
Ok(())
functools_wraps(zelf.as_object(), &callable, vm)
}
}
@@ -92,55 +94,68 @@ impl Initializer for PyStaticMethod {
flags(BASETYPE, HAS_DICT, HAS_WEAKREF)
)]
impl PyStaticMethod {
#[pygetset]
fn __func__(&self) -> PyObjectRef {
self.callable.lock().clone()
#[pymember]
fn __func__(vm: &VirtualMachine, zelf: PyObjectRef) -> PyResult {
let zelf: &Py<Self> = zelf.try_to_value(vm)?;
Ok(zelf.callable.lock().clone())
}
#[pymember]
fn __wrapped__(vm: &VirtualMachine, zelf: PyObjectRef) -> PyResult {
let zelf: &Py<Self> = zelf.try_to_value(vm)?;
Ok(zelf.callable.lock().clone())
}
#[pygetset]
fn __wrapped__(&self) -> PyObjectRef {
self.callable.lock().clone()
}
#[pygetset]
fn __module__(&self, vm: &VirtualMachine) -> PyResult {
self.callable.lock().get_attr("__module__", vm)
}
#[pygetset]
fn __qualname__(&self, vm: &VirtualMachine) -> PyResult {
self.callable.lock().get_attr("__qualname__", vm)
}
#[pygetset]
fn __name__(&self, vm: &VirtualMachine) -> PyResult {
self.callable.lock().get_attr("__name__", vm)
}
#[pygetset]
fn __annotations__(&self, vm: &VirtualMachine) -> PyResult {
self.callable.lock().get_attr("__annotations__", vm)
fn __annotations__(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult {
let callable = zelf.callable.lock().clone();
descriptor_get_wrapped_attribute(
callable,
zelf.as_object(),
identifier!(vm.ctx, __annotations__),
vm,
)
}
#[pygetset(setter)]
fn set___annotations__(&self, value: PySetterValue, vm: &VirtualMachine) -> PyResult<()> {
match value {
PySetterValue::Assign(v) => self.callable.lock().set_attr("__annotations__", v, vm),
PySetterValue::Delete => Ok(()), // Silently ignore delete like CPython
}
fn set___annotations__(
zelf: &Py<Self>,
value: PySetterValue,
vm: &VirtualMachine,
) -> PyResult<()> {
descriptor_set_wrapped_attribute(
zelf.as_object(),
identifier!(vm.ctx, __annotations__),
value,
"staticmethod",
vm,
)
}
#[pygetset]
fn __annotate__(&self, vm: &VirtualMachine) -> PyResult {
self.callable.lock().get_attr("__annotate__", vm)
fn __annotate__(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult {
let callable = zelf.callable.lock().clone();
descriptor_get_wrapped_attribute(
callable,
zelf.as_object(),
identifier!(vm.ctx, __annotate__),
vm,
)
}
#[pygetset(setter)]
fn set___annotate__(&self, value: PySetterValue, vm: &VirtualMachine) -> PyResult<()> {
match value {
PySetterValue::Assign(v) => self.callable.lock().set_attr("__annotate__", v, vm),
PySetterValue::Delete => Ok(()), // Silently ignore delete like CPython
}
fn set___annotate__(
zelf: &Py<Self>,
value: PySetterValue,
vm: &VirtualMachine,
) -> PyResult<()> {
descriptor_set_wrapped_attribute(
zelf.as_object(),
identifier!(vm.ctx, __annotate__),
value,
"staticmethod",
vm,
)
}
#[pygetset]
+46 -31
View File
@@ -1,6 +1,6 @@
use crate::{
AsObject, Py, PyObject, PyObjectRef, PyRef, PyResult, TryFromObject, VirtualMachine,
builtins::{PyCode, PyStrRef, PyTupleRef},
AsObject, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, TryFromObject, VirtualMachine,
builtins::{PyCode, PyStrRef, PyTraceback, PyTupleRef},
common::lock::PyMutex,
exceptions::types::PyBaseException,
frame::{ExecutionResult, FrameObject, FrameObjectRef, FrameOwner, InterpreterFrame},
@@ -8,6 +8,7 @@ use crate::{
object::{PyAtomicRef, Traverse, TraverseFn},
protocol::PyIterReturn,
};
use core::sync::atomic::Ordering;
use crossbeam_utils::atomic::AtomicCell;
impl ExecutionResult {
@@ -86,6 +87,7 @@ fn gen_name(jen: &PyObject, vm: &VirtualMachine) -> &'static str {
impl Coro {
pub fn new(frame: FrameObjectRef, name: PyStrRef, qualname: PyStrRef) -> Self {
let code = frame.iframe().code().to_owned();
frame.as_object().mark_cache_published();
Self {
frame: Some(frame).into(),
code,
@@ -97,30 +99,21 @@ impl Coro {
}
}
/// `_PyFrame_ClearExceptCode`. Clear locals unless another reference
/// still holds the frame object; then take_ownership instead.
/// `_PyFrame_ClearExceptCode`. Steal the frame slot first, then
/// clear locals only if this was the last reference.
fn clear_except_code(&self) {
let Some(frame) = self.frame.deref() else {
return;
};
if frame.as_object().strong_count() == 1 {
frame.clear_locals_and_stack();
} else {
self.take_ownership();
}
}
/// Steal the iframe's `frame_obj` pointer (`take_ownership`). The live
/// locals stay on that frame object; this generator no longer roots it.
fn take_ownership(&self) {
let Some(frame) = (unsafe { self.frame.swap(None) }) else {
return;
};
frame.iframe().owner.store(
FrameOwner::FrameObject as i8,
core::sync::atomic::Ordering::Release,
);
frame.clear_generator();
if frame.as_object().strong_count() == 1 {
frame.clear_locals_and_stack();
} else {
frame.iframe().owner.store(
FrameOwner::FrameObject as i8,
core::sync::atomic::Ordering::Release,
);
}
}
/// Retire the generator if the frame it just ran came to an end. The claim
@@ -331,20 +324,18 @@ impl Coro {
vm.ctx.none(),
)
});
if !matches!(&result, Ok(ExecutionResult::Yield(_))) {
self.closed.store(true);
self.clear_except_code();
}
drop(claim);
match result {
Ok(ExecutionResult::Yield(_)) => {
Err(vm.new_runtime_error(format!("{} ignored GeneratorExit", gen_name(jen, vm))))
}
other => {
self.closed.store(true);
self.clear_except_code();
match other {
Err(e) if !is_gen_exit(&e, vm) => Err(e),
Ok(ExecutionResult::Return(value)) => Ok(value),
_ => Ok(vm.ctx.none()),
}
}
Err(e) if !is_gen_exit(&e, vm) => Err(e),
Ok(ExecutionResult::Return(value)) => Ok(value),
_ => Ok(vm.ctx.none()),
}
}
@@ -367,7 +358,7 @@ impl Coro {
}
pub fn frame_opt(&self) -> Option<FrameObjectRef> {
self.frame.to_owned()
self.frame.try_to_owned(Ordering::Acquire)
}
pub fn code(&self) -> PyRef<PyCode> {
@@ -494,6 +485,30 @@ pub(crate) fn get_awaitable_iter(obj: PyObjectRef, vm: &VirtualMachine) -> PyRes
Err(vm.new_type_error(format!("'{}' object can't be awaited", obj.class().name())))
}
pub(crate) fn unraisable_while_closing(
jen: &PyObject,
coro: &Coro,
e: crate::builtins::PyBaseExceptionRef,
vm: &VirtualMachine,
) {
// Explicit close() leaves the traceback to the caller frame.
// Finalize has no caller frame, so attach the generator site here.
if e.__traceback__().is_none()
&& let Some(frame) = coro.frame_opt()
{
let lasti = frame.lasti().saturating_mul(2);
let lineno = rustpython_compiler_core::OneIndexed::new(frame.f_lineno().max(1))
.unwrap_or(rustpython_compiler_core::OneIndexed::MIN);
let tb = PyTraceback::new(None, frame, lasti, lineno);
e.set_traceback_typed(Some(tb.into_ref(&vm.ctx)));
}
let msg = jen
.repr(vm)
.ok()
.map(|r| format!("Exception ignored while closing generator {r}"));
vm.run_unraisable(e, msg, vm.ctx.none());
}
/// Emit DeprecationWarning for the deprecated 3-argument throw() signature.
pub(crate) fn warn_deprecated_throw_signature(
exc_val: &OptionalArg,
+20 -9
View File
@@ -3390,13 +3390,24 @@ impl ExecutingFrame<'_> {
};
if is_gen_exit {
// gen_close_iter: close the sub-iterator
// gen_close_iter: close the sub-iterator. A failed
// lookup of close is unraisable; a failed close()
// call is raised into this generator.
let close_result = if let Some(coro) = self.builtin_coro(jen) {
coro.close(jen, vm).map(|_| ())
} else if let Some(close_meth) = vm.get_attribute_opt(jen.to_owned(), "close")? {
close_meth.call((), vm).map(|_| ())
} else {
Ok(())
match vm.get_attribute_opt(jen.to_owned(), "close") {
Ok(Some(close_meth)) => close_meth.call((), vm).map(|_| ()),
Ok(None) => Ok(()),
Err(e) => {
let msg = jen
.repr(vm)
.ok()
.map(|r| format!("Exception ignored while closing generator {r}"));
vm.run_unraisable(e, msg, vm.ctx.none());
Ok(())
}
}
};
if let Err(err) = close_result {
let idx = self.lasti().saturating_sub(1) as usize;
@@ -3409,7 +3420,7 @@ impl ExecutingFrame<'_> {
}
self.push_value(vm.ctx.none());
vm.contextualize_exception(&err);
vm.chain_stack_item(&err);
return match self.unwind_blocks(vm, UnwindReason::Raising { exception: err }) {
Ok(None) => {
self.prev_line.set(0);
@@ -3454,7 +3465,7 @@ impl ExecutingFrame<'_> {
}
self.push_value(vm.ctx.none());
vm.contextualize_exception(&err);
vm.chain_stack_item(&err);
match self.unwind_blocks(vm, UnwindReason::Raising { exception: err }) {
Ok(None) => {
self.prev_line.set(0);
@@ -3518,9 +3529,9 @@ impl ExecutingFrame<'_> {
}
};
// when raising an exception, set __context__ to the current exception
// This is done in _PyErr_SetObject
vm.contextualize_exception(&exception);
// PyErr_Restore: do not touch __context__. Chain only when this
// generator's own exc_info slot is occupied (_PyErr_ChainStackItem).
vm.chain_stack_item(&exception);
// always pushes Py_None before calling gen_send_ex with exc=1
// This is needed for exception handler to have correct stack state
+1 -1
View File
@@ -29,7 +29,7 @@ bitflags::bitflags! {
// already filled the entry. When defined, the flag allows a separate
// method, "__contains__" for example, to coexist with a defined
// slot like sq_contains.
// const COEXIST = 0x0040;
const COEXIST = 0x0040;
// if not Py_LIMITED_API
const FASTCALL = 0x0080;
+10 -2
View File
@@ -1951,7 +1951,11 @@ impl PyObject {
if let Err(e) = slot_del(zelf, vm) {
let del_method = zelf.get_class_attr(identifier!(vm, __del__)).unwrap();
vm.run_unraisable(e, None, del_method);
let msg = del_method
.repr(vm)
.ok()
.map(|r| format!("Exception ignored while calling deallocator {r}"));
vm.run_unraisable(e, msg, del_method);
}
// Undo the temporary resurrection. Always remove both
@@ -2047,7 +2051,11 @@ impl PyObject {
if let Err(e) = slot_del(self, vm)
&& let Some(del_method) = self.get_class_attr(identifier!(vm, __del__))
{
vm.run_unraisable(e, None, del_method);
let msg = del_method
.repr(vm)
.ok()
.map(|r| format!("Exception ignored while calling deallocator {r}"));
vm.run_unraisable(e, msg, del_method);
}
});
let _ = result;
+6
View File
@@ -26,6 +26,12 @@ mod errno_mod {
#[cfg(any(unix, windows, target_os = "wasi"))]
pub use rustpython_host_env::errno::errors;
#[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))]
pub mod errors {
pub const ECONNABORTED: i32 = 103;
pub const ECONNRESET: i32 = 104;
}
#[cfg(any(unix, windows, target_os = "wasi"))]
macro_rules! e {
($name:ident) => {
+87 -87
View File
@@ -1021,149 +1021,149 @@ pub static SLOT_DEFS: &[SlotDef] = &[
name: "__buffer__",
accessor: SlotAccessor::BfGetBuffer,
op: None,
doc: "Return a buffer object that exposes the underlying memory of the object.",
doc: "__buffer__($self, flags, /)\n--\n\nReturn a buffer object that exposes the underlying memory of the object.",
},
SlotDef {
name: "__release_buffer__",
accessor: SlotAccessor::BfReleaseBuffer,
op: None,
doc: "Release the buffer object that exposes the underlying memory of the object.",
doc: "__release_buffer__($self, buffer, /)\n--\n\nRelease the buffer object that exposes the underlying memory of the object.",
},
// Type slots (tp_*)
SlotDef {
name: "__init__",
accessor: SlotAccessor::TpInit,
op: None,
doc: "Initialize self. See help(type(self)) for accurate signature.",
doc: "__init__($self, /, *args, **kwargs)\n--\n\nInitialize self. See help(type(self)) for accurate signature.",
},
SlotDef {
name: "__new__",
accessor: SlotAccessor::TpNew,
op: None,
doc: "Create and return a new object. See help(type) for accurate signature.",
doc: "__new__($type, /, *args, **kwargs)\n--\n\nCreate and return a new object. See help(type) for accurate signature.",
},
SlotDef {
name: "__del__",
accessor: SlotAccessor::TpDel,
op: None,
doc: "Called when the instance is about to be destroyed.",
doc: "__del__($self, /)\n--\n\nCalled when the instance is about to be destroyed.",
},
SlotDef {
name: "__repr__",
accessor: SlotAccessor::TpRepr,
op: None,
doc: "Return repr(self).",
doc: "__repr__($self, /)\n--\n\nReturn repr(self).",
},
SlotDef {
name: "__str__",
accessor: SlotAccessor::TpStr,
op: None,
doc: "Return str(self).",
doc: "__str__($self, /)\n--\n\nReturn str(self).",
},
SlotDef {
name: "__hash__",
accessor: SlotAccessor::TpHash,
op: None,
doc: "Return hash(self).",
doc: "__hash__($self, /)\n--\n\nReturn hash(self).",
},
SlotDef {
name: "__call__",
accessor: SlotAccessor::TpCall,
op: None,
doc: "Call self as a function.",
doc: "__call__($self, /, *args, **kwargs)\n--\n\nCall self as a function.",
},
SlotDef {
name: "__iter__",
accessor: SlotAccessor::TpIter,
op: None,
doc: "Implement iter(self).",
doc: "__iter__($self, /)\n--\n\nImplement iter(self).",
},
SlotDef {
name: "__next__",
accessor: SlotAccessor::TpIternext,
op: None,
doc: "Implement next(self).",
doc: "__next__($self, /)\n--\n\nImplement next(self).",
},
// Attribute access
SlotDef {
name: "__getattribute__",
accessor: SlotAccessor::TpGetattro,
op: None,
doc: "Return getattr(self, name).",
doc: "__getattribute__($self, name, /)\n--\n\nReturn getattr(self, name).",
},
SlotDef {
name: "__getattr__",
accessor: SlotAccessor::TpGetattro,
op: None,
doc: "Implement getattr(self, name).",
doc: "__getattr__($self, name, /)\n--\n\nImplement getattr(self, name).",
},
SlotDef {
name: "__setattr__",
accessor: SlotAccessor::TpSetattro,
op: None,
doc: "Implement setattr(self, name, value).",
doc: "__setattr__($self, name, value, /)\n--\n\nImplement setattr(self, name, value).",
},
SlotDef {
name: "__delattr__",
accessor: SlotAccessor::TpSetattro,
op: Some(SlotOp::Delete),
doc: "Implement delattr(self, name).",
doc: "__delattr__($self, name, /)\n--\n\nImplement delattr(self, name).",
},
// Rich comparison - all map to TpRichcompare with different op
SlotDef {
name: "__eq__",
accessor: SlotAccessor::TpRichcompare,
op: Some(SlotOp::Eq),
doc: "Return self==value.",
doc: "__eq__($self, value, /)\n--\n\nReturn self==value.",
},
SlotDef {
name: "__ne__",
accessor: SlotAccessor::TpRichcompare,
op: Some(SlotOp::Ne),
doc: "Return self!=value.",
doc: "__ne__($self, value, /)\n--\n\nReturn self!=value.",
},
SlotDef {
name: "__lt__",
accessor: SlotAccessor::TpRichcompare,
op: Some(SlotOp::Lt),
doc: "Return self<value.",
doc: "__lt__($self, value, /)\n--\n\nReturn self<value.",
},
SlotDef {
name: "__le__",
accessor: SlotAccessor::TpRichcompare,
op: Some(SlotOp::Le),
doc: "Return self<=value.",
doc: "__le__($self, value, /)\n--\n\nReturn self<=value.",
},
SlotDef {
name: "__gt__",
accessor: SlotAccessor::TpRichcompare,
op: Some(SlotOp::Gt),
doc: "Return self>value.",
doc: "__gt__($self, value, /)\n--\n\nReturn self>value.",
},
SlotDef {
name: "__ge__",
accessor: SlotAccessor::TpRichcompare,
op: Some(SlotOp::Ge),
doc: "Return self>=value.",
doc: "__ge__($self, value, /)\n--\n\nReturn self>=value.",
},
// Descriptor protocol
SlotDef {
name: "__get__",
accessor: SlotAccessor::TpDescrGet,
op: None,
doc: "Return an attribute of instance, which is of type owner.",
doc: "__get__($self, instance, owner=None, /)\n--\n\nReturn an attribute of instance, which is of type owner.",
},
SlotDef {
name: "__set__",
accessor: SlotAccessor::TpDescrSet,
op: None,
doc: "Set an attribute of instance to value.",
doc: "__set__($self, instance, value, /)\n--\n\nSet an attribute of instance to value.",
},
SlotDef {
name: "__delete__",
accessor: SlotAccessor::TpDescrSet,
op: Some(SlotOp::Delete),
doc: "Delete an attribute of instance.",
doc: "__delete__($self, instance, /)\n--\n\nDelete an attribute of instance.",
},
// Mapping protocol (mp_*) - must come before Sequence protocol
// so that mp_subscript wins over sq_item for __getitem__
@@ -1172,383 +1172,383 @@ pub static SLOT_DEFS: &[SlotDef] = &[
name: "__len__",
accessor: SlotAccessor::MpLength,
op: None,
doc: "Return len(self).",
doc: "__len__($self, /)\n--\n\nReturn len(self).",
},
SlotDef {
name: "__getitem__",
accessor: SlotAccessor::MpSubscript,
op: None,
doc: "Return self[key].",
doc: "__getitem__($self, key, /)\n--\n\nReturn self[key].",
},
SlotDef {
name: "__setitem__",
accessor: SlotAccessor::MpAssSubscript,
op: None,
doc: "Set self[key] to value.",
doc: "__setitem__($self, key, value, /)\n--\n\nSet self[key] to value.",
},
SlotDef {
name: "__delitem__",
accessor: SlotAccessor::MpAssSubscript,
op: Some(SlotOp::Delete),
doc: "Delete self[key].",
doc: "__delitem__($self, key, /)\n--\n\nDelete self[key].",
},
// Sequence protocol (sq_*)
SlotDef {
name: "__len__",
accessor: SlotAccessor::SqLength,
op: None,
doc: "Return len(self).",
doc: "__len__($self, /)\n--\n\nReturn len(self).",
},
SlotDef {
name: "__getitem__",
accessor: SlotAccessor::SqItem,
op: None,
doc: "Return self[key].",
doc: "__getitem__($self, key, /)\n--\n\nReturn self[key].",
},
SlotDef {
name: "__setitem__",
accessor: SlotAccessor::SqAssItem,
op: None,
doc: "Set self[key] to value.",
doc: "__setitem__($self, key, value, /)\n--\n\nSet self[key] to value.",
},
SlotDef {
name: "__delitem__",
accessor: SlotAccessor::SqAssItem,
op: Some(SlotOp::Delete),
doc: "Delete self[key].",
doc: "__delitem__($self, key, /)\n--\n\nDelete self[key].",
},
SlotDef {
name: "__contains__",
accessor: SlotAccessor::SqContains,
op: None,
doc: "Return key in self.",
doc: "__contains__($self, key, /)\n--\n\nReturn key in self.",
},
// Number protocol - binary ops with left/right variants
SlotDef {
name: "__add__",
accessor: SlotAccessor::NbAdd,
op: Some(SlotOp::Left),
doc: "Return self+value.",
doc: "__add__($self, value, /)\n--\n\nReturn self+value.",
},
SlotDef {
name: "__radd__",
accessor: SlotAccessor::NbAdd,
op: Some(SlotOp::Right),
doc: "Return value+self.",
doc: "__radd__($self, value, /)\n--\n\nReturn value+self.",
},
SlotDef {
name: "__iadd__",
accessor: SlotAccessor::NbInplaceAdd,
op: None,
doc: "Implement self+=value.",
doc: "__iadd__($self, value, /)\n--\n\nImplement self+=value.",
},
SlotDef {
name: "__sub__",
accessor: SlotAccessor::NbSubtract,
op: Some(SlotOp::Left),
doc: "Return self-value.",
doc: "__sub__($self, value, /)\n--\n\nReturn self-value.",
},
SlotDef {
name: "__rsub__",
accessor: SlotAccessor::NbSubtract,
op: Some(SlotOp::Right),
doc: "Return value-self.",
doc: "__rsub__($self, value, /)\n--\n\nReturn value-self.",
},
SlotDef {
name: "__isub__",
accessor: SlotAccessor::NbInplaceSubtract,
op: None,
doc: "Implement self-=value.",
doc: "__isub__($self, value, /)\n--\n\nImplement self-=value.",
},
SlotDef {
name: "__mul__",
accessor: SlotAccessor::NbMultiply,
op: Some(SlotOp::Left),
doc: "Return self*value.",
doc: "__mul__($self, value, /)\n--\n\nReturn self*value.",
},
SlotDef {
name: "__rmul__",
accessor: SlotAccessor::NbMultiply,
op: Some(SlotOp::Right),
doc: "Return value*self.",
doc: "__rmul__($self, value, /)\n--\n\nReturn value*self.",
},
SlotDef {
name: "__imul__",
accessor: SlotAccessor::NbInplaceMultiply,
op: None,
doc: "Implement self*=value.",
doc: "__imul__($self, value, /)\n--\n\nImplement self*=value.",
},
SlotDef {
name: "__mod__",
accessor: SlotAccessor::NbRemainder,
op: Some(SlotOp::Left),
doc: "Return self%value.",
doc: "__mod__($self, value, /)\n--\n\nReturn self%value.",
},
SlotDef {
name: "__rmod__",
accessor: SlotAccessor::NbRemainder,
op: Some(SlotOp::Right),
doc: "Return value%self.",
doc: "__rmod__($self, value, /)\n--\n\nReturn value%self.",
},
SlotDef {
name: "__imod__",
accessor: SlotAccessor::NbInplaceRemainder,
op: None,
doc: "Implement self%=value.",
doc: "__imod__($self, value, /)\n--\n\nImplement self%=value.",
},
SlotDef {
name: "__divmod__",
accessor: SlotAccessor::NbDivmod,
op: Some(SlotOp::Left),
doc: "Return divmod(self, value).",
doc: "__divmod__($self, value, /)\n--\n\nReturn divmod(self, value).",
},
SlotDef {
name: "__rdivmod__",
accessor: SlotAccessor::NbDivmod,
op: Some(SlotOp::Right),
doc: "Return divmod(value, self).",
doc: "__rdivmod__($self, value, /)\n--\n\nReturn divmod(value, self).",
},
SlotDef {
name: "__pow__",
accessor: SlotAccessor::NbPower,
op: Some(SlotOp::Left),
doc: "Return pow(self, value, mod).",
doc: "__pow__($self, value, mod=None, /)\n--\n\nReturn pow(self, value, mod).",
},
SlotDef {
name: "__rpow__",
accessor: SlotAccessor::NbPower,
op: Some(SlotOp::Right),
doc: "Return pow(value, self, mod).",
doc: "__rpow__($self, value, mod=None, /)\n--\n\nReturn pow(value, self, mod).",
},
SlotDef {
name: "__ipow__",
accessor: SlotAccessor::NbInplacePower,
op: None,
doc: "Implement self**=value.",
doc: "__ipow__($self, value, /)\n--\n\nImplement self**=value.",
},
SlotDef {
name: "__lshift__",
accessor: SlotAccessor::NbLshift,
op: Some(SlotOp::Left),
doc: "Return self<<value.",
doc: "__lshift__($self, value, /)\n--\n\nReturn self<<value.",
},
SlotDef {
name: "__rlshift__",
accessor: SlotAccessor::NbLshift,
op: Some(SlotOp::Right),
doc: "Return value<<self.",
doc: "__rlshift__($self, value, /)\n--\n\nReturn value<<self.",
},
SlotDef {
name: "__ilshift__",
accessor: SlotAccessor::NbInplaceLshift,
op: None,
doc: "Implement self<<=value.",
doc: "__ilshift__($self, value, /)\n--\n\nImplement self<<=value.",
},
SlotDef {
name: "__rshift__",
accessor: SlotAccessor::NbRshift,
op: Some(SlotOp::Left),
doc: "Return self>>value.",
doc: "__rshift__($self, value, /)\n--\n\nReturn self>>value.",
},
SlotDef {
name: "__rrshift__",
accessor: SlotAccessor::NbRshift,
op: Some(SlotOp::Right),
doc: "Return value>>self.",
doc: "__rrshift__($self, value, /)\n--\n\nReturn value>>self.",
},
SlotDef {
name: "__irshift__",
accessor: SlotAccessor::NbInplaceRshift,
op: None,
doc: "Implement self>>=value.",
doc: "__irshift__($self, value, /)\n--\n\nImplement self>>=value.",
},
SlotDef {
name: "__and__",
accessor: SlotAccessor::NbAnd,
op: Some(SlotOp::Left),
doc: "Return self&value.",
doc: "__and__($self, value, /)\n--\n\nReturn self&value.",
},
SlotDef {
name: "__rand__",
accessor: SlotAccessor::NbAnd,
op: Some(SlotOp::Right),
doc: "Return value&self.",
doc: "__rand__($self, value, /)\n--\n\nReturn value&self.",
},
SlotDef {
name: "__iand__",
accessor: SlotAccessor::NbInplaceAnd,
op: None,
doc: "Implement self&=value.",
doc: "__iand__($self, value, /)\n--\n\nImplement self&=value.",
},
SlotDef {
name: "__xor__",
accessor: SlotAccessor::NbXor,
op: Some(SlotOp::Left),
doc: "Return self^value.",
doc: "__xor__($self, value, /)\n--\n\nReturn self^value.",
},
SlotDef {
name: "__rxor__",
accessor: SlotAccessor::NbXor,
op: Some(SlotOp::Right),
doc: "Return value^self.",
doc: "__rxor__($self, value, /)\n--\n\nReturn value^self.",
},
SlotDef {
name: "__ixor__",
accessor: SlotAccessor::NbInplaceXor,
op: None,
doc: "Implement self^=value.",
doc: "__ixor__($self, value, /)\n--\n\nImplement self^=value.",
},
SlotDef {
name: "__or__",
accessor: SlotAccessor::NbOr,
op: Some(SlotOp::Left),
doc: "Return self|value.",
doc: "__or__($self, value, /)\n--\n\nReturn self|value.",
},
SlotDef {
name: "__ror__",
accessor: SlotAccessor::NbOr,
op: Some(SlotOp::Right),
doc: "Return value|self.",
doc: "__ror__($self, value, /)\n--\n\nReturn value|self.",
},
SlotDef {
name: "__ior__",
accessor: SlotAccessor::NbInplaceOr,
op: None,
doc: "Implement self|=value.",
doc: "__ior__($self, value, /)\n--\n\nImplement self|=value.",
},
SlotDef {
name: "__floordiv__",
accessor: SlotAccessor::NbFloorDivide,
op: Some(SlotOp::Left),
doc: "Return self//value.",
doc: "__floordiv__($self, value, /)\n--\n\nReturn self//value.",
},
SlotDef {
name: "__rfloordiv__",
accessor: SlotAccessor::NbFloorDivide,
op: Some(SlotOp::Right),
doc: "Return value//self.",
doc: "__rfloordiv__($self, value, /)\n--\n\nReturn value//self.",
},
SlotDef {
name: "__ifloordiv__",
accessor: SlotAccessor::NbInplaceFloorDivide,
op: None,
doc: "Implement self//=value.",
doc: "__ifloordiv__($self, value, /)\n--\n\nImplement self//=value.",
},
SlotDef {
name: "__truediv__",
accessor: SlotAccessor::NbTrueDivide,
op: Some(SlotOp::Left),
doc: "Return self/value.",
doc: "__truediv__($self, value, /)\n--\n\nReturn self/value.",
},
SlotDef {
name: "__rtruediv__",
accessor: SlotAccessor::NbTrueDivide,
op: Some(SlotOp::Right),
doc: "Return value/self.",
doc: "__rtruediv__($self, value, /)\n--\n\nReturn value/self.",
},
SlotDef {
name: "__itruediv__",
accessor: SlotAccessor::NbInplaceTrueDivide,
op: None,
doc: "Implement self/=value.",
doc: "__itruediv__($self, value, /)\n--\n\nImplement self/=value.",
},
SlotDef {
name: "__matmul__",
accessor: SlotAccessor::NbMatrixMultiply,
op: Some(SlotOp::Left),
doc: "Return self@value.",
doc: "__matmul__($self, value, /)\n--\n\nReturn self@value.",
},
SlotDef {
name: "__rmatmul__",
accessor: SlotAccessor::NbMatrixMultiply,
op: Some(SlotOp::Right),
doc: "Return value@self.",
doc: "__rmatmul__($self, value, /)\n--\n\nReturn value@self.",
},
SlotDef {
name: "__imatmul__",
accessor: SlotAccessor::NbInplaceMatrixMultiply,
op: None,
doc: "Implement self@=value.",
doc: "__imatmul__($self, value, /)\n--\n\nImplement self@=value.",
},
// Number unary operations
SlotDef {
name: "__neg__",
accessor: SlotAccessor::NbNegative,
op: None,
doc: "Return -self.",
doc: "__neg__($self, /)\n--\n\nReturn -self.",
},
SlotDef {
name: "__pos__",
accessor: SlotAccessor::NbPositive,
op: None,
doc: "Return +self.",
doc: "__pos__($self, /)\n--\n\nReturn +self.",
},
SlotDef {
name: "__abs__",
accessor: SlotAccessor::NbAbsolute,
op: None,
doc: "Return abs(self).",
doc: "__abs__($self, /)\n--\n\nReturn abs(self).",
},
SlotDef {
name: "__invert__",
accessor: SlotAccessor::NbInvert,
op: None,
doc: "Return ~self.",
doc: "__invert__($self, /)\n--\n\nReturn ~self.",
},
SlotDef {
name: "__bool__",
accessor: SlotAccessor::NbBool,
op: None,
doc: "Return self != 0.",
doc: "__bool__($self, /)\n--\n\nTrue if self else False",
},
SlotDef {
name: "__int__",
accessor: SlotAccessor::NbInt,
op: None,
doc: "Return int(self).",
doc: "__int__($self, /)\n--\n\nReturn int(self).",
},
SlotDef {
name: "__float__",
accessor: SlotAccessor::NbFloat,
op: None,
doc: "Return float(self).",
doc: "__float__($self, /)\n--\n\nReturn float(self).",
},
SlotDef {
name: "__index__",
accessor: SlotAccessor::NbIndex,
op: None,
doc: "Return self converted to an integer, if self is suitable for use as an index into a list.",
doc: "__index__($self, /)\n--\n\nReturn self converted to an integer, if self is suitable for use as an index into a list.",
},
// Sequence inplace operations (also map to number slots for some types)
SlotDef {
name: "__add__",
accessor: SlotAccessor::SqConcat,
op: None,
doc: "Return self+value.",
doc: "__add__($self, value, /)\n--\n\nReturn self+value.",
},
SlotDef {
name: "__mul__",
accessor: SlotAccessor::SqRepeat,
op: None,
doc: "Return self*value.",
doc: "__mul__($self, value, /)\n--\n\nReturn self*value.",
},
SlotDef {
name: "__rmul__",
accessor: SlotAccessor::SqRepeat,
op: None,
doc: "Return value*self.",
doc: "__rmul__($self, value, /)\n--\n\nReturn value*self.",
},
SlotDef {
name: "__iadd__",
accessor: SlotAccessor::SqInplaceConcat,
op: None,
doc: "Implement self+=value.",
doc: "__iadd__($self, value, /)\n--\n\nImplement self+=value.",
},
SlotDef {
name: "__imul__",
accessor: SlotAccessor::SqInplaceRepeat,
op: None,
doc: "Implement self*=value.",
doc: "__imul__($self, value, /)\n--\n\nImplement self*=value.",
},
];
+2 -1
View File
@@ -628,13 +628,14 @@ impl Context {
getter: fn(&VirtualMachine, PyObjectRef) -> PyResult,
setter: MemberSetterFunc,
class: &'static Py<PyType>,
doc: Option<&str>,
) -> PyRef<PyMemberDescriptor> {
let member_def = PyMemberDef {
name: name.to_owned(),
kind: member_kind,
getter: MemberGetter::Getter(getter),
setter: MemberSetter::Setter(setter),
doc: None,
doc: doc.map(str::to_owned),
};
let member_descriptor = PyMemberDescriptor {
common: PyDescriptorOwned {
+9
View File
@@ -3123,6 +3123,15 @@ impl VirtualMachine {
}
}
/// `_PyErr_ChainStackItem`: if the current `exc_info` slot is occupied,
/// set that handled exception as `__context__` of `exception`. A vacant
/// current slot must not walk to an outer frame's exception.
pub(crate) fn chain_stack_item(&self, exception: &Py<PyBaseException>) {
if self.current_exception().is_some() {
self.contextualize_exception(exception);
}
}
pub(crate) fn contextualize_exception(&self, exception: &Py<PyBaseException>) {
if let Some(context_exc) = self.topmost_exception()
&& !context_exc.is(exception)
+9 -1
View File
@@ -42,6 +42,14 @@ web-sys = { workspace = true, features = [
"Response"
] }
[target.'cfg(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none")))'.dependencies]
getrandom = { workspace = true, features = ["wasm_js"] }
getrandom_02 = { package = "getrandom", version = "0.2", features = ["js"] }
rustls = { workspace = true, default-features = false, features = ["std", "tls12", "custom-provider"] }
rustls-pki-types = { version = "1", default-features = false, features = ["alloc", "std", "web"] }
rustls-rustcrypto = "0.0.2-alpha"
rustpython-host_env = { workspace = true, features = ["ssl"] }
[package.metadata.wasm-pack.profile.release]
wasm-opt = false#["-O1"]
@@ -49,4 +57,4 @@ wasm-opt = false#["-O1"]
workspace = true
[package.metadata.cargo-shear]
ignored = ["serde"]
ignored = ["serde", "getrandom", "rustls-pki-types"]
+4
View File
@@ -3,6 +3,10 @@ extern crate alloc;
pub mod browser_module;
pub mod convert;
pub mod js_module;
#[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))]
mod socket;
#[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))]
mod ssl;
pub mod vm_class;
pub mod wasm_builtins;
+457
View File
@@ -0,0 +1,457 @@
pub(crate) use _socket::module_def;
#[pymodule]
mod _socket {
use rustpython_vm::{
Py, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine,
builtins::{PyType, PyTypeRef},
common::lock::PyMutex,
function::{OptionalArg, OptionalOption},
types::{Constructor, Initializer},
};
use std::net::{Ipv4Addr, Ipv6Addr};
use std::sync::atomic::{AtomicI32, Ordering};
#[pyattr]
const AF_UNSPEC: i32 = 0;
#[pyattr]
const AF_UNIX: i32 = 1;
#[pyattr]
const AF_INET: i32 = 2;
#[pyattr]
const AF_INET6: i32 = 10;
#[pyattr]
const SOCK_STREAM: i32 = 1;
#[pyattr]
const SOCK_DGRAM: i32 = 2;
#[pyattr]
const SOCK_RAW: i32 = 3;
#[pyattr]
const SOL_SOCKET: i32 = 1;
#[pyattr]
const SO_REUSEADDR: i32 = 2;
#[pyattr]
const SO_TYPE: i32 = 3;
#[pyattr]
const SO_ERROR: i32 = 4;
#[pyattr]
const SO_BROADCAST: i32 = 6;
#[pyattr]
const SO_KEEPALIVE: i32 = 9;
#[pyattr]
const SO_RCVBUF: i32 = 8;
#[pyattr]
const SO_SNDBUF: i32 = 7;
#[pyattr]
const IPPROTO_IP: i32 = 0;
#[pyattr]
const IPPROTO_TCP: i32 = 6;
#[pyattr]
const IPPROTO_UDP: i32 = 17;
#[pyattr]
const IPPROTO_IPV6: i32 = 41;
#[pyattr]
const SOL_TCP: i32 = IPPROTO_TCP;
#[pyattr]
const SHUT_RD: i32 = 0;
#[pyattr]
const SHUT_WR: i32 = 1;
#[pyattr]
const SHUT_RDWR: i32 = 2;
#[pyattr]
const AI_PASSIVE: i32 = 1;
#[pyattr]
const AI_CANONNAME: i32 = 2;
#[pyattr]
const AI_NUMERICHOST: i32 = 4;
#[pyattr]
const AI_NUMERICSERV: i32 = 8;
#[pyattr]
const AI_ADDRCONFIG: i32 = 32;
#[pyattr]
const NI_NUMERICHOST: i32 = 1;
#[pyattr]
const NI_NUMERICSERV: i32 = 2;
#[pyattr]
const NI_NOFQDN: i32 = 4;
#[pyattr]
const NI_NAMEREQD: i32 = 8;
#[pyattr]
const NI_DGRAM: i32 = 16;
#[pyattr]
const INADDR_ANY: u32 = 0;
#[pyattr]
const INADDR_LOOPBACK: u32 = 0x7f00_0001;
#[pyattr]
const INADDR_BROADCAST: u32 = 0xffff_ffff;
#[pyattr]
const INADDR_NONE: u32 = 0xffff_ffff;
#[pyattr]
const IPPORT_RESERVED: i32 = 1024;
#[pyattr]
const IPPORT_USERRESERVED: i32 = 5000;
#[pyattr]
const TCP_NODELAY: i32 = 1;
#[pyattr(name = "has_ipv6")]
const HAS_IPV6: bool = true;
static DEFAULT_TIMEOUT: AtomicI32 = AtomicI32::new(-1);
fn unsupported(
vm: &VirtualMachine,
op: &str,
) -> PyRef<rustpython_vm::builtins::PyBaseException> {
vm.new_os_error(format!("{op} is not available"))
}
#[pyattr]
fn error(vm: &VirtualMachine) -> PyTypeRef {
vm.ctx.exceptions.os_error.to_owned()
}
#[pyattr]
fn timeout(vm: &VirtualMachine) -> PyTypeRef {
vm.ctx.exceptions.timeout_error.to_owned()
}
#[pyattr(once)]
fn herror(vm: &VirtualMachine) -> PyTypeRef {
vm.ctx.new_exception_type(
"socket",
"herror",
Some(vec![vm.ctx.exceptions.os_error.to_owned()]),
)
}
#[pyattr(once)]
fn gaierror(vm: &VirtualMachine) -> PyTypeRef {
vm.ctx.new_exception_type(
"socket",
"gaierror",
Some(vec![vm.ctx.exceptions.os_error.to_owned()]),
)
}
#[pyfunction]
const fn htonl(x: u32) -> u32 {
u32::to_be(x)
}
#[pyfunction]
const fn htons(x: u16) -> u16 {
u16::to_be(x)
}
#[pyfunction]
const fn ntohl(x: u32) -> u32 {
u32::from_be(x)
}
#[pyfunction]
const fn ntohs(x: u16) -> u16 {
u16::from_be(x)
}
#[pyfunction]
fn inet_aton(
ip: rustpython_vm::builtins::PyUtf8StrRef,
vm: &VirtualMachine,
) -> PyResult<Vec<u8>> {
ip.as_str()
.parse::<Ipv4Addr>()
.map(|addr| addr.octets().to_vec())
.map_err(|_| vm.new_os_error("illegal IP address string passed to inet_aton"))
}
#[pyfunction]
fn inet_ntoa(
packed: rustpython_vm::function::ArgBytesLike,
vm: &VirtualMachine,
) -> PyResult<String> {
let buf = packed.borrow_buf();
let octets: [u8; 4] = (&*buf)
.try_into()
.map_err(|_| vm.new_os_error("packed IP wrong length for inet_ntoa"))?;
Ok(Ipv4Addr::from(octets).to_string())
}
#[pyfunction]
fn inet_pton(
af: i32,
ip: rustpython_vm::builtins::PyUtf8StrRef,
vm: &VirtualMachine,
) -> PyResult<Vec<u8>> {
match af {
AF_INET => ip
.as_str()
.parse::<Ipv4Addr>()
.map(|addr| addr.octets().to_vec())
.map_err(|_| vm.new_os_error("illegal IP address string passed to inet_pton")),
AF_INET6 => ip
.as_str()
.parse::<Ipv6Addr>()
.map(|addr| addr.octets().to_vec())
.map_err(|_| vm.new_os_error("illegal IP address string passed to inet_pton")),
_ => Err(vm.new_os_error("Address family not supported")),
}
}
#[pyfunction]
fn inet_ntop(
af: i32,
packed: rustpython_vm::function::ArgBytesLike,
vm: &VirtualMachine,
) -> PyResult<String> {
let buf = packed.borrow_buf();
match af {
AF_INET => {
let octets: [u8; 4] = (&*buf).try_into().map_err(|_| {
vm.new_value_error("invalid length of packed IP address string")
})?;
Ok(Ipv4Addr::from(octets).to_string())
}
AF_INET6 => {
let octets: [u8; 16] = (&*buf).try_into().map_err(|_| {
vm.new_value_error("invalid length of packed IP address string")
})?;
Ok(Ipv6Addr::from(octets).to_string())
}
_ => Err(vm.new_value_error("unknown address family")),
}
}
#[pyfunction]
fn getdefaulttimeout() -> Option<f64> {
let timeout = DEFAULT_TIMEOUT.load(Ordering::Relaxed);
(timeout >= 0).then_some(f64::from(timeout))
}
#[pyfunction]
fn setdefaulttimeout(timeout: OptionalOption<f64>, vm: &VirtualMachine) -> PyResult<()> {
match timeout.flatten() {
None => DEFAULT_TIMEOUT.store(-1, Ordering::Relaxed),
Some(value) if value >= 0.0 => {
DEFAULT_TIMEOUT.store(value as i32, Ordering::Relaxed);
}
Some(_) => return Err(vm.new_value_error("Timeout value out of range")),
}
Ok(())
}
#[derive(FromArgs)]
struct SocketInitArgs {
#[pyarg(any, optional)]
family: OptionalArg<i32>,
#[pyarg(any, optional)]
r#type: OptionalArg<i32>,
#[pyarg(any, optional)]
proto: OptionalArg<i32>,
#[pyarg(any, optional)]
fileno: OptionalArg<Option<PyObjectRef>>,
}
#[pyattr(name = "socket")]
#[pyattr(name = "SocketType")]
#[pyclass(name = "socket")]
#[derive(Debug, PyPayload)]
struct PySocket {
family: AtomicI32,
kind: AtomicI32,
proto: AtomicI32,
timeout: PyMutex<Option<f64>>,
closed: PyMutex<bool>,
}
impl Default for PySocket {
fn default() -> Self {
Self {
family: AtomicI32::new(AF_INET),
kind: AtomicI32::new(SOCK_STREAM),
proto: AtomicI32::new(0),
timeout: PyMutex::new(None),
closed: PyMutex::new(false),
}
}
}
#[pyclass(with(Constructor, Initializer), flags(BASETYPE))]
impl PySocket {
fn ensure_open(&self, vm: &VirtualMachine) -> PyResult<()> {
if *self.closed.lock() {
return Err(vm.new_os_error("Bad file descriptor"));
}
Ok(())
}
#[pygetset]
fn family(&self) -> i32 {
self.family.load(Ordering::Relaxed)
}
#[pygetset]
fn r#type(&self) -> i32 {
self.kind.load(Ordering::Relaxed)
}
#[pygetset]
fn proto(&self) -> i32 {
self.proto.load(Ordering::Relaxed)
}
#[pymethod]
fn fileno(&self) -> i32 {
if *self.closed.lock() { -1 } else { 0 }
}
#[pymethod]
fn close(&self) {
*self.closed.lock() = true;
}
#[pymethod]
fn detach(&self) -> i32 {
*self.closed.lock() = true;
-1
}
#[pymethod]
fn gettimeout(&self) -> Option<f64> {
*self.timeout.lock()
}
#[pymethod]
fn settimeout(&self, timeout: OptionalOption<f64>, vm: &VirtualMachine) -> PyResult<()> {
*self.timeout.lock() = match timeout.flatten() {
None => None,
Some(value) if value >= 0.0 => Some(value),
Some(_) => return Err(vm.new_value_error("Timeout value out of range")),
};
Ok(())
}
#[pymethod]
fn setblocking(&self, blocking: bool) {
*self.timeout.lock() = if blocking { None } else { Some(0.0) };
}
#[pymethod]
fn getblocking(&self) -> bool {
!matches!(*self.timeout.lock(), Some(t) if t == 0.0)
}
#[pymethod]
fn getsockopt(&self, level: i32, optname: i32, vm: &VirtualMachine) -> PyResult<i32> {
self.ensure_open(vm)?;
if level == SOL_SOCKET && optname == SO_TYPE {
return Ok(self.kind.load(Ordering::Relaxed));
}
if level == SOL_SOCKET && optname == SO_ERROR {
return Ok(0);
}
Err(unsupported(vm, "getsockopt").into())
}
#[pymethod]
fn setsockopt(
&self,
_level: i32,
_optname: i32,
_value: OptionalArg<PyObjectRef>,
vm: &VirtualMachine,
) -> PyResult<()> {
self.ensure_open(vm)?;
Ok(())
}
#[pymethod]
fn bind(&self, _address: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
self.ensure_open(vm)?;
Err(unsupported(vm, "bind").into())
}
#[pymethod]
fn connect(&self, _address: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
self.ensure_open(vm)?;
Err(unsupported(vm, "connect").into())
}
#[pymethod]
fn listen(&self, _backlog: OptionalArg<i32>, vm: &VirtualMachine) -> PyResult<()> {
self.ensure_open(vm)?;
Err(unsupported(vm, "listen").into())
}
#[pymethod]
fn accept(&self, vm: &VirtualMachine) -> PyResult<(PyObjectRef, PyObjectRef)> {
self.ensure_open(vm)?;
Err(unsupported(vm, "accept").into())
}
#[pymethod]
fn send(
&self,
_data: rustpython_vm::function::ArgBytesLike,
vm: &VirtualMachine,
) -> PyResult<usize> {
self.ensure_open(vm)?;
Err(unsupported(vm, "send").into())
}
#[pymethod]
fn recv(&self, _bufsize: i32, vm: &VirtualMachine) -> PyResult<Vec<u8>> {
self.ensure_open(vm)?;
Err(unsupported(vm, "recv").into())
}
#[pymethod]
fn shutdown(&self, _how: i32, vm: &VirtualMachine) -> PyResult<()> {
self.ensure_open(vm)?;
Err(unsupported(vm, "shutdown").into())
}
#[pymethod]
fn getsockname(&self, vm: &VirtualMachine) -> PyResult<(String, i32)> {
self.ensure_open(vm)?;
Ok(("0.0.0.0".to_owned(), 0))
}
#[pymethod]
fn getpeername(&self, vm: &VirtualMachine) -> PyResult<(String, i32)> {
self.ensure_open(vm)?;
Err(unsupported(vm, "getpeername").into())
}
}
impl Constructor for PySocket {
type Args = ();
fn py_new(_cls: &Py<PyType>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<Self> {
Ok(Self::default())
}
}
impl Initializer for PySocket {
type Args = SocketInitArgs;
fn init(zelf: PyRef<Self>, args: Self::Args, _vm: &VirtualMachine) -> PyResult<()> {
let family = args.family.unwrap_or(AF_INET);
let kind = args.r#type.unwrap_or(SOCK_STREAM);
let proto = args.proto.unwrap_or(0);
let _ = args.fileno;
zelf.family.store(family, Ordering::Relaxed);
zelf.kind.store(kind, Ordering::Relaxed);
zelf.proto.store(proto, Ordering::Relaxed);
*zelf.closed.lock() = false;
let default = DEFAULT_TIMEOUT.load(Ordering::Relaxed);
*zelf.timeout.lock() = (default >= 0).then_some(f64::from(default));
Ok(())
}
}
}
File diff suppressed because it is too large. Load diff
+31 -1
View File
@@ -86,10 +86,20 @@ impl StoredVirtualMachine {
.add_frozen_modules(rustpython_pylib::FROZEN_STDLIB);
}
// Add wasm-specific modules
// Add wasm-specific modules. Browser `_ssl` / `_socket` live here
// rather than rustpython-stdlib: there are no BSD sockets, and rustls
// is driven through MemoryBIO / wrap_bio.
let js_def = js_module::module_def(&builder.ctx);
builder = builder.add_native_module(js_def);
#[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))]
{
install_browser_tls_provider();
let ssl_def = crate::ssl::module_def(&builder.ctx);
let socket_def = crate::socket::module_def(&builder.ctx);
builder = builder.add_native_modules(&[ssl_def, socket_def]);
}
if inject_browser_module {
let window_def = _window::module_def(&builder.ctx);
let browser_def = browser_module::module_def(&builder.ctx);
@@ -115,6 +125,26 @@ impl StoredVirtualMachine {
}
}
#[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))]
fn install_browser_tls_provider() {
use rustls::Error;
use rustpython_host_env::ssl::providers::CryptoExt;
use std::sync::Arc;
fn no_ticketer() -> Result<Arc<dyn rustls::server::ProducesTickets>, Error> {
Err(Error::General("TLS session tickets are unavailable".into()))
}
let ext = CryptoExt {
all_cipher_suites: None,
default_cipher_suites: None,
all_kx_groups: None,
any_supported_key: Some(rustls_rustcrypto::sign::any_supported_type),
ticketer: no_ticketer,
};
let _ = CryptoExt::set_provider(rustls_rustcrypto::provider(), ext);
}
// It's fine that it's thread local, since WASM doesn't even have threads yet. thread_local!
// probably gets compiled down to a normal-ish static variable, like Atomic* types do:
// https://rustwasm.github.io/2018/10/24/multithreading-rust-and-wasm.html#atomic-instructions