From 689837a2e9910bae994fefb6ce657cc1bb4cc4d3 Mon Sep 17 00:00:00 2001 From: ellainng <299958882+ellainng@users.noreply.github.com> Date: Sun, 1 Nov 2026 18:22:40 -0800 Subject: [PATCH 1/9] feat(LA-53): adopt scalar mutation failure metadata --- docs/interpreter-exceptions.md | 11 + runtime/class-values.mjs | 2 + runtime/exceptions.test.mjs | 10 + src/pocket_python/_interpreter_lock.py | 4 +- tests/fixtures/interpreter/exceptions.json | 393 ++++++++++++++++++++- 5 files changed, 417 insertions(+), 3 deletions(-) diff --git a/docs/interpreter-exceptions.md b/docs/interpreter-exceptions.md index f182be7..0065b2c 100644 --- a/docs/interpreter-exceptions.md +++ b/docs/interpreter-exceptions.md @@ -485,3 +485,14 @@ Groups/except*, tracebacks, note export, original-instance shared-key deletion, other native attribute/import/parser metadata and the declared qualifications keep #124 and A2 open. Wider representation/formatting, container APIs and library/package/candidate gates retain their existing owners. + +LA-53 adopts failed attribute writes and deletions on None, NotImplemented, +bool, int, float and complex. Missing names retain the original receiver and +name, including string-subtype keys, independently of args. Existing native +attributes leave both native fields absent. Native object mutation wrappers +use the same selection and do not invoke key formatting or hashing callbacks. +Scalar subclasses keep their ordinary instance dictionaries. The 65 pinned +reference cases cover both mutation directions, native wrappers, subtype keys, +evaluation order and successful subclass controls. This follows the pinned +[generic mutation implementation](https://github.com/python/cpython/blob/v3.14.7/Objects/object.c#L1820-L1917). +The existing core attribute-mutation boundary remains explicit. diff --git a/runtime/class-values.mjs b/runtime/class-values.mjs index 3eda644..163d84d 100644 --- a/runtime/class-values.mjs +++ b/runtime/class-values.mjs @@ -1096,6 +1096,8 @@ export class ClassRuntime { return this.functionEntry(receiver, name, remove ? "delete" : "set", value, key); } if (name === "__class__" && !this.protocolObject(receiver) && !(receiver instanceof ClassValue)) return {kind: "value", value: this.reassignClass(receiver, value, remove)}; + if (this.numericDirectoryNames(receiver) !== null) + this.fixedMutationFailure(receiver, name, key, "Scalar attributes are read-only"); if (receiver instanceof GeneratorValue && ["__name__", "__qualname__"].includes(name)) { if (remove || !this.values.isString(value)) this.fail("TypeError", name + " must be a string"); return {kind: "value", value: this.functionMetadata(receiver, name === "__name__" ? "identityName" : "identityQualname", value)}; diff --git a/runtime/exceptions.test.mjs b/runtime/exceptions.test.mjs index 5d5f81f..942d2a9 100644 --- a/runtime/exceptions.test.mjs +++ b/runtime/exceptions.test.mjs @@ -46,6 +46,16 @@ async function lifetime(source, expected, helpers = []) { } } finally { registry.close(); } } +test('scalar mutation errors retain cyclic keys and temporary numeric receivers', async () => { + await lifetime('class Key(str): pass\nkey=Key("missing")\ntry: setattr(1.25,key,[])\nexcept AttributeError as error: e=error\nkey.error=e\ne.args=()\nkey=None\nprint("entered")\nprint(e.name.error is e,e.obj==1.25)\n', 'entered\nTrue True\n'); +}); + +test('retained scalar mutation graphs share the allocation limit', async () => { + const result = await runSourceRequest({modules: [unit('held=[]\nprint("ready")\nwhile True:\n try: (1.25).missing=[]\n except AttributeError as e: held.append(e)\n')], + compileOptions: {languageProfile: corpus.profile}, options: {maxWork: 20000000}, memoryLimits: {maxBytes: 48000}}); + assert.equal(result.stdout, 'ready\n'); assert.equal(result.error?.type, 'MemoryError'); closed(result.closed); +}); + test('module mutation errors retain cyclic keys and the module after args replacement', async () => { await lifetime('import helper\nclass Key(str): pass\nkey=Key("missing")\ntry: delattr(helper,key)\nexcept AttributeError as error: e=error\nkey.error=e\ne.args=()\nhelper=None\nkey=None\nprint("entered")\nprint(e.name.error is e,e.obj.marker)\n', 'entered\nTrue 7\n', [{name: 'helper', source: 'marker=7\n'}]); diff --git a/src/pocket_python/_interpreter_lock.py b/src/pocket_python/_interpreter_lock.py index 0770776..7ee5e5c 100644 --- a/src/pocket_python/_interpreter_lock.py +++ b/src/pocket_python/_interpreter_lock.py @@ -1,11 +1,11 @@ """Known source-console assets, canonical LF bytes; refresh with scripts/lock_interpreter.py.""" -ENGINE_FINGERPRINT = 'b8f728514dd77555b56aa4c527827615ade655ac637f6949c35c868158b522f3' +ENGINE_FINGERPRINT = '1b71446f9a2c039dde50b70d9302fbd559b9c1f4f05d537dc79b009ddd25c7bd' ENGINE_FILES = ( ('browser-protocol.mjs', 10966, '85f50d640eaa5324cf62e75f5dc6050186bf167201208d26e84a49efd74bf499'), ('bytecode.mjs', 6836, 'e76fabeea879bc1da8016283f7e74df5974479666b100199c2d3c31a53b8ecf4'), ('class-policy.mjs', 5539, 'eb4fb02aba4f6c4fb237c4fd0bf5633d2b7e3d0de176d6cf8581690d8b1eceb4'), - ('class-values.mjs', 159601, '0032b4b8226d66e0bc99aaa6ed52143703464d5984fcba739daf1f5cd0395137'), + ('class-values.mjs', 159745, '9854266a0ab26846ceca3d4cdd9f6ba398f8501ab67cbd4161141ae6317d0445'), ('dynamic-evaluation.mjs', 6820, '44ad82dae7fa185c524e163440a96db953e800e96baa372249dd5e9d613b9eca'), ('dynamic-imports.mjs', 9307, '9996c3f3f7e82cd440cf16244176d37e9456c8dd9bbac2e5ac675cf22497c951'), ('dynamic-source.mjs', 5723, '9040a40e8906723f01f496f30aafd329816f823ecad158dd29db8e21b844bf2c'), diff --git a/tests/fixtures/interpreter/exceptions.json b/tests/fixtures/interpreter/exceptions.json index d4c0138..c24ea50 100644 --- a/tests/fixtures/interpreter/exceptions.json +++ b/tests/fixtures/interpreter/exceptions.json @@ -52,7 +52,8 @@ "LA49", "LA50", "LA51", - "LA52" + "LA52", + "LA53" ], "cases": [ { @@ -9364,6 +9365,396 @@ "helper" ], "stdout": "missing True True 7\n" + }, + { + "id": "la53-none-write-missing-builtin", + "source": "obj=None\nkey=\"missing\"\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA53", + "stdout": "True True False True\n" + }, + { + "id": "la53-none-write-missing-native", + "source": "obj=None\nkey=\"missing\"\ntry: object.__setattr__(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA53", + "stdout": "True True False True\n" + }, + { + "id": "la53-none-write-__doc__-builtin", + "source": "obj=None\nkey=\"__doc__\"\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA53", + "stdout": "False True True True\n" + }, + { + "id": "la53-none-write-__doc__-native", + "source": "obj=None\nkey=\"__doc__\"\ntry: object.__setattr__(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA53", + "stdout": "False True True True\n" + }, + { + "id": "la53-none-write-subtype-key", + "source": "class Key(str):\n def __hash__(self): raise ValueError(\"unexpected hash\")\n def __str__(self): raise ValueError(\"unexpected string\")\nobj=None\nkey=Key(\"missing\")\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj)\n", + "stage": "LA53", + "stdout": "True True\n" + }, + { + "id": "la53-none-delete-missing-builtin", + "source": "obj=None\nkey=\"missing\"\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA53", + "stdout": "True True False True\n" + }, + { + "id": "la53-none-delete-missing-native", + "source": "obj=None\nkey=\"missing\"\ntry: object.__delattr__(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA53", + "stdout": "True True False True\n" + }, + { + "id": "la53-none-delete-__doc__-builtin", + "source": "obj=None\nkey=\"__doc__\"\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA53", + "stdout": "False True True True\n" + }, + { + "id": "la53-none-delete-__doc__-native", + "source": "obj=None\nkey=\"__doc__\"\ntry: object.__delattr__(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA53", + "stdout": "False True True True\n" + }, + { + "id": "la53-none-delete-subtype-key", + "source": "class Key(str):\n def __hash__(self): raise ValueError(\"unexpected hash\")\n def __str__(self): raise ValueError(\"unexpected string\")\nobj=None\nkey=Key(\"missing\")\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj)\n", + "stage": "LA53", + "stdout": "True True\n" + }, + { + "id": "la53-notimplemented-write-missing-builtin", + "source": "obj=NotImplemented\nkey=\"missing\"\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA53", + "stdout": "True True False False\n" + }, + { + "id": "la53-notimplemented-write-missing-native", + "source": "obj=NotImplemented\nkey=\"missing\"\ntry: object.__setattr__(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA53", + "stdout": "True True False False\n" + }, + { + "id": "la53-notimplemented-write-__doc__-builtin", + "source": "obj=NotImplemented\nkey=\"__doc__\"\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA53", + "stdout": "False False True True\n" + }, + { + "id": "la53-notimplemented-write-__doc__-native", + "source": "obj=NotImplemented\nkey=\"__doc__\"\ntry: object.__setattr__(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA53", + "stdout": "False False True True\n" + }, + { + "id": "la53-notimplemented-write-subtype-key", + "source": "class Key(str):\n def __hash__(self): raise ValueError(\"unexpected hash\")\n def __str__(self): raise ValueError(\"unexpected string\")\nobj=NotImplemented\nkey=Key(\"missing\")\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj)\n", + "stage": "LA53", + "stdout": "True True\n" + }, + { + "id": "la53-notimplemented-delete-missing-builtin", + "source": "obj=NotImplemented\nkey=\"missing\"\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA53", + "stdout": "True True False False\n" + }, + { + "id": "la53-notimplemented-delete-missing-native", + "source": "obj=NotImplemented\nkey=\"missing\"\ntry: object.__delattr__(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA53", + "stdout": "True True False False\n" + }, + { + "id": "la53-notimplemented-delete-__doc__-builtin", + "source": "obj=NotImplemented\nkey=\"__doc__\"\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA53", + "stdout": "False False True True\n" + }, + { + "id": "la53-notimplemented-delete-__doc__-native", + "source": "obj=NotImplemented\nkey=\"__doc__\"\ntry: object.__delattr__(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA53", + "stdout": "False False True True\n" + }, + { + "id": "la53-notimplemented-delete-subtype-key", + "source": "class Key(str):\n def __hash__(self): raise ValueError(\"unexpected hash\")\n def __str__(self): raise ValueError(\"unexpected string\")\nobj=NotImplemented\nkey=Key(\"missing\")\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj)\n", + "stage": "LA53", + "stdout": "True True\n" + }, + { + "id": "la53-bool-write-missing-builtin", + "source": "obj=True\nkey=\"missing\"\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA53", + "stdout": "True True False False\n" + }, + { + "id": "la53-bool-write-missing-native", + "source": "obj=True\nkey=\"missing\"\ntry: object.__setattr__(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA53", + "stdout": "True True False False\n" + }, + { + "id": "la53-bool-write-real-builtin", + "source": "obj=True\nkey=\"real\"\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA53", + "stdout": "False False True True\n" + }, + { + "id": "la53-bool-write-real-native", + "source": "obj=True\nkey=\"real\"\ntry: object.__setattr__(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA53", + "stdout": "False False True True\n" + }, + { + "id": "la53-bool-write-subtype-key", + "source": "class Key(str):\n def __hash__(self): raise ValueError(\"unexpected hash\")\n def __str__(self): raise ValueError(\"unexpected string\")\nobj=True\nkey=Key(\"missing\")\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj)\n", + "stage": "LA53", + "stdout": "True True\n" + }, + { + "id": "la53-bool-delete-missing-builtin", + "source": "obj=True\nkey=\"missing\"\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA53", + "stdout": "True True False False\n" + }, + { + "id": "la53-bool-delete-missing-native", + "source": "obj=True\nkey=\"missing\"\ntry: object.__delattr__(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA53", + "stdout": "True True False False\n" + }, + { + "id": "la53-bool-delete-real-builtin", + "source": "obj=True\nkey=\"real\"\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA53", + "stdout": "False False True True\n" + }, + { + "id": "la53-bool-delete-real-native", + "source": "obj=True\nkey=\"real\"\ntry: object.__delattr__(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA53", + "stdout": "False False True True\n" + }, + { + "id": "la53-bool-delete-subtype-key", + "source": "class Key(str):\n def __hash__(self): raise ValueError(\"unexpected hash\")\n def __str__(self): raise ValueError(\"unexpected string\")\nobj=True\nkey=Key(\"missing\")\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj)\n", + "stage": "LA53", + "stdout": "True True\n" + }, + { + "id": "la53-int-write-missing-builtin", + "source": "obj=12345678901234567890\nkey=\"missing\"\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA53", + "stdout": "True True False False\n" + }, + { + "id": "la53-int-write-missing-native", + "source": "obj=12345678901234567890\nkey=\"missing\"\ntry: object.__setattr__(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA53", + "stdout": "True True False False\n" + }, + { + "id": "la53-int-write-numerator-builtin", + "source": "obj=12345678901234567890\nkey=\"numerator\"\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA53", + "stdout": "False False True True\n" + }, + { + "id": "la53-int-write-numerator-native", + "source": "obj=12345678901234567890\nkey=\"numerator\"\ntry: object.__setattr__(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA53", + "stdout": "False False True True\n" + }, + { + "id": "la53-int-write-subtype-key", + "source": "class Key(str):\n def __hash__(self): raise ValueError(\"unexpected hash\")\n def __str__(self): raise ValueError(\"unexpected string\")\nobj=12345678901234567890\nkey=Key(\"missing\")\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj)\n", + "stage": "LA53", + "stdout": "True True\n" + }, + { + "id": "la53-int-delete-missing-builtin", + "source": "obj=12345678901234567890\nkey=\"missing\"\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA53", + "stdout": "True True False False\n" + }, + { + "id": "la53-int-delete-missing-native", + "source": "obj=12345678901234567890\nkey=\"missing\"\ntry: object.__delattr__(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA53", + "stdout": "True True False False\n" + }, + { + "id": "la53-int-delete-numerator-builtin", + "source": "obj=12345678901234567890\nkey=\"numerator\"\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA53", + "stdout": "False False True True\n" + }, + { + "id": "la53-int-delete-numerator-native", + "source": "obj=12345678901234567890\nkey=\"numerator\"\ntry: object.__delattr__(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA53", + "stdout": "False False True True\n" + }, + { + "id": "la53-int-delete-subtype-key", + "source": "class Key(str):\n def __hash__(self): raise ValueError(\"unexpected hash\")\n def __str__(self): raise ValueError(\"unexpected string\")\nobj=12345678901234567890\nkey=Key(\"missing\")\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj)\n", + "stage": "LA53", + "stdout": "True True\n" + }, + { + "id": "la53-float-write-missing-builtin", + "source": "obj=1.25\nkey=\"missing\"\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA53", + "stdout": "True True False False\n" + }, + { + "id": "la53-float-write-missing-native", + "source": "obj=1.25\nkey=\"missing\"\ntry: object.__setattr__(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA53", + "stdout": "True True False False\n" + }, + { + "id": "la53-float-write-real-builtin", + "source": "obj=1.25\nkey=\"real\"\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA53", + "stdout": "False False True True\n" + }, + { + "id": "la53-float-write-real-native", + "source": "obj=1.25\nkey=\"real\"\ntry: object.__setattr__(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA53", + "stdout": "False False True True\n" + }, + { + "id": "la53-float-write-subtype-key", + "source": "class Key(str):\n def __hash__(self): raise ValueError(\"unexpected hash\")\n def __str__(self): raise ValueError(\"unexpected string\")\nobj=1.25\nkey=Key(\"missing\")\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj)\n", + "stage": "LA53", + "stdout": "True True\n" + }, + { + "id": "la53-float-delete-missing-builtin", + "source": "obj=1.25\nkey=\"missing\"\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA53", + "stdout": "True True False False\n" + }, + { + "id": "la53-float-delete-missing-native", + "source": "obj=1.25\nkey=\"missing\"\ntry: object.__delattr__(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA53", + "stdout": "True True False False\n" + }, + { + "id": "la53-float-delete-real-builtin", + "source": "obj=1.25\nkey=\"real\"\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA53", + "stdout": "False False True True\n" + }, + { + "id": "la53-float-delete-real-native", + "source": "obj=1.25\nkey=\"real\"\ntry: object.__delattr__(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA53", + "stdout": "False False True True\n" + }, + { + "id": "la53-float-delete-subtype-key", + "source": "class Key(str):\n def __hash__(self): raise ValueError(\"unexpected hash\")\n def __str__(self): raise ValueError(\"unexpected string\")\nobj=1.25\nkey=Key(\"missing\")\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj)\n", + "stage": "LA53", + "stdout": "True True\n" + }, + { + "id": "la53-complex-write-missing-builtin", + "source": "obj=2+3j\nkey=\"missing\"\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA53", + "stdout": "True True False False\n" + }, + { + "id": "la53-complex-write-missing-native", + "source": "obj=2+3j\nkey=\"missing\"\ntry: object.__setattr__(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA53", + "stdout": "True True False False\n" + }, + { + "id": "la53-complex-write-imag-builtin", + "source": "obj=2+3j\nkey=\"imag\"\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA53", + "stdout": "False False True True\n" + }, + { + "id": "la53-complex-write-imag-native", + "source": "obj=2+3j\nkey=\"imag\"\ntry: object.__setattr__(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA53", + "stdout": "False False True True\n" + }, + { + "id": "la53-complex-write-subtype-key", + "source": "class Key(str):\n def __hash__(self): raise ValueError(\"unexpected hash\")\n def __str__(self): raise ValueError(\"unexpected string\")\nobj=2+3j\nkey=Key(\"missing\")\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj)\n", + "stage": "LA53", + "stdout": "True True\n" + }, + { + "id": "la53-complex-delete-missing-builtin", + "source": "obj=2+3j\nkey=\"missing\"\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA53", + "stdout": "True True False False\n" + }, + { + "id": "la53-complex-delete-missing-native", + "source": "obj=2+3j\nkey=\"missing\"\ntry: object.__delattr__(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA53", + "stdout": "True True False False\n" + }, + { + "id": "la53-complex-delete-imag-builtin", + "source": "obj=2+3j\nkey=\"imag\"\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA53", + "stdout": "False False True True\n" + }, + { + "id": "la53-complex-delete-imag-native", + "source": "obj=2+3j\nkey=\"imag\"\ntry: object.__delattr__(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA53", + "stdout": "False False True True\n" + }, + { + "id": "la53-complex-delete-subtype-key", + "source": "class Key(str):\n def __hash__(self): raise ValueError(\"unexpected hash\")\n def __str__(self): raise ValueError(\"unexpected string\")\nobj=2+3j\nkey=Key(\"missing\")\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj)\n", + "stage": "LA53", + "stdout": "True True\n" + }, + { + "id": "la53-int-subclass-storage-control", + "source": "class A(int): pass\na=A(7)\na.field=[7]\nprint(a.field[0])\ndel a.field\nprint(hasattr(a,\"field\"))\n", + "stage": "LA53", + "stdout": "7\nFalse\n" + }, + { + "id": "la53-float-subclass-storage-control", + "source": "class A(float): pass\na=A(1.25)\na.field=[7]\nprint(a.field[0])\ndel a.field\nprint(hasattr(a,\"field\"))\n", + "stage": "LA53", + "stdout": "7\nFalse\n" + }, + { + "id": "la53-complex-subclass-storage-control", + "source": "class A(complex): pass\na=A(2+3j)\na.field=[7]\nprint(a.field[0])\ndel a.field\nprint(hasattr(a,\"field\"))\n", + "stage": "LA53", + "stdout": "7\nFalse\n" + }, + { + "id": "la53-evaluation-order-write", + "source": "def obj():\n print(\"receiver\")\n return 7\ndef key():\n print(\"key\")\n return \"missing\"\ndef value():\n print(\"value\")\n return []\ntry: setattr(obj(),key(),value())\nexcept AttributeError as e: print(e.name,e.obj)\n", + "stage": "LA53", + "stdout": "receiver\nkey\nvalue\nmissing 7\n" + }, + { + "id": "la53-evaluation-order-delete", + "source": "def obj():\n print(\"receiver\")\n return 7\ndef key():\n print(\"key\")\n return \"missing\"\ndef value():\n print(\"value\")\n return []\ntry: delattr(obj(),key())\nexcept AttributeError as e: print(e.name,e.obj)\n", + "stage": "LA53", + "stdout": "receiver\nkey\nmissing 7\n" } ], "reference_boundaries": [ From 207adc27a4733430985bff5e62529d6fdbb05bfb Mon Sep 17 00:00:00 2001 From: ellainng <299958882+ellainng@users.noreply.github.com> Date: Sun, 1 Nov 2026 18:25:27 -0800 Subject: [PATCH 2/9] feat(LA-54): adopt container mutation failure metadata --- docs/interpreter-exceptions.md | 9 + runtime/class-values.mjs | 4 + runtime/exceptions.test.mjs | 14 + src/pocket_python/_interpreter_lock.py | 4 +- tests/fixtures/interpreter/exceptions.json | 777 ++++++++++++++++++++- 5 files changed, 805 insertions(+), 3 deletions(-) diff --git a/docs/interpreter-exceptions.md b/docs/interpreter-exceptions.md index 0065b2c..c3fe033 100644 --- a/docs/interpreter-exceptions.md +++ b/docs/interpreter-exceptions.md @@ -496,3 +496,12 @@ reference cases cover both mutation directions, native wrappers, subtype keys, evaluation order and successful subclass controls. This follows the pinned [generic mutation implementation](https://github.com/python/cpython/blob/v3.14.7/Objects/object.c#L1820-L1917). The existing core attribute-mutation boundary remains explicit. + +LA-54 applies the same selection to str, list, tuple, dict, range, slice, set, +frozenset, bytes, bytearray and memoryview. Missing names preserve the original +key and receiver; existing native attributes and direct read-only storage +descriptors leave both fields absent. Released memoryviews still select the +attribute failure without reading their released storage. Native object +wrappers, temporary buffers and detached owners follow the same ownership +rules. The 129 reference cases include writable subclass-dictionary controls; +broader constructors, view APIs and codecs keep their existing gates. diff --git a/runtime/class-values.mjs b/runtime/class-values.mjs index 163d84d..62dc05b 100644 --- a/runtime/class-values.mjs +++ b/runtime/class-values.mjs @@ -1098,6 +1098,8 @@ export class ClassRuntime { if (name === "__class__" && !this.protocolObject(receiver) && !(receiver instanceof ClassValue)) return {kind: "value", value: this.reassignClass(receiver, value, remove)}; if (this.numericDirectoryNames(receiver) !== null) this.fixedMutationFailure(receiver, name, key, "Scalar attributes are read-only"); + if (this.containerDirectoryNames(receiver) !== null || this.values.isRange(receiver) || this.values.isSlice(receiver)) + this.fixedMutationFailure(receiver, name, key, "Container attributes are read-only"); if (receiver instanceof GeneratorValue && ["__name__", "__qualname__"].includes(name)) { if (remove || !this.values.isString(value)) this.fail("TypeError", name + " must be a string"); return {kind: "value", value: this.functionMetadata(receiver, name === "__name__" ? "identityName" : "identityQualname", value)}; @@ -1420,6 +1422,8 @@ export class ClassRuntime { if (receiver.owner !== undefined) this.fail("UnsupportedOperationError", "Dictionary subclass view mappings require the #116 proxy contract", "UNSUPPORTED"); return {kind: "value", value: this.make(ClassNamespaceValue, receiver.dictionary)}; } + if (operation !== "get" && name !== "__class__" && (this.containerDirectoryNames(this.payload(receiver)) !== null || this.values.isRange(receiver) || this.values.isSlice(receiver))) + this.attributeMutationFailure(receiver, name, null, false, "Container storage is read-only"); const fieldOwner = this.nativeFieldOwner(name); if (fieldOwner && !(receiver instanceof FunctionValue && ["O72", "O73"].includes(fieldOwner) || receiver instanceof Cell && fieldOwner === "O73" || (receiver instanceof FrameValue || receiver instanceof GeneratorValue) && fieldOwner === "O74")) this.fail("UnsupportedOperationError", "Native field " + descriptor.name + " is owned by " + fieldOwner + ".", "UNSUPPORTED"); if (operation !== "get" && receiver instanceof MethodWrapperValue && ["__func__", "__wrapped__"].includes(name)) diff --git a/runtime/exceptions.test.mjs b/runtime/exceptions.test.mjs index 942d2a9..b64f237 100644 --- a/runtime/exceptions.test.mjs +++ b/runtime/exceptions.test.mjs @@ -56,6 +56,20 @@ test('retained scalar mutation graphs share the allocation limit', async () => { assert.equal(result.stdout, 'ready\n'); assert.equal(result.error?.type, 'MemoryError'); closed(result.closed); }); +test('container mutation errors retain detached receivers and cyclic subtype keys', async () => { + await lifetime('class Key(str): pass\nkey=Key("missing")\ntry: setattr([7],key,[])\nexcept AttributeError as error: e=error\nkey.error=e\ne.obj.append(e)\ne.args=()\nkey=None\nprint("entered")\nprint(e.name.error is e,e.obj[0],e.obj[1] is e)\n', 'entered\nTrue 7 True\n'); +}); + +test('buffer mutation errors retain temporary views and original bytearray owners', async () => { + await lifetime('class Key(str): pass\nkey=Key("missing")\ntry: delattr(memoryview(bytearray(b"abc")),key)\nexcept AttributeError as error: e=error\nkey.error=e\ne.args=()\nkey=None\nprint("entered")\nprint(e.name.error is e,e.obj[0])\ne.obj.obj[0]=90\nprint(e.obj[0])\n', 'entered\nTrue 97\n90\n'); +}); + +test('retained buffer mutation graphs share the allocation limit', async () => { + const result = await runSourceRequest({modules: [unit('held=[]\nprint("ready")\nwhile True:\n try: memoryview(bytearray(b"abc")).missing=7\n except AttributeError as e: held.append(e)\n')], + compileOptions: {languageProfile: corpus.profile}, options: {maxWork: 20000000}, memoryLimits: {maxBytes: 48000}}); + assert.equal(result.stdout, 'ready\n'); assert.equal(result.error?.type, 'MemoryError'); closed(result.closed); +}); + test('module mutation errors retain cyclic keys and the module after args replacement', async () => { await lifetime('import helper\nclass Key(str): pass\nkey=Key("missing")\ntry: delattr(helper,key)\nexcept AttributeError as error: e=error\nkey.error=e\ne.args=()\nhelper=None\nkey=None\nprint("entered")\nprint(e.name.error is e,e.obj.marker)\n', 'entered\nTrue 7\n', [{name: 'helper', source: 'marker=7\n'}]); diff --git a/src/pocket_python/_interpreter_lock.py b/src/pocket_python/_interpreter_lock.py index 7ee5e5c..9f0fe2b 100644 --- a/src/pocket_python/_interpreter_lock.py +++ b/src/pocket_python/_interpreter_lock.py @@ -1,11 +1,11 @@ """Known source-console assets, canonical LF bytes; refresh with scripts/lock_interpreter.py.""" -ENGINE_FINGERPRINT = '1b71446f9a2c039dde50b70d9302fbd559b9c1f4f05d537dc79b009ddd25c7bd' +ENGINE_FINGERPRINT = '2c659a775b219c6730edfc076c78bfb4c47b365151471c3835321a602bddf526' ENGINE_FILES = ( ('browser-protocol.mjs', 10966, '85f50d640eaa5324cf62e75f5dc6050186bf167201208d26e84a49efd74bf499'), ('bytecode.mjs', 6836, 'e76fabeea879bc1da8016283f7e74df5974479666b100199c2d3c31a53b8ecf4'), ('class-policy.mjs', 5539, 'eb4fb02aba4f6c4fb237c4fd0bf5633d2b7e3d0de176d6cf8581690d8b1eceb4'), - ('class-values.mjs', 159745, '9854266a0ab26846ceca3d4cdd9f6ba398f8501ab67cbd4161141ae6317d0445'), + ('class-values.mjs', 160246, '1c689847de2650caa32e1e667cca2b610caf9231fa1e79b65fb976077abda987'), ('dynamic-evaluation.mjs', 6820, '44ad82dae7fa185c524e163440a96db953e800e96baa372249dd5e9d613b9eca'), ('dynamic-imports.mjs', 9307, '9996c3f3f7e82cd440cf16244176d37e9456c8dd9bbac2e5ac675cf22497c951'), ('dynamic-source.mjs', 5723, '9040a40e8906723f01f496f30aafd329816f823ecad158dd29db8e21b844bf2c'), diff --git a/tests/fixtures/interpreter/exceptions.json b/tests/fixtures/interpreter/exceptions.json index c24ea50..37c3a4b 100644 --- a/tests/fixtures/interpreter/exceptions.json +++ b/tests/fixtures/interpreter/exceptions.json @@ -53,7 +53,8 @@ "LA50", "LA51", "LA52", - "LA53" + "LA53", + "LA54" ], "cases": [ { @@ -9755,6 +9756,780 @@ "source": "def obj():\n print(\"receiver\")\n return 7\ndef key():\n print(\"key\")\n return \"missing\"\ndef value():\n print(\"value\")\n return []\ntry: delattr(obj(),key())\nexcept AttributeError as e: print(e.name,e.obj)\n", "stage": "LA53", "stdout": "receiver\nkey\nmissing 7\n" + }, + { + "id": "la54-str-write-missing-builtin", + "source": "obj=\"text\"\nkey=\"missing\"\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54", + "stdout": "True True False False\n" + }, + { + "id": "la54-str-write-missing-native", + "source": "obj=\"text\"\nkey=\"missing\"\ntry: object.__setattr__(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54", + "stdout": "True True False False\n" + }, + { + "id": "la54-str-write-upper-builtin", + "source": "obj=\"text\"\nkey=\"upper\"\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54", + "stdout": "False False True True\n" + }, + { + "id": "la54-str-write-upper-native", + "source": "obj=\"text\"\nkey=\"upper\"\ntry: object.__setattr__(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54", + "stdout": "False False True True\n" + }, + { + "id": "la54-str-write-subtype-key", + "source": "class Key(str):\n def __hash__(self): raise ValueError(\"unexpected hash\")\n def __str__(self): raise ValueError(\"unexpected string\")\nobj=\"text\"\nkey=Key(\"missing\")\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj)\n", + "stage": "LA54", + "stdout": "True True\n" + }, + { + "id": "la54-str-delete-missing-builtin", + "source": "obj=\"text\"\nkey=\"missing\"\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54", + "stdout": "True True False False\n" + }, + { + "id": "la54-str-delete-missing-native", + "source": "obj=\"text\"\nkey=\"missing\"\ntry: object.__delattr__(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54", + "stdout": "True True False False\n" + }, + { + "id": "la54-str-delete-upper-builtin", + "source": "obj=\"text\"\nkey=\"upper\"\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54", + "stdout": "False False True True\n" + }, + { + "id": "la54-str-delete-upper-native", + "source": "obj=\"text\"\nkey=\"upper\"\ntry: object.__delattr__(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54", + "stdout": "False False True True\n" + }, + { + "id": "la54-str-delete-subtype-key", + "source": "class Key(str):\n def __hash__(self): raise ValueError(\"unexpected hash\")\n def __str__(self): raise ValueError(\"unexpected string\")\nobj=\"text\"\nkey=Key(\"missing\")\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj)\n", + "stage": "LA54", + "stdout": "True True\n" + }, + { + "id": "la54-list-write-missing-builtin", + "source": "obj=[7]\nkey=\"missing\"\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54", + "stdout": "True True False False\n" + }, + { + "id": "la54-list-write-missing-native", + "source": "obj=[7]\nkey=\"missing\"\ntry: object.__setattr__(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54", + "stdout": "True True False False\n" + }, + { + "id": "la54-list-write-append-builtin", + "source": "obj=[7]\nkey=\"append\"\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54", + "stdout": "False False True True\n" + }, + { + "id": "la54-list-write-append-native", + "source": "obj=[7]\nkey=\"append\"\ntry: object.__setattr__(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54", + "stdout": "False False True True\n" + }, + { + "id": "la54-list-write-subtype-key", + "source": "class Key(str):\n def __hash__(self): raise ValueError(\"unexpected hash\")\n def __str__(self): raise ValueError(\"unexpected string\")\nobj=[7]\nkey=Key(\"missing\")\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj)\n", + "stage": "LA54", + "stdout": "True True\n" + }, + { + "id": "la54-list-delete-missing-builtin", + "source": "obj=[7]\nkey=\"missing\"\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54", + "stdout": "True True False False\n" + }, + { + "id": "la54-list-delete-missing-native", + "source": "obj=[7]\nkey=\"missing\"\ntry: object.__delattr__(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54", + "stdout": "True True False False\n" + }, + { + "id": "la54-list-delete-append-builtin", + "source": "obj=[7]\nkey=\"append\"\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54", + "stdout": "False False True True\n" + }, + { + "id": "la54-list-delete-append-native", + "source": "obj=[7]\nkey=\"append\"\ntry: object.__delattr__(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54", + "stdout": "False False True True\n" + }, + { + "id": "la54-list-delete-subtype-key", + "source": "class Key(str):\n def __hash__(self): raise ValueError(\"unexpected hash\")\n def __str__(self): raise ValueError(\"unexpected string\")\nobj=[7]\nkey=Key(\"missing\")\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj)\n", + "stage": "LA54", + "stdout": "True True\n" + }, + { + "id": "la54-tuple-write-missing-builtin", + "source": "obj=(7,)\nkey=\"missing\"\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54", + "stdout": "True True False False\n" + }, + { + "id": "la54-tuple-write-missing-native", + "source": "obj=(7,)\nkey=\"missing\"\ntry: object.__setattr__(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54", + "stdout": "True True False False\n" + }, + { + "id": "la54-tuple-write-count-builtin", + "source": "obj=(7,)\nkey=\"count\"\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54", + "stdout": "False False True True\n" + }, + { + "id": "la54-tuple-write-count-native", + "source": "obj=(7,)\nkey=\"count\"\ntry: object.__setattr__(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54", + "stdout": "False False True True\n" + }, + { + "id": "la54-tuple-write-subtype-key", + "source": "class Key(str):\n def __hash__(self): raise ValueError(\"unexpected hash\")\n def __str__(self): raise ValueError(\"unexpected string\")\nobj=(7,)\nkey=Key(\"missing\")\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj)\n", + "stage": "LA54", + "stdout": "True True\n" + }, + { + "id": "la54-tuple-delete-missing-builtin", + "source": "obj=(7,)\nkey=\"missing\"\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54", + "stdout": "True True False False\n" + }, + { + "id": "la54-tuple-delete-missing-native", + "source": "obj=(7,)\nkey=\"missing\"\ntry: object.__delattr__(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54", + "stdout": "True True False False\n" + }, + { + "id": "la54-tuple-delete-count-builtin", + "source": "obj=(7,)\nkey=\"count\"\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54", + "stdout": "False False True True\n" + }, + { + "id": "la54-tuple-delete-count-native", + "source": "obj=(7,)\nkey=\"count\"\ntry: object.__delattr__(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54", + "stdout": "False False True True\n" + }, + { + "id": "la54-tuple-delete-subtype-key", + "source": "class Key(str):\n def __hash__(self): raise ValueError(\"unexpected hash\")\n def __str__(self): raise ValueError(\"unexpected string\")\nobj=(7,)\nkey=Key(\"missing\")\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj)\n", + "stage": "LA54", + "stdout": "True True\n" + }, + { + "id": "la54-dict-write-missing-builtin", + "source": "obj={\"field\":7}\nkey=\"missing\"\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54", + "stdout": "True True False False\n" + }, + { + "id": "la54-dict-write-missing-native", + "source": "obj={\"field\":7}\nkey=\"missing\"\ntry: object.__setattr__(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54", + "stdout": "True True False False\n" + }, + { + "id": "la54-dict-write-keys-builtin", + "source": "obj={\"field\":7}\nkey=\"keys\"\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54", + "stdout": "False False True True\n" + }, + { + "id": "la54-dict-write-keys-native", + "source": "obj={\"field\":7}\nkey=\"keys\"\ntry: object.__setattr__(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54", + "stdout": "False False True True\n" + }, + { + "id": "la54-dict-write-subtype-key", + "source": "class Key(str):\n def __hash__(self): raise ValueError(\"unexpected hash\")\n def __str__(self): raise ValueError(\"unexpected string\")\nobj={\"field\":7}\nkey=Key(\"missing\")\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj)\n", + "stage": "LA54", + "stdout": "True True\n" + }, + { + "id": "la54-dict-delete-missing-builtin", + "source": "obj={\"field\":7}\nkey=\"missing\"\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54", + "stdout": "True True False False\n" + }, + { + "id": "la54-dict-delete-missing-native", + "source": "obj={\"field\":7}\nkey=\"missing\"\ntry: object.__delattr__(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54", + "stdout": "True True False False\n" + }, + { + "id": "la54-dict-delete-keys-builtin", + "source": "obj={\"field\":7}\nkey=\"keys\"\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54", + "stdout": "False False True True\n" + }, + { + "id": "la54-dict-delete-keys-native", + "source": "obj={\"field\":7}\nkey=\"keys\"\ntry: object.__delattr__(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54", + "stdout": "False False True True\n" + }, + { + "id": "la54-dict-delete-subtype-key", + "source": "class Key(str):\n def __hash__(self): raise ValueError(\"unexpected hash\")\n def __str__(self): raise ValueError(\"unexpected string\")\nobj={\"field\":7}\nkey=Key(\"missing\")\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj)\n", + "stage": "LA54", + "stdout": "True True\n" + }, + { + "id": "la54-range-write-missing-builtin", + "source": "obj=range(3)\nkey=\"missing\"\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54", + "stdout": "True True False False\n" + }, + { + "id": "la54-range-write-missing-native", + "source": "obj=range(3)\nkey=\"missing\"\ntry: object.__setattr__(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54", + "stdout": "True True False False\n" + }, + { + "id": "la54-range-write-start-builtin", + "source": "obj=range(3)\nkey=\"start\"\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54", + "stdout": "False False True True\n" + }, + { + "id": "la54-range-write-start-native", + "source": "obj=range(3)\nkey=\"start\"\ntry: object.__setattr__(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54", + "stdout": "False False True True\n" + }, + { + "id": "la54-range-write-subtype-key", + "source": "class Key(str):\n def __hash__(self): raise ValueError(\"unexpected hash\")\n def __str__(self): raise ValueError(\"unexpected string\")\nobj=range(3)\nkey=Key(\"missing\")\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj)\n", + "stage": "LA54", + "stdout": "True True\n" + }, + { + "id": "la54-range-delete-missing-builtin", + "source": "obj=range(3)\nkey=\"missing\"\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54", + "stdout": "True True False False\n" + }, + { + "id": "la54-range-delete-missing-native", + "source": "obj=range(3)\nkey=\"missing\"\ntry: object.__delattr__(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54", + "stdout": "True True False False\n" + }, + { + "id": "la54-range-delete-start-builtin", + "source": "obj=range(3)\nkey=\"start\"\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54", + "stdout": "False False True True\n" + }, + { + "id": "la54-range-delete-start-native", + "source": "obj=range(3)\nkey=\"start\"\ntry: object.__delattr__(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54", + "stdout": "False False True True\n" + }, + { + "id": "la54-range-delete-subtype-key", + "source": "class Key(str):\n def __hash__(self): raise ValueError(\"unexpected hash\")\n def __str__(self): raise ValueError(\"unexpected string\")\nobj=range(3)\nkey=Key(\"missing\")\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj)\n", + "stage": "LA54", + "stdout": "True True\n" + }, + { + "id": "la54-slice-write-missing-builtin", + "source": "obj=slice(1,3)\nkey=\"missing\"\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54", + "stdout": "True True False False\n" + }, + { + "id": "la54-slice-write-missing-native", + "source": "obj=slice(1,3)\nkey=\"missing\"\ntry: object.__setattr__(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54", + "stdout": "True True False False\n" + }, + { + "id": "la54-slice-write-stop-builtin", + "source": "obj=slice(1,3)\nkey=\"stop\"\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54", + "stdout": "False False True True\n" + }, + { + "id": "la54-slice-write-stop-native", + "source": "obj=slice(1,3)\nkey=\"stop\"\ntry: object.__setattr__(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54", + "stdout": "False False True True\n" + }, + { + "id": "la54-slice-write-subtype-key", + "source": "class Key(str):\n def __hash__(self): raise ValueError(\"unexpected hash\")\n def __str__(self): raise ValueError(\"unexpected string\")\nobj=slice(1,3)\nkey=Key(\"missing\")\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj)\n", + "stage": "LA54", + "stdout": "True True\n" + }, + { + "id": "la54-slice-delete-missing-builtin", + "source": "obj=slice(1,3)\nkey=\"missing\"\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54", + "stdout": "True True False False\n" + }, + { + "id": "la54-slice-delete-missing-native", + "source": "obj=slice(1,3)\nkey=\"missing\"\ntry: object.__delattr__(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54", + "stdout": "True True False False\n" + }, + { + "id": "la54-slice-delete-stop-builtin", + "source": "obj=slice(1,3)\nkey=\"stop\"\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54", + "stdout": "False False True True\n" + }, + { + "id": "la54-slice-delete-stop-native", + "source": "obj=slice(1,3)\nkey=\"stop\"\ntry: object.__delattr__(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54", + "stdout": "False False True True\n" + }, + { + "id": "la54-slice-delete-subtype-key", + "source": "class Key(str):\n def __hash__(self): raise ValueError(\"unexpected hash\")\n def __str__(self): raise ValueError(\"unexpected string\")\nobj=slice(1,3)\nkey=Key(\"missing\")\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj)\n", + "stage": "LA54", + "stdout": "True True\n" + }, + { + "id": "la54-set-write-missing-builtin", + "source": "obj={7}\nkey=\"missing\"\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54", + "stdout": "True True False False\n" + }, + { + "id": "la54-set-write-missing-native", + "source": "obj={7}\nkey=\"missing\"\ntry: object.__setattr__(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54", + "stdout": "True True False False\n" + }, + { + "id": "la54-set-write-add-builtin", + "source": "obj={7}\nkey=\"add\"\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54", + "stdout": "False False True True\n" + }, + { + "id": "la54-set-write-add-native", + "source": "obj={7}\nkey=\"add\"\ntry: object.__setattr__(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54", + "stdout": "False False True True\n" + }, + { + "id": "la54-set-write-subtype-key", + "source": "class Key(str):\n def __hash__(self): raise ValueError(\"unexpected hash\")\n def __str__(self): raise ValueError(\"unexpected string\")\nobj={7}\nkey=Key(\"missing\")\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj)\n", + "stage": "LA54", + "stdout": "True True\n" + }, + { + "id": "la54-set-delete-missing-builtin", + "source": "obj={7}\nkey=\"missing\"\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54", + "stdout": "True True False False\n" + }, + { + "id": "la54-set-delete-missing-native", + "source": "obj={7}\nkey=\"missing\"\ntry: object.__delattr__(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54", + "stdout": "True True False False\n" + }, + { + "id": "la54-set-delete-add-builtin", + "source": "obj={7}\nkey=\"add\"\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54", + "stdout": "False False True True\n" + }, + { + "id": "la54-set-delete-add-native", + "source": "obj={7}\nkey=\"add\"\ntry: object.__delattr__(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54", + "stdout": "False False True True\n" + }, + { + "id": "la54-set-delete-subtype-key", + "source": "class Key(str):\n def __hash__(self): raise ValueError(\"unexpected hash\")\n def __str__(self): raise ValueError(\"unexpected string\")\nobj={7}\nkey=Key(\"missing\")\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj)\n", + "stage": "LA54", + "stdout": "True True\n" + }, + { + "id": "la54-frozenset-write-missing-builtin", + "source": "obj=frozenset([7])\nkey=\"missing\"\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54", + "stdout": "True True False False\n" + }, + { + "id": "la54-frozenset-write-missing-native", + "source": "obj=frozenset([7])\nkey=\"missing\"\ntry: object.__setattr__(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54", + "stdout": "True True False False\n" + }, + { + "id": "la54-frozenset-write-union-builtin", + "source": "obj=frozenset([7])\nkey=\"union\"\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54", + "stdout": "False False True True\n" + }, + { + "id": "la54-frozenset-write-union-native", + "source": "obj=frozenset([7])\nkey=\"union\"\ntry: object.__setattr__(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54", + "stdout": "False False True True\n" + }, + { + "id": "la54-frozenset-write-subtype-key", + "source": "class Key(str):\n def __hash__(self): raise ValueError(\"unexpected hash\")\n def __str__(self): raise ValueError(\"unexpected string\")\nobj=frozenset([7])\nkey=Key(\"missing\")\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj)\n", + "stage": "LA54", + "stdout": "True True\n" + }, + { + "id": "la54-frozenset-delete-missing-builtin", + "source": "obj=frozenset([7])\nkey=\"missing\"\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54", + "stdout": "True True False False\n" + }, + { + "id": "la54-frozenset-delete-missing-native", + "source": "obj=frozenset([7])\nkey=\"missing\"\ntry: object.__delattr__(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54", + "stdout": "True True False False\n" + }, + { + "id": "la54-frozenset-delete-union-builtin", + "source": "obj=frozenset([7])\nkey=\"union\"\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54", + "stdout": "False False True True\n" + }, + { + "id": "la54-frozenset-delete-union-native", + "source": "obj=frozenset([7])\nkey=\"union\"\ntry: object.__delattr__(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54", + "stdout": "False False True True\n" + }, + { + "id": "la54-frozenset-delete-subtype-key", + "source": "class Key(str):\n def __hash__(self): raise ValueError(\"unexpected hash\")\n def __str__(self): raise ValueError(\"unexpected string\")\nobj=frozenset([7])\nkey=Key(\"missing\")\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj)\n", + "stage": "LA54", + "stdout": "True True\n" + }, + { + "id": "la54-bytes-write-missing-builtin", + "source": "obj=b\"abc\"\nkey=\"missing\"\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54", + "stdout": "True True False False\n" + }, + { + "id": "la54-bytes-write-missing-native", + "source": "obj=b\"abc\"\nkey=\"missing\"\ntry: object.__setattr__(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54", + "stdout": "True True False False\n" + }, + { + "id": "la54-bytes-write-decode-builtin", + "source": "obj=b\"abc\"\nkey=\"decode\"\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54", + "stdout": "False False True True\n" + }, + { + "id": "la54-bytes-write-decode-native", + "source": "obj=b\"abc\"\nkey=\"decode\"\ntry: object.__setattr__(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54", + "stdout": "False False True True\n" + }, + { + "id": "la54-bytes-write-subtype-key", + "source": "class Key(str):\n def __hash__(self): raise ValueError(\"unexpected hash\")\n def __str__(self): raise ValueError(\"unexpected string\")\nobj=b\"abc\"\nkey=Key(\"missing\")\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj)\n", + "stage": "LA54", + "stdout": "True True\n" + }, + { + "id": "la54-bytes-delete-missing-builtin", + "source": "obj=b\"abc\"\nkey=\"missing\"\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54", + "stdout": "True True False False\n" + }, + { + "id": "la54-bytes-delete-missing-native", + "source": "obj=b\"abc\"\nkey=\"missing\"\ntry: object.__delattr__(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54", + "stdout": "True True False False\n" + }, + { + "id": "la54-bytes-delete-decode-builtin", + "source": "obj=b\"abc\"\nkey=\"decode\"\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54", + "stdout": "False False True True\n" + }, + { + "id": "la54-bytes-delete-decode-native", + "source": "obj=b\"abc\"\nkey=\"decode\"\ntry: object.__delattr__(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54", + "stdout": "False False True True\n" + }, + { + "id": "la54-bytes-delete-subtype-key", + "source": "class Key(str):\n def __hash__(self): raise ValueError(\"unexpected hash\")\n def __str__(self): raise ValueError(\"unexpected string\")\nobj=b\"abc\"\nkey=Key(\"missing\")\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj)\n", + "stage": "LA54", + "stdout": "True True\n" + }, + { + "id": "la54-bytearray-write-missing-builtin", + "source": "obj=bytearray(b\"abc\")\nkey=\"missing\"\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54", + "stdout": "True True False False\n" + }, + { + "id": "la54-bytearray-write-missing-native", + "source": "obj=bytearray(b\"abc\")\nkey=\"missing\"\ntry: object.__setattr__(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54", + "stdout": "True True False False\n" + }, + { + "id": "la54-bytearray-write-append-builtin", + "source": "obj=bytearray(b\"abc\")\nkey=\"append\"\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54", + "stdout": "False False True True\n" + }, + { + "id": "la54-bytearray-write-append-native", + "source": "obj=bytearray(b\"abc\")\nkey=\"append\"\ntry: object.__setattr__(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54", + "stdout": "False False True True\n" + }, + { + "id": "la54-bytearray-write-subtype-key", + "source": "class Key(str):\n def __hash__(self): raise ValueError(\"unexpected hash\")\n def __str__(self): raise ValueError(\"unexpected string\")\nobj=bytearray(b\"abc\")\nkey=Key(\"missing\")\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj)\n", + "stage": "LA54", + "stdout": "True True\n" + }, + { + "id": "la54-bytearray-delete-missing-builtin", + "source": "obj=bytearray(b\"abc\")\nkey=\"missing\"\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54", + "stdout": "True True False False\n" + }, + { + "id": "la54-bytearray-delete-missing-native", + "source": "obj=bytearray(b\"abc\")\nkey=\"missing\"\ntry: object.__delattr__(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54", + "stdout": "True True False False\n" + }, + { + "id": "la54-bytearray-delete-append-builtin", + "source": "obj=bytearray(b\"abc\")\nkey=\"append\"\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54", + "stdout": "False False True True\n" + }, + { + "id": "la54-bytearray-delete-append-native", + "source": "obj=bytearray(b\"abc\")\nkey=\"append\"\ntry: object.__delattr__(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54", + "stdout": "False False True True\n" + }, + { + "id": "la54-bytearray-delete-subtype-key", + "source": "class Key(str):\n def __hash__(self): raise ValueError(\"unexpected hash\")\n def __str__(self): raise ValueError(\"unexpected string\")\nobj=bytearray(b\"abc\")\nkey=Key(\"missing\")\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj)\n", + "stage": "LA54", + "stdout": "True True\n" + }, + { + "id": "la54-memoryview-write-missing-builtin", + "source": "obj=memoryview(bytearray(b\"abc\"))\nkey=\"missing\"\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54", + "stdout": "True True False False\n" + }, + { + "id": "la54-memoryview-write-missing-native", + "source": "obj=memoryview(bytearray(b\"abc\"))\nkey=\"missing\"\ntry: object.__setattr__(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54", + "stdout": "True True False False\n" + }, + { + "id": "la54-memoryview-write-readonly-builtin", + "source": "obj=memoryview(bytearray(b\"abc\"))\nkey=\"readonly\"\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54", + "stdout": "False False True True\n" + }, + { + "id": "la54-memoryview-write-readonly-native", + "source": "obj=memoryview(bytearray(b\"abc\"))\nkey=\"readonly\"\ntry: object.__setattr__(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54", + "stdout": "False False True True\n" + }, + { + "id": "la54-memoryview-write-subtype-key", + "source": "class Key(str):\n def __hash__(self): raise ValueError(\"unexpected hash\")\n def __str__(self): raise ValueError(\"unexpected string\")\nobj=memoryview(bytearray(b\"abc\"))\nkey=Key(\"missing\")\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj)\n", + "stage": "LA54", + "stdout": "True True\n" + }, + { + "id": "la54-memoryview-delete-missing-builtin", + "source": "obj=memoryview(bytearray(b\"abc\"))\nkey=\"missing\"\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54", + "stdout": "True True False False\n" + }, + { + "id": "la54-memoryview-delete-missing-native", + "source": "obj=memoryview(bytearray(b\"abc\"))\nkey=\"missing\"\ntry: object.__delattr__(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54", + "stdout": "True True False False\n" + }, + { + "id": "la54-memoryview-delete-readonly-builtin", + "source": "obj=memoryview(bytearray(b\"abc\"))\nkey=\"readonly\"\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54", + "stdout": "False False True True\n" + }, + { + "id": "la54-memoryview-delete-readonly-native", + "source": "obj=memoryview(bytearray(b\"abc\"))\nkey=\"readonly\"\ntry: object.__delattr__(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54", + "stdout": "False False True True\n" + }, + { + "id": "la54-memoryview-delete-subtype-key", + "source": "class Key(str):\n def __hash__(self): raise ValueError(\"unexpected hash\")\n def __str__(self): raise ValueError(\"unexpected string\")\nobj=memoryview(bytearray(b\"abc\"))\nkey=Key(\"missing\")\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj)\n", + "stage": "LA54", + "stdout": "True True\n" + }, + { + "id": "la54-released-view-write-missing-builtin", + "source": "view=memoryview(b\"abc\")\nview.release()\nobj=view\nkey=\"missing\"\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54", + "stdout": "True True False False\n" + }, + { + "id": "la54-released-view-write-missing-native", + "source": "view=memoryview(b\"abc\")\nview.release()\nobj=view\nkey=\"missing\"\ntry: object.__setattr__(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54", + "stdout": "True True False False\n" + }, + { + "id": "la54-released-view-write-nbytes-builtin", + "source": "view=memoryview(b\"abc\")\nview.release()\nobj=view\nkey=\"nbytes\"\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54", + "stdout": "False False True True\n" + }, + { + "id": "la54-released-view-write-nbytes-native", + "source": "view=memoryview(b\"abc\")\nview.release()\nobj=view\nkey=\"nbytes\"\ntry: object.__setattr__(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54", + "stdout": "False False True True\n" + }, + { + "id": "la54-released-view-write-subtype-key", + "source": "view=memoryview(b\"abc\")\nview.release()\nclass Key(str):\n def __hash__(self): raise ValueError(\"unexpected hash\")\n def __str__(self): raise ValueError(\"unexpected string\")\nobj=view\nkey=Key(\"missing\")\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj)\n", + "stage": "LA54", + "stdout": "True True\n" + }, + { + "id": "la54-released-view-delete-missing-builtin", + "source": "view=memoryview(b\"abc\")\nview.release()\nobj=view\nkey=\"missing\"\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54", + "stdout": "True True False False\n" + }, + { + "id": "la54-released-view-delete-missing-native", + "source": "view=memoryview(b\"abc\")\nview.release()\nobj=view\nkey=\"missing\"\ntry: object.__delattr__(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54", + "stdout": "True True False False\n" + }, + { + "id": "la54-released-view-delete-nbytes-builtin", + "source": "view=memoryview(b\"abc\")\nview.release()\nobj=view\nkey=\"nbytes\"\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54", + "stdout": "False False True True\n" + }, + { + "id": "la54-released-view-delete-nbytes-native", + "source": "view=memoryview(b\"abc\")\nview.release()\nobj=view\nkey=\"nbytes\"\ntry: object.__delattr__(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54", + "stdout": "False False True True\n" + }, + { + "id": "la54-released-view-delete-subtype-key", + "source": "view=memoryview(b\"abc\")\nview.release()\nclass Key(str):\n def __hash__(self): raise ValueError(\"unexpected hash\")\n def __str__(self): raise ValueError(\"unexpected string\")\nobj=view\nkey=Key(\"missing\")\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj)\n", + "stage": "LA54", + "stdout": "True True\n" + }, + { + "id": "la54-str-subclass-storage-control", + "source": "class A(str): pass\na=A(\"text\")\na.field=[7]\nprint(a.field[0])\ndel a.field\nprint(hasattr(a,\"field\"))\n", + "stage": "LA54", + "stdout": "7\nFalse\n" + }, + { + "id": "la54-list-subclass-storage-control", + "source": "class A(list): pass\na=A([7])\na.field=[7]\nprint(a.field[0])\ndel a.field\nprint(hasattr(a,\"field\"))\n", + "stage": "LA54", + "stdout": "7\nFalse\n" + }, + { + "id": "la54-tuple-subclass-storage-control", + "source": "class A(tuple): pass\na=A((7,))\na.field=[7]\nprint(a.field[0])\ndel a.field\nprint(hasattr(a,\"field\"))\n", + "stage": "LA54", + "stdout": "7\nFalse\n" + }, + { + "id": "la54-dict-subclass-storage-control", + "source": "class A(dict): pass\na=A()\na.field=[7]\nprint(a.field[0])\ndel a.field\nprint(hasattr(a,\"field\"))\n", + "stage": "LA54", + "stdout": "7\nFalse\n" + }, + { + "id": "la54-set-subclass-storage-control", + "source": "class A(set): pass\na=A([7])\na.field=[7]\nprint(a.field[0])\ndel a.field\nprint(hasattr(a,\"field\"))\n", + "stage": "LA54", + "stdout": "7\nFalse\n" + }, + { + "id": "la54-frozenset-subclass-storage-control", + "source": "class A(frozenset): pass\na=A([7])\na.field=[7]\nprint(a.field[0])\ndel a.field\nprint(hasattr(a,\"field\"))\n", + "stage": "LA54", + "stdout": "7\nFalse\n" + }, + { + "id": "la54-bytes-subclass-storage-control", + "source": "class A(bytes): pass\na=A(b\"abc\")\na.field=[7]\nprint(a.field[0])\ndel a.field\nprint(hasattr(a,\"field\"))\n", + "stage": "LA54", + "stdout": "7\nFalse\n" + }, + { + "id": "la54-bytearray-subclass-storage-control", + "source": "class A(bytearray): pass\na=A(b\"abc\")\na.field=[7]\nprint(a.field[0])\ndel a.field\nprint(hasattr(a,\"field\"))\n", + "stage": "LA54", + "stdout": "7\nFalse\n" + }, + { + "id": "la54-readonly-member-descriptor-control", + "source": "view=memoryview(b\"abc\")\nfor name in [\"readonly\",\"nbytes\"]:\n descriptor=memoryview.__dict__[name]\n for remove in [False,True]:\n try:\n if remove: descriptor.__delete__(view)\n else: descriptor.__set__(view,7)\n except AttributeError as e: print(e.name is None,e.obj is None)\n", + "stage": "LA54", + "stdout": "True True\nTrue True\nTrue True\nTrue True\n" } ], "reference_boundaries": [ From c133bf7809f7b685d8b9a4f13d1ec86526c67e5b Mon Sep 17 00:00:00 2001 From: ellainng <299958882+ellainng@users.noreply.github.com> Date: Sun, 1 Nov 2026 18:30:45 -0800 Subject: [PATCH 3/9] fix(LA-54A): preserve direct value mutation metadata --- runtime/functions.mjs | 2 +- src/pocket_python/_interpreter_lock.py | 4 +- tests/fixtures/interpreter/exceptions.json | 195 ++++++++++++++++++++- 3 files changed, 197 insertions(+), 4 deletions(-) diff --git a/runtime/functions.mjs b/runtime/functions.mjs index a00e0df..2c1bc22 100644 --- a/runtime/functions.mjs +++ b/runtime/functions.mjs @@ -979,7 +979,7 @@ function activation(program, {maxSteps = 10_000, maxWork = 1_000_000, maxDepth = } else if (["attribute-set", "attribute-delete"].includes(op)) { const value = op === "attribute-set" ? stack.pop() : null; const receiver = stack.pop(); - if (arg !== "__class__" && (values.isView(receiver) || values.isBinary(receiver) || values.isSet(receiver) || values.isSlice(receiver) || values.isComplex(receiver) || values.isRange(receiver))) python("AttributeError", "Value attributes are read-only"); + if (verified.code.version < 4 && arg !== "__class__" && (values.isView(receiver) || values.isBinary(receiver) || values.isSet(receiver) || values.isSlice(receiver) || values.isComplex(receiver) || values.isRange(receiver))) python("AttributeError", "Value attributes are read-only"); if (receiver instanceof ModuleValue || classes.lookupMayCallback(receiver) || classes.hasAttributeHook(receiver, op === "attribute-set" ? "__setattr__" : "__delattr__") || userProtocol(receiver) && arg === "__class__" && classes.hasSlot(classes.checkingType(receiver), "__class__")) { dispatch(attributeTask(receiver, arg, summary(frame, cursor), null, 0, op === "attribute-set" ? "set" : "delete", null, null, value)); return; diff --git a/src/pocket_python/_interpreter_lock.py b/src/pocket_python/_interpreter_lock.py index 9f0fe2b..e85c085 100644 --- a/src/pocket_python/_interpreter_lock.py +++ b/src/pocket_python/_interpreter_lock.py @@ -1,6 +1,6 @@ """Known source-console assets, canonical LF bytes; refresh with scripts/lock_interpreter.py.""" -ENGINE_FINGERPRINT = '2c659a775b219c6730edfc076c78bfb4c47b365151471c3835321a602bddf526' +ENGINE_FINGERPRINT = '14eb6ed2da3b0e6f3da7f7bd7708644d1d29d3437f8fb894866be78f265bba1f' ENGINE_FILES = ( ('browser-protocol.mjs', 10966, '85f50d640eaa5324cf62e75f5dc6050186bf167201208d26e84a49efd74bf499'), ('bytecode.mjs', 6836, 'e76fabeea879bc1da8016283f7e74df5974479666b100199c2d3c31a53b8ecf4'), @@ -11,7 +11,7 @@ ('dynamic-source.mjs', 5723, '9040a40e8906723f01f496f30aafd329816f823ecad158dd29db8e21b844bf2c'), ('function-bytecode.mjs', 25120, '75e7646ba7d001eedc0f365032eb0344705f3291ada9c0a44a0b471fb4d3d496'), ('function-objects.mjs', 13718, '76130ece89e86db8ca1b77bc1cd746eb08940728f8ac90615e9e8a50350631f1'), - ('functions.mjs', 103537, 'a5753bfeac8b93fa56a3c8d8115a179829c40b3d1c01f7c5d7ce25f4a0b03d48'), + ('functions.mjs', 103566, 'f1c4d703760dfbe4709408f382d20d561304367d83f7302429f4ccc26e0bd659'), ('interpreter-host.mjs', 7318, '4afadfd987000afa6221ee73dcd64e9c00a2d13c4324718248d396302a39296d'), ('interpreter-worker.mjs', 8855, '324cc23100eb40ef04f2909b805d7abc0c8651b4608b3a61a95470ec7e74e19e'), ('library-catalog.mjs', 907, '0adcaae0f6f07b4e1acb22e46493ec99f7247a5828b426c703530d8b2736032c'), diff --git a/tests/fixtures/interpreter/exceptions.json b/tests/fixtures/interpreter/exceptions.json index 37c3a4b..49c0a3d 100644 --- a/tests/fixtures/interpreter/exceptions.json +++ b/tests/fixtures/interpreter/exceptions.json @@ -54,7 +54,8 @@ "LA51", "LA52", "LA53", - "LA54" + "LA54", + "LA54A" ], "cases": [ { @@ -10530,6 +10531,198 @@ "source": "view=memoryview(b\"abc\")\nfor name in [\"readonly\",\"nbytes\"]:\n descriptor=memoryview.__dict__[name]\n for remove in [False,True]:\n try:\n if remove: descriptor.__delete__(view)\n else: descriptor.__set__(view,7)\n except AttributeError as e: print(e.name is None,e.obj is None)\n", "stage": "LA54", "stdout": "True True\nTrue True\nTrue True\nTrue True\n" + }, + { + "id": "direct-values-complex-missing-write", + "source": "obj=2+3j\ntry: obj.missing=7\nexcept AttributeError as e:\n e.args=()\n print(e.name==\"missing\",e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54A", + "stdout": "True True False False\n" + }, + { + "id": "direct-values-complex-missing-delete", + "source": "obj=2+3j\ntry: del obj.missing\nexcept AttributeError as e:\n e.args=()\n print(e.name==\"missing\",e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54A", + "stdout": "True True False False\n" + }, + { + "id": "direct-values-complex-imag-write", + "source": "obj=2+3j\ntry: obj.imag=7\nexcept AttributeError as e:\n e.args=()\n print(e.name==\"imag\",e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54A", + "stdout": "False False True True\n" + }, + { + "id": "direct-values-complex-imag-delete", + "source": "obj=2+3j\ntry: del obj.imag\nexcept AttributeError as e:\n e.args=()\n print(e.name==\"imag\",e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54A", + "stdout": "False False True True\n" + }, + { + "id": "direct-values-range-missing-write", + "source": "obj=range(3)\ntry: obj.missing=7\nexcept AttributeError as e:\n e.args=()\n print(e.name==\"missing\",e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54A", + "stdout": "True True False False\n" + }, + { + "id": "direct-values-range-missing-delete", + "source": "obj=range(3)\ntry: del obj.missing\nexcept AttributeError as e:\n e.args=()\n print(e.name==\"missing\",e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54A", + "stdout": "True True False False\n" + }, + { + "id": "direct-values-range-start-write", + "source": "obj=range(3)\ntry: obj.start=7\nexcept AttributeError as e:\n e.args=()\n print(e.name==\"start\",e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54A", + "stdout": "False False True True\n" + }, + { + "id": "direct-values-range-start-delete", + "source": "obj=range(3)\ntry: del obj.start\nexcept AttributeError as e:\n e.args=()\n print(e.name==\"start\",e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54A", + "stdout": "False False True True\n" + }, + { + "id": "direct-values-slice-missing-write", + "source": "obj=slice(1,3)\ntry: obj.missing=7\nexcept AttributeError as e:\n e.args=()\n print(e.name==\"missing\",e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54A", + "stdout": "True True False False\n" + }, + { + "id": "direct-values-slice-missing-delete", + "source": "obj=slice(1,3)\ntry: del obj.missing\nexcept AttributeError as e:\n e.args=()\n print(e.name==\"missing\",e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54A", + "stdout": "True True False False\n" + }, + { + "id": "direct-values-slice-stop-write", + "source": "obj=slice(1,3)\ntry: obj.stop=7\nexcept AttributeError as e:\n e.args=()\n print(e.name==\"stop\",e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54A", + "stdout": "False False True True\n" + }, + { + "id": "direct-values-slice-stop-delete", + "source": "obj=slice(1,3)\ntry: del obj.stop\nexcept AttributeError as e:\n e.args=()\n print(e.name==\"stop\",e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54A", + "stdout": "False False True True\n" + }, + { + "id": "direct-values-set-missing-write", + "source": "obj={7}\ntry: obj.missing=7\nexcept AttributeError as e:\n e.args=()\n print(e.name==\"missing\",e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54A", + "stdout": "True True False False\n" + }, + { + "id": "direct-values-set-missing-delete", + "source": "obj={7}\ntry: del obj.missing\nexcept AttributeError as e:\n e.args=()\n print(e.name==\"missing\",e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54A", + "stdout": "True True False False\n" + }, + { + "id": "direct-values-set-add-write", + "source": "obj={7}\ntry: obj.add=7\nexcept AttributeError as e:\n e.args=()\n print(e.name==\"add\",e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54A", + "stdout": "False False True True\n" + }, + { + "id": "direct-values-set-add-delete", + "source": "obj={7}\ntry: del obj.add\nexcept AttributeError as e:\n e.args=()\n print(e.name==\"add\",e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54A", + "stdout": "False False True True\n" + }, + { + "id": "direct-values-frozenset-missing-write", + "source": "obj=frozenset([7])\ntry: obj.missing=7\nexcept AttributeError as e:\n e.args=()\n print(e.name==\"missing\",e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54A", + "stdout": "True True False False\n" + }, + { + "id": "direct-values-frozenset-missing-delete", + "source": "obj=frozenset([7])\ntry: del obj.missing\nexcept AttributeError as e:\n e.args=()\n print(e.name==\"missing\",e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54A", + "stdout": "True True False False\n" + }, + { + "id": "direct-values-frozenset-union-write", + "source": "obj=frozenset([7])\ntry: obj.union=7\nexcept AttributeError as e:\n e.args=()\n print(e.name==\"union\",e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54A", + "stdout": "False False True True\n" + }, + { + "id": "direct-values-frozenset-union-delete", + "source": "obj=frozenset([7])\ntry: del obj.union\nexcept AttributeError as e:\n e.args=()\n print(e.name==\"union\",e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54A", + "stdout": "False False True True\n" + }, + { + "id": "direct-values-bytes-missing-write", + "source": "obj=b\"abc\"\ntry: obj.missing=7\nexcept AttributeError as e:\n e.args=()\n print(e.name==\"missing\",e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54A", + "stdout": "True True False False\n" + }, + { + "id": "direct-values-bytes-missing-delete", + "source": "obj=b\"abc\"\ntry: del obj.missing\nexcept AttributeError as e:\n e.args=()\n print(e.name==\"missing\",e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54A", + "stdout": "True True False False\n" + }, + { + "id": "direct-values-bytes-decode-write", + "source": "obj=b\"abc\"\ntry: obj.decode=7\nexcept AttributeError as e:\n e.args=()\n print(e.name==\"decode\",e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54A", + "stdout": "False False True True\n" + }, + { + "id": "direct-values-bytes-decode-delete", + "source": "obj=b\"abc\"\ntry: del obj.decode\nexcept AttributeError as e:\n e.args=()\n print(e.name==\"decode\",e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54A", + "stdout": "False False True True\n" + }, + { + "id": "direct-values-bytearray-missing-write", + "source": "obj=bytearray(b\"abc\")\ntry: obj.missing=7\nexcept AttributeError as e:\n e.args=()\n print(e.name==\"missing\",e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54A", + "stdout": "True True False False\n" + }, + { + "id": "direct-values-bytearray-missing-delete", + "source": "obj=bytearray(b\"abc\")\ntry: del obj.missing\nexcept AttributeError as e:\n e.args=()\n print(e.name==\"missing\",e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54A", + "stdout": "True True False False\n" + }, + { + "id": "direct-values-bytearray-append-write", + "source": "obj=bytearray(b\"abc\")\ntry: obj.append=7\nexcept AttributeError as e:\n e.args=()\n print(e.name==\"append\",e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54A", + "stdout": "False False True True\n" + }, + { + "id": "direct-values-bytearray-append-delete", + "source": "obj=bytearray(b\"abc\")\ntry: del obj.append\nexcept AttributeError as e:\n e.args=()\n print(e.name==\"append\",e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54A", + "stdout": "False False True True\n" + }, + { + "id": "direct-values-memoryview-missing-write", + "source": "obj=memoryview(b\"abc\")\ntry: obj.missing=7\nexcept AttributeError as e:\n e.args=()\n print(e.name==\"missing\",e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54A", + "stdout": "True True False False\n" + }, + { + "id": "direct-values-memoryview-missing-delete", + "source": "obj=memoryview(b\"abc\")\ntry: del obj.missing\nexcept AttributeError as e:\n e.args=()\n print(e.name==\"missing\",e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54A", + "stdout": "True True False False\n" + }, + { + "id": "direct-values-memoryview-readonly-write", + "source": "obj=memoryview(b\"abc\")\ntry: obj.readonly=7\nexcept AttributeError as e:\n e.args=()\n print(e.name==\"readonly\",e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54A", + "stdout": "False False True True\n" + }, + { + "id": "direct-values-memoryview-readonly-delete", + "source": "obj=memoryview(b\"abc\")\ntry: del obj.readonly\nexcept AttributeError as e:\n e.args=()\n print(e.name==\"readonly\",e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA54A", + "stdout": "False False True True\n" } ], "reference_boundaries": [ From fdde73e8949ad8a0f7416678a6c9def36e1aa38b Mon Sep 17 00:00:00 2001 From: ellainng <299958882+ellainng@users.noreply.github.com> Date: Sun, 1 Nov 2026 18:32:48 -0800 Subject: [PATCH 4/9] test(LA-55): accept value mutation failures offline --- docs/interpreter-exceptions.md | 17 +++++ examples/interpreter_value_failures.py | 19 ++++++ runtime/exceptions.test.mjs | 13 ++++ scripts/object_integration_browser_smoke.py | 17 ++++- tests/fixtures/interpreter/exceptions.json | 76 ++++++++++++++++++++- 5 files changed, 140 insertions(+), 2 deletions(-) create mode 100644 examples/interpreter_value_failures.py diff --git a/docs/interpreter-exceptions.md b/docs/interpreter-exceptions.md index c3fe033..56d81a1 100644 --- a/docs/interpreter-exceptions.md +++ b/docs/interpreter-exceptions.md @@ -505,3 +505,20 @@ attribute failure without reading their released storage. Native object wrappers, temporary buffers and detached owners follow the same ownership rules. The 129 reference cases include writable subclass-dictionary controls; broader constructors, view APIs and codecs keep their existing gates. + +LA-54A routes direct assignment/deletion bytecode through the same owned +mutation implementation for complex, range, slice, set and binary/buffer values. +The 32 direct-syntax reference cases complement the reflection/wrapper cases; +legacy version-3 scalar error behavior is retained. + +LA-55 accepts scalar/container/buffer mutation failures across registry modules, +dynamic source, causes, notes, owner cycles and suspended cleanup. Run +`examples/interpreter_value_failures.py` unchanged on the pinned reference or +in the offline console. Installed acceptance checks original scalar/buffer +failure locations, Stop/Restart while errors retain their owners and fresh +targets after restart. The exception corpus now has 1,458 cases: 1,447 supported +semantic/error comparisons and 11 explicit refusals, with 267 shared core +cases. The six existing qualifications remain outside those counts. These +finite observations do not measure overall Python completion. Generator and +iterator mutation metadata follow separately; other #124 and candidate gates +remain open. diff --git a/examples/interpreter_value_failures.py b/examples/interpreter_value_failures.py new file mode 100644 index 0000000..a2179b8 --- /dev/null +++ b/examples/interpreter_value_failures.py @@ -0,0 +1,19 @@ +"""Retain rejected update targets for a useful cleanup report.""" + + +def update(record): + try: + setattr(record["target"], record["field"], record["value"]) + except AttributeError as error: + error.add_note("check the target before retrying") + raise error from ValueError(record["id"]) + + +for target in [1.25, [7], memoryview(bytearray(b"abc"))]: + record = {"id": 7, "target": target, "field": "missing", "value": []} + try: + update(record) + except AttributeError as error: + error.args = () + print(error.name, error.obj is target, error.__cause__.args[0]) + print(error.__notes__[0]) diff --git a/runtime/exceptions.test.mjs b/runtime/exceptions.test.mjs index b64f237..136945e 100644 --- a/runtime/exceptions.test.mjs +++ b/runtime/exceptions.test.mjs @@ -70,6 +70,19 @@ test('retained buffer mutation graphs share the allocation limit', async () => { assert.equal(result.stdout, 'ready\n'); assert.equal(result.error?.type, 'MemoryError'); closed(result.closed); }); +for (const mode of ['scalar', 'buffer']) test(mode + ' value mutation failures preserve original dynamic frames', async () => { + const item = corpus.cases.find(item => item.id === 'values-' + mode + '-dynamic-failure'); + const result = await runSourceRequest({modules: [unit(item.source)], compileOptions: {languageProfile: corpus.profile}}); + assert.equal(result.error.type, 'AttributeError'); + assert.deepEqual(result.error.traceback.map(frame => [frame.function, frame.location.filename, frame.location.span[0]]), [ + ['', 'sources/app.py', 1], ['', mode + '-value-original.py', 7], ['fail', mode + '-value-original.py', 3]]); + closed(result.closed); +}); + +test('value mutation errors survive interrupted suspended buffer cleanup', async () => { + await lifetime('def work():\n try: memoryview(bytearray(b"abc")).missing=7\n except AttributeError as e:\n try: yield e\n finally:\n print("entered")\n print(e.name,e.obj[0],len(e.args))\ni=work()\ne=next(i)\ne.args=()\ni.close()\n', 'entered\nmissing 97 0\n'); +}); + test('module mutation errors retain cyclic keys and the module after args replacement', async () => { await lifetime('import helper\nclass Key(str): pass\nkey=Key("missing")\ntry: delattr(helper,key)\nexcept AttributeError as error: e=error\nkey.error=e\ne.args=()\nhelper=None\nkey=None\nprint("entered")\nprint(e.name.error is e,e.obj.marker)\n', 'entered\nTrue 7\n', [{name: 'helper', source: 'marker=7\n'}]); diff --git a/scripts/object_integration_browser_smoke.py b/scripts/object_integration_browser_smoke.py index d939a20..fdafc06 100644 --- a/scripts/object_integration_browser_smoke.py +++ b/scripts/object_integration_browser_smoke.py @@ -66,7 +66,7 @@ def run(source, expected, error=None): for case in corpus['cases']: if (case.get('modules') or containers and case['stage'] not in {'LA-7', 'LA-6A'} or - exceptions and case['stage'] not in {'LA13', 'LA16', 'LA19', 'LA22', 'LA25', 'LA28', 'LA31', 'LA34', 'LA37', 'LA40', 'LA43', 'LA46', 'LA49', 'LA52'}): + exceptions and case['stage'] not in {'LA13', 'LA16', 'LA19', 'LA22', 'LA25', 'LA28', 'LA31', 'LA34', 'LA37', 'LA40', 'LA43', 'LA46', 'LA49', 'LA52', 'LA55'}): continue run(case['source'], case['stdout'], case.get('error_type')) if case['id'] == 'dynamic-original-source-failure': @@ -102,6 +102,9 @@ def run(source, expected, error=None): if case['id'] in {'callable-views-native-dynamic-failure', 'callable-views-view-dynamic-failure'}: filename = 'callable' if 'native' in case['id'] else 'view' assert f'fail at {filename}-mutation-original.py:2' in page.locator('#diagnostic').inner_text() + if case['id'] in {'values-scalar-dynamic-failure', 'values-buffer-dynamic-failure'}: + filename = 'scalar' if 'scalar' in case['id'] else 'buffer' + assert f'fail at {filename}-value-original.py:3' in page.locator('#diagnostic').inner_text() records.append({'id': case['id'], 'status': 'pass'}) if exceptions: for case in corpus.get('reference_boundaries', []): @@ -147,6 +150,10 @@ def run(source, expected, error=None): callable_views_expected = subprocess.check_output([sys.executable, '-I', '-S', '-c', callable_views], text=True, encoding='utf-8') run(callable_views, callable_views_expected) records.append({'id': 'callable-view-mutation-example', 'status': 'pass'}) + value_failures = (ROOT / 'examples/interpreter_value_failures.py').read_text(encoding='utf-8') + value_failures_expected = subprocess.check_output([sys.executable, '-I', '-S', '-c', value_failures], text=True, encoding='utf-8') + run(value_failures, value_failures_expected) + records.append({'id': 'value-mutation-example', 'status': 'pass'}) mutations = (ROOT / 'examples/interpreter_attribute_mutations.py').read_text(encoding='utf-8') mutations_expected = subprocess.check_output([sys.executable, '-I', '-S', '-c', mutations], text=True, encoding='utf-8') run(mutations, mutations_expected) @@ -184,6 +191,11 @@ def run(source, expected, error=None): 'def work():\n try: {}.keys().missing=7\n except AttributeError as e:\n try: yield e\n finally:\n print("entered")\n while True: pass\ni=work()\ne=next(i)\ni.close()\n', 'class Key:\n def __hash__(self): return hash("field")\n def __eq__(self,other):\n print("entered")\n while True: pass\nclass Record: pass\nr=Record()\nr.__dict__[Key()]=7\ndel r.field\n', ] + if exceptions: + busy_sources += [ + 'try: (1.25).missing=7\nexcept AttributeError as e:\n e.args=()\n print("entered")\n while True: e.obj\n', + 'def work():\n try: memoryview(bytearray(b"abc")).missing=7\n except AttributeError as e:\n try: yield e\n finally:\n print("entered")\n while True: e.obj[0]\ni=work()\ne=next(i)\ne.args=()\ni.close()\n', + ] for source in busy_sources: page.fill('#input', source) page.click('#run') @@ -198,6 +210,7 @@ def run(source, expected, error=None): run(example, expected) if exceptions: run(callable_views, callable_views_expected) + run(value_failures, value_failures_expected) context.set_offline(False) shutil.rmtree(root / 'site/console') shutil.copytree(root / 'reduced', root / 'site/console') @@ -266,6 +279,8 @@ def run(source, expected, error=None): 'stop-during-metaclass-deletion', 'class-mutation-original-diagnostics', 'stop-during-function-dictionary-comparison', 'stop-during-view-cleanup', 'callable-view-original-diagnostics', 'callable-view-fresh-restart', + 'stop-during-scalar-error-retention', 'stop-during-buffer-error-cleanup', + 'value-mutation-original-diagnostics', 'value-mutation-fresh-restart', 'restart-fresh-owner', 'reduced-profile-update', 'old-cache-removed', 'core-error-arguments', 'core-source-boundary'] return {'schema_version': 1, 'suite': 'interpreter-exceptions-offline/1' if exceptions else 'interpreter-containers-offline/1' if containers else 'interpreter-object-offline/1', diff --git a/tests/fixtures/interpreter/exceptions.json b/tests/fixtures/interpreter/exceptions.json index 49c0a3d..7ebdbe0 100644 --- a/tests/fixtures/interpreter/exceptions.json +++ b/tests/fixtures/interpreter/exceptions.json @@ -55,7 +55,8 @@ "LA52", "LA53", "LA54", - "LA54A" + "LA54A", + "LA55" ], "cases": [ { @@ -10723,6 +10724,79 @@ "source": "obj=memoryview(b\"abc\")\ntry: del obj.readonly\nexcept AttributeError as e:\n e.args=()\n print(e.name==\"readonly\",e.obj is obj,e.name is None,e.obj is None)\n", "stage": "LA54A", "stdout": "False False True True\n" + }, + { + "id": "values-composed-example", + "source": "\"\"\"Retain rejected update targets for a useful cleanup report.\"\"\"\n\n\ndef update(record):\n try:\n setattr(record[\"target\"], record[\"field\"], record[\"value\"])\n except AttributeError as error:\n error.add_note(\"check the target before retrying\")\n raise error from ValueError(record[\"id\"])\n\n\nfor target in [1.25, [7], memoryview(bytearray(b\"abc\"))]:\n record = {\"id\": 7, \"target\": target, \"field\": \"missing\", \"value\": []}\n try:\n update(record)\n except AttributeError as error:\n error.args = ()\n print(error.name, error.obj is target, error.__cause__.args[0])\n print(error.__notes__[0])\n", + "stage": "LA55", + "stdout": "missing True 7\ncheck the target before retrying\nmissing True 7\ncheck the target before retrying\nmissing True 7\ncheck the target before retrying\n" + }, + { + "id": "values-composed-module", + "source": "import helper\nobj=memoryview(bytearray(b\"abc\"))\nkey=\"missing\"\ntry: helper.fail(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.obj[0],e.__cause__.args[0],e.__notes__[0])\n", + "imports": [ + "helper" + ], + "modules": [ + { + "name": "helper", + "package": false, + "imports": [], + "source": "def fail(obj,key):\n try: delattr(obj,key)\n except AttributeError as e:\n e.add_note(\"cleanup failed\")\n raise e from ValueError(7)\n" + } + ], + "stage": "LA55", + "stdout": "True True 97 7 cleanup failed\n" + }, + { + "id": "values-scalar-dynamic-failure", + "source": "exec(compile('def fail():\\n obj=1.25\\n try: obj.missing=7\\n except AttributeError as e:\\n print(e.name,e.obj is obj)\\n raise\\nfail()',\"scalar-value-original.py\",\"exec\"))\n", + "error_type": "AttributeError", + "stage": "LA55", + "stdout": "missing True\n" + }, + { + "id": "values-buffer-dynamic-failure", + "source": "exec(compile('def fail():\\n obj=memoryview(b\"abc\")\\n try: obj.missing=7\\n except AttributeError as e:\\n print(e.name,e.obj is obj)\\n raise\\nfail()',\"buffer-value-original.py\",\"exec\"))\n", + "error_type": "AttributeError", + "stage": "LA55", + "stdout": "missing True\n" + }, + { + "id": "values-suspended-cleanup", + "source": "def work():\n try: [7].missing=[]\n except AttributeError as e:\n try: yield e\n finally: print(e.name,e.obj[0],e.__notes__[0])\ni=work()\ne=next(i)\ne.args=()\ne.add_note(\"retained\")\ni.close()\n", + "stage": "LA55", + "stdout": "missing 7 retained\n" + }, + { + "id": "values-retained-owner-cycle", + "source": "class Key(str): pass\nkey=Key(\"missing\")\ntry: setattr([7],key,[])\nexcept AttributeError as error: e=error\ne.obj.append(e)\nkey.error=e\ne.args=()\nkey=None\nprint(e.name.error is e,e.obj[1] is e,e.obj[0])\n", + "stage": "LA55", + "stdout": "True True 7\n" + }, + { + "id": "values-released-buffer-cleanup", + "source": "view=memoryview(bytearray(b\"abc\"))\nview.release()\ntry: del view.missing\nexcept AttributeError as e:\n e.args=()\n print(e.name,e.obj is view)\ntry: view[0]\nexcept ValueError: print(\"released\")\n", + "stage": "LA55", + "stdout": "missing True\nreleased\n" + }, + { + "id": "values-readonly-storage-cleanup", + "source": "obj=range(1,7,2)\ntry: obj.start=9\nexcept AttributeError as e: print(e.name is None,e.obj is None,obj.start)\ntry: del obj.missing\nexcept AttributeError as e: print(e.name,e.obj is obj)\n", + "stage": "LA55", + "stdout": "True True 1\nmissing True\n" + }, + { + "id": "values-owned-setter-control", + "source": "owned=AttributeError(\"owned\",name=\"custom\",obj=[7])\nclass A(list):\n def __setattr__(self,key,value): raise owned\na=A([8])\ntry: a.missing=9\nexcept AttributeError as e: print(e is owned,e.name,e.obj[0],a[0])\n", + "stage": "LA55", + "stdout": "True custom 7 8\n" + }, + { + "id": "values-cause-notes-rethrow", + "source": "try:\n try: (2+3j).missing=7\n except AttributeError as e:\n e.add_note(\"retained\")\n raise e from ValueError(8)\nexcept AttributeError as e:\n e.args=()\n print(e.name,e.obj==2+3j,e.__cause__.args[0],e.__notes__[0],e.__suppress_context__)\n", + "stage": "LA55", + "stdout": "missing True 8 retained True\n" } ], "reference_boundaries": [ From 880cfa325f55a04efc087103f7f86792f72a2c4c Mon Sep 17 00:00:00 2001 From: ellainng <299958882+ellainng@users.noreply.github.com> Date: Sun, 1 Nov 2026 18:34:48 -0800 Subject: [PATCH 5/9] feat(LA-56): adopt generator mutation failure metadata --- docs/interpreter-exceptions.md | 12 + docs/interpreter-object-scope.json | 2 +- runtime/class-values.mjs | 8 +- runtime/exceptions.test.mjs | 14 + src/pocket_python/_interpreter_lock.py | 4 +- tests/fixtures/interpreter/exceptions.json | 345 ++++++++++++++++++++- 6 files changed, 378 insertions(+), 7 deletions(-) diff --git a/docs/interpreter-exceptions.md b/docs/interpreter-exceptions.md index 56d81a1..d3dd6ef 100644 --- a/docs/interpreter-exceptions.md +++ b/docs/interpreter-exceptions.md @@ -522,3 +522,15 @@ cases. The six existing qualifications remain outside those counts. These finite observations do not measure overall Python completion. Generator and iterator mutation metadata follow separately; other #124 and candidate gates remain open. + +LA-56 adopts generator mutation failure metadata for created, suspended and +closed generators and generator expressions. Missing names retain the original +key and generator; read-only state attributes and native descriptors leave +fields absent. Generator name/qualname replacement accepts and retains string +subtypes without formatting callbacks; invalid values/deletion preserve the +previous name. The 57 reference cases include native object wrappers and direct +state descriptors. Suspended sources and cyclic replacement names share the +ordinary ownership/allocation rules. Frame tracing/jumping and wider generator +throw remain separately gated. The pinned +[generator implementation](https://github.com/python/cpython/blob/v3.14.7/Objects/genobject.c) +defines the native state and name descriptors. diff --git a/docs/interpreter-object-scope.json b/docs/interpreter-object-scope.json index 8e1854c..768cf60 100644 --- a/docs/interpreter-object-scope.json +++ b/docs/interpreter-object-scope.json @@ -401,7 +401,7 @@ }, { "path": "runtime/class-values.mjs", - "site": "this.fail(receiver instanceof FrameValue && [\"f_lineno\", \"f_trace\"].includes(name) ? \"UnsupportedOperationError\" : \"AttributeError\", \"Frame/generator metadata is read-only; tracing and jumping are separate\", receiver instanceof FrameValue && [\"f_lineno\", \"f_trace\"].includes(name) ? \"UNSUPPORTED\" : \"PYTHON\");", + "site": "this.fail(receiver instanceof FrameValue && [\"f_lineno\", \"f_trace\"].includes(name) ? \"UnsupportedOperationError\" : \"AttributeError\", \"Frame metadata is read-only; tracing and jumping are separate\", receiver instanceof FrameValue && [\"f_lineno\", \"f_trace\"].includes(name) ? \"UNSUPPORTED\" : \"PYTHON\");", "owner": "boundary:source-interface", "boundary": "Finite source code/frame interface excludes raw backend code constructors, native layouts and frame debugging." }, diff --git a/runtime/class-values.mjs b/runtime/class-values.mjs index 62dc05b..a38a4b1 100644 --- a/runtime/class-values.mjs +++ b/runtime/class-values.mjs @@ -1101,11 +1101,13 @@ export class ClassRuntime { if (this.containerDirectoryNames(receiver) !== null || this.values.isRange(receiver) || this.values.isSlice(receiver)) this.fixedMutationFailure(receiver, name, key, "Container attributes are read-only"); if (receiver instanceof GeneratorValue && ["__name__", "__qualname__"].includes(name)) { - if (remove || !this.values.isString(value)) this.fail("TypeError", name + " must be a string"); + if (remove || !this.values.isString(this.payload(value))) this.fail("TypeError", name + " must be a string"); return {kind: "value", value: this.functionMetadata(receiver, name === "__name__" ? "identityName" : "identityQualname", value)}; } - if (receiver instanceof GeneratorValue || receiver instanceof FrameValue || receiver instanceof FrameLocalsValue) - this.fail(receiver instanceof FrameValue && ["f_lineno", "f_trace"].includes(name) ? "UnsupportedOperationError" : "AttributeError", "Frame/generator metadata is read-only; tracing and jumping are separate", receiver instanceof FrameValue && ["f_lineno", "f_trace"].includes(name) ? "UNSUPPORTED" : "PYTHON"); + if (receiver instanceof GeneratorValue) + this.fixedMutationFailure(receiver, name, key, "Generator state is read-only"); + if (receiver instanceof FrameValue || receiver instanceof FrameLocalsValue) + this.fail(receiver instanceof FrameValue && ["f_lineno", "f_trace"].includes(name) ? "UnsupportedOperationError" : "AttributeError", "Frame metadata is read-only; tracing and jumping are separate", receiver instanceof FrameValue && ["f_lineno", "f_trace"].includes(name) ? "UNSUPPORTED" : "PYTHON"); if (receiver instanceof Cell) { if (name !== "cell_contents") this.fail("AttributeError", "Cell attributes are read-only"); const before = [receiver.bound, receiver.value]; receiver.bound = !remove; receiver.value = remove ? null : value; diff --git a/runtime/exceptions.test.mjs b/runtime/exceptions.test.mjs index 136945e..0e0c14a 100644 --- a/runtime/exceptions.test.mjs +++ b/runtime/exceptions.test.mjs @@ -83,6 +83,20 @@ test('value mutation errors survive interrupted suspended buffer cleanup', async await lifetime('def work():\n try: memoryview(bytearray(b"abc")).missing=7\n except AttributeError as e:\n try: yield e\n finally:\n print("entered")\n print(e.name,e.obj[0],len(e.args))\ni=work()\ne=next(i)\ne.args=()\ni.close()\n', 'entered\nmissing 97 0\n'); }); +test('generator mutation errors retain suspended sources and cyclic subtype keys', async () => { + await lifetime('class Key(str): pass\nkey=Key("missing")\ndef work(value):\n yield value\ntry: setattr(work([7]),key,[])\nexcept AttributeError as error: e=error\nkey.error=e\ne.args=()\nkey=None\nprint("entered")\nprint(e.name.error is e,next(e.obj)[0])\ne.obj.close()\n', 'entered\nTrue 7\n'); +}); + +test('generator name replacement retains cyclic string subclasses through collection', async () => { + await lifetime('class Name(str): pass\ndef work(): yield 7\ng=work()\nname=Name("renamed")\nname.owner=g\ng.__name__=name\nname=None\nprint("entered")\nprint(g.__name__.owner is g,next(g))\n', 'entered\nTrue 7\n'); +}); + +test('retained generator mutation graphs share the allocation limit', async () => { + const result = await runSourceRequest({modules: [unit('def work(value): yield value\nheld=[]\nprint("ready")\nwhile True:\n try: work([7]).missing=[]\n except AttributeError as e: held.append(e)\n')], + compileOptions: {languageProfile: corpus.profile}, options: {maxWork: 20000000}, memoryLimits: {maxBytes: 48000}}); + assert.equal(result.stdout, 'ready\n'); assert.equal(result.error?.type, 'MemoryError'); closed(result.closed); +}); + test('module mutation errors retain cyclic keys and the module after args replacement', async () => { await lifetime('import helper\nclass Key(str): pass\nkey=Key("missing")\ntry: delattr(helper,key)\nexcept AttributeError as error: e=error\nkey.error=e\ne.args=()\nhelper=None\nkey=None\nprint("entered")\nprint(e.name.error is e,e.obj.marker)\n', 'entered\nTrue 7\n', [{name: 'helper', source: 'marker=7\n'}]); diff --git a/src/pocket_python/_interpreter_lock.py b/src/pocket_python/_interpreter_lock.py index e85c085..a4026e6 100644 --- a/src/pocket_python/_interpreter_lock.py +++ b/src/pocket_python/_interpreter_lock.py @@ -1,11 +1,11 @@ """Known source-console assets, canonical LF bytes; refresh with scripts/lock_interpreter.py.""" -ENGINE_FINGERPRINT = '14eb6ed2da3b0e6f3da7f7bd7708644d1d29d3437f8fb894866be78f265bba1f' +ENGINE_FINGERPRINT = 'f672af08dbad2d0fb5dcceb585fc8e74cce675abf2d4c7533bbc0ff1d18b3107' ENGINE_FILES = ( ('browser-protocol.mjs', 10966, '85f50d640eaa5324cf62e75f5dc6050186bf167201208d26e84a49efd74bf499'), ('bytecode.mjs', 6836, 'e76fabeea879bc1da8016283f7e74df5974479666b100199c2d3c31a53b8ecf4'), ('class-policy.mjs', 5539, 'eb4fb02aba4f6c4fb237c4fd0bf5633d2b7e3d0de176d6cf8581690d8b1eceb4'), - ('class-values.mjs', 160246, '1c689847de2650caa32e1e667cca2b610caf9231fa1e79b65fb976077abda987'), + ('class-values.mjs', 160342, 'ffe4f7aee069f9155a82d6907f8532ccfeef158e268120bdd016be6616fa277b'), ('dynamic-evaluation.mjs', 6820, '44ad82dae7fa185c524e163440a96db953e800e96baa372249dd5e9d613b9eca'), ('dynamic-imports.mjs', 9307, '9996c3f3f7e82cd440cf16244176d37e9456c8dd9bbac2e5ac675cf22497c951'), ('dynamic-source.mjs', 5723, '9040a40e8906723f01f496f30aafd329816f823ecad158dd29db8e21b844bf2c'), diff --git a/tests/fixtures/interpreter/exceptions.json b/tests/fixtures/interpreter/exceptions.json index 7ebdbe0..4190507 100644 --- a/tests/fixtures/interpreter/exceptions.json +++ b/tests/fixtures/interpreter/exceptions.json @@ -56,7 +56,8 @@ "LA53", "LA54", "LA54A", - "LA55" + "LA55", + "LA56" ], "cases": [ { @@ -10797,6 +10798,348 @@ "source": "try:\n try: (2+3j).missing=7\n except AttributeError as e:\n e.add_note(\"retained\")\n raise e from ValueError(8)\nexcept AttributeError as e:\n e.args=()\n print(e.name,e.obj==2+3j,e.__cause__.args[0],e.__notes__[0],e.__suppress_context__)\n", "stage": "LA55", "stdout": "missing True 8 retained True\n" + }, + { + "id": "la56-created-write-missing-builtin", + "source": "def work():\n yield [7]\nobj=work()\nkey=\"missing\"\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA56", + "stdout": "True True False False\n" + }, + { + "id": "la56-created-write-missing-native", + "source": "def work():\n yield [7]\nobj=work()\nkey=\"missing\"\ntry: object.__setattr__(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA56", + "stdout": "True True False False\n" + }, + { + "id": "la56-created-write-gi_code-builtin", + "source": "def work():\n yield [7]\nobj=work()\nkey=\"gi_code\"\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA56", + "stdout": "False False True True\n" + }, + { + "id": "la56-created-write-gi_code-native", + "source": "def work():\n yield [7]\nobj=work()\nkey=\"gi_code\"\ntry: object.__setattr__(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA56", + "stdout": "False False True True\n" + }, + { + "id": "la56-created-write-subtype-key", + "source": "def work():\n yield [7]\nclass Key(str):\n def __hash__(self): raise ValueError(\"unexpected hash\")\n def __str__(self): raise ValueError(\"unexpected string\")\nobj=work()\nkey=Key(\"missing\")\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj)\n", + "stage": "LA56", + "stdout": "True True\n" + }, + { + "id": "la56-created-delete-missing-builtin", + "source": "def work():\n yield [7]\nobj=work()\nkey=\"missing\"\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA56", + "stdout": "True True False False\n" + }, + { + "id": "la56-created-delete-missing-native", + "source": "def work():\n yield [7]\nobj=work()\nkey=\"missing\"\ntry: object.__delattr__(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA56", + "stdout": "True True False False\n" + }, + { + "id": "la56-created-delete-gi_code-builtin", + "source": "def work():\n yield [7]\nobj=work()\nkey=\"gi_code\"\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA56", + "stdout": "False False True True\n" + }, + { + "id": "la56-created-delete-gi_code-native", + "source": "def work():\n yield [7]\nobj=work()\nkey=\"gi_code\"\ntry: object.__delattr__(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA56", + "stdout": "False False True True\n" + }, + { + "id": "la56-created-delete-subtype-key", + "source": "def work():\n yield [7]\nclass Key(str):\n def __hash__(self): raise ValueError(\"unexpected hash\")\n def __str__(self): raise ValueError(\"unexpected string\")\nobj=work()\nkey=Key(\"missing\")\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj)\n", + "stage": "LA56", + "stdout": "True True\n" + }, + { + "id": "la56-suspended-write-missing-builtin", + "source": "def work():\n yield [7]\ng=work()\nnext(g)\nobj=g\nkey=\"missing\"\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA56", + "stdout": "True True False False\n" + }, + { + "id": "la56-suspended-write-missing-native", + "source": "def work():\n yield [7]\ng=work()\nnext(g)\nobj=g\nkey=\"missing\"\ntry: object.__setattr__(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA56", + "stdout": "True True False False\n" + }, + { + "id": "la56-suspended-write-gi_frame-builtin", + "source": "def work():\n yield [7]\ng=work()\nnext(g)\nobj=g\nkey=\"gi_frame\"\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA56", + "stdout": "False False True True\n" + }, + { + "id": "la56-suspended-write-gi_frame-native", + "source": "def work():\n yield [7]\ng=work()\nnext(g)\nobj=g\nkey=\"gi_frame\"\ntry: object.__setattr__(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA56", + "stdout": "False False True True\n" + }, + { + "id": "la56-suspended-write-subtype-key", + "source": "def work():\n yield [7]\ng=work()\nnext(g)\nclass Key(str):\n def __hash__(self): raise ValueError(\"unexpected hash\")\n def __str__(self): raise ValueError(\"unexpected string\")\nobj=g\nkey=Key(\"missing\")\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj)\n", + "stage": "LA56", + "stdout": "True True\n" + }, + { + "id": "la56-suspended-delete-missing-builtin", + "source": "def work():\n yield [7]\ng=work()\nnext(g)\nobj=g\nkey=\"missing\"\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA56", + "stdout": "True True False False\n" + }, + { + "id": "la56-suspended-delete-missing-native", + "source": "def work():\n yield [7]\ng=work()\nnext(g)\nobj=g\nkey=\"missing\"\ntry: object.__delattr__(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA56", + "stdout": "True True False False\n" + }, + { + "id": "la56-suspended-delete-gi_frame-builtin", + "source": "def work():\n yield [7]\ng=work()\nnext(g)\nobj=g\nkey=\"gi_frame\"\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA56", + "stdout": "False False True True\n" + }, + { + "id": "la56-suspended-delete-gi_frame-native", + "source": "def work():\n yield [7]\ng=work()\nnext(g)\nobj=g\nkey=\"gi_frame\"\ntry: object.__delattr__(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA56", + "stdout": "False False True True\n" + }, + { + "id": "la56-suspended-delete-subtype-key", + "source": "def work():\n yield [7]\ng=work()\nnext(g)\nclass Key(str):\n def __hash__(self): raise ValueError(\"unexpected hash\")\n def __str__(self): raise ValueError(\"unexpected string\")\nobj=g\nkey=Key(\"missing\")\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj)\n", + "stage": "LA56", + "stdout": "True True\n" + }, + { + "id": "la56-closed-write-missing-builtin", + "source": "def work():\n yield [7]\ng=work()\ng.close()\nobj=g\nkey=\"missing\"\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA56", + "stdout": "True True False False\n" + }, + { + "id": "la56-closed-write-missing-native", + "source": "def work():\n yield [7]\ng=work()\ng.close()\nobj=g\nkey=\"missing\"\ntry: object.__setattr__(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA56", + "stdout": "True True False False\n" + }, + { + "id": "la56-closed-write-gi_suspended-builtin", + "source": "def work():\n yield [7]\ng=work()\ng.close()\nobj=g\nkey=\"gi_suspended\"\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA56", + "stdout": "False False True True\n" + }, + { + "id": "la56-closed-write-gi_suspended-native", + "source": "def work():\n yield [7]\ng=work()\ng.close()\nobj=g\nkey=\"gi_suspended\"\ntry: object.__setattr__(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA56", + "stdout": "False False True True\n" + }, + { + "id": "la56-closed-write-subtype-key", + "source": "def work():\n yield [7]\ng=work()\ng.close()\nclass Key(str):\n def __hash__(self): raise ValueError(\"unexpected hash\")\n def __str__(self): raise ValueError(\"unexpected string\")\nobj=g\nkey=Key(\"missing\")\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj)\n", + "stage": "LA56", + "stdout": "True True\n" + }, + { + "id": "la56-closed-delete-missing-builtin", + "source": "def work():\n yield [7]\ng=work()\ng.close()\nobj=g\nkey=\"missing\"\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA56", + "stdout": "True True False False\n" + }, + { + "id": "la56-closed-delete-missing-native", + "source": "def work():\n yield [7]\ng=work()\ng.close()\nobj=g\nkey=\"missing\"\ntry: object.__delattr__(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA56", + "stdout": "True True False False\n" + }, + { + "id": "la56-closed-delete-gi_suspended-builtin", + "source": "def work():\n yield [7]\ng=work()\ng.close()\nobj=g\nkey=\"gi_suspended\"\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA56", + "stdout": "False False True True\n" + }, + { + "id": "la56-closed-delete-gi_suspended-native", + "source": "def work():\n yield [7]\ng=work()\ng.close()\nobj=g\nkey=\"gi_suspended\"\ntry: object.__delattr__(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA56", + "stdout": "False False True True\n" + }, + { + "id": "la56-closed-delete-subtype-key", + "source": "def work():\n yield [7]\ng=work()\ng.close()\nclass Key(str):\n def __hash__(self): raise ValueError(\"unexpected hash\")\n def __str__(self): raise ValueError(\"unexpected string\")\nobj=g\nkey=Key(\"missing\")\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj)\n", + "stage": "LA56", + "stdout": "True True\n" + }, + { + "id": "la56-expression-write-missing-builtin", + "source": "obj=(x for x in [7])\nkey=\"missing\"\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA56", + "stdout": "True True False False\n" + }, + { + "id": "la56-expression-write-missing-native", + "source": "obj=(x for x in [7])\nkey=\"missing\"\ntry: object.__setattr__(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA56", + "stdout": "True True False False\n" + }, + { + "id": "la56-expression-write-gi_running-builtin", + "source": "obj=(x for x in [7])\nkey=\"gi_running\"\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA56", + "stdout": "False False True True\n" + }, + { + "id": "la56-expression-write-gi_running-native", + "source": "obj=(x for x in [7])\nkey=\"gi_running\"\ntry: object.__setattr__(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA56", + "stdout": "False False True True\n" + }, + { + "id": "la56-expression-write-subtype-key", + "source": "class Key(str):\n def __hash__(self): raise ValueError(\"unexpected hash\")\n def __str__(self): raise ValueError(\"unexpected string\")\nobj=(x for x in [7])\nkey=Key(\"missing\")\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj)\n", + "stage": "LA56", + "stdout": "True True\n" + }, + { + "id": "la56-expression-delete-missing-builtin", + "source": "obj=(x for x in [7])\nkey=\"missing\"\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA56", + "stdout": "True True False False\n" + }, + { + "id": "la56-expression-delete-missing-native", + "source": "obj=(x for x in [7])\nkey=\"missing\"\ntry: object.__delattr__(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA56", + "stdout": "True True False False\n" + }, + { + "id": "la56-expression-delete-gi_running-builtin", + "source": "obj=(x for x in [7])\nkey=\"gi_running\"\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA56", + "stdout": "False False True True\n" + }, + { + "id": "la56-expression-delete-gi_running-native", + "source": "obj=(x for x in [7])\nkey=\"gi_running\"\ntry: object.__delattr__(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA56", + "stdout": "False False True True\n" + }, + { + "id": "la56-expression-delete-subtype-key", + "source": "class Key(str):\n def __hash__(self): raise ValueError(\"unexpected hash\")\n def __str__(self): raise ValueError(\"unexpected string\")\nobj=(x for x in [7])\nkey=Key(\"missing\")\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj)\n", + "stage": "LA56", + "stdout": "True True\n" + }, + { + "id": "generator-direct-gi_code-write", + "source": "def work():\n yield [7]\ng=work()\ntry: g.gi_code=[]\nexcept AttributeError as e: print(e.name is None,e.obj is None)\n", + "stage": "LA56", + "stdout": "True True\n" + }, + { + "id": "generator-direct-gi_code-delete", + "source": "def work():\n yield [7]\ng=work()\ntry: del g.gi_code\nexcept AttributeError as e: print(e.name is None,e.obj is None)\n", + "stage": "LA56", + "stdout": "True True\n" + }, + { + "id": "generator-direct-gi_frame-write", + "source": "def work():\n yield [7]\ng=work()\ntry: g.gi_frame=[]\nexcept AttributeError as e: print(e.name is None,e.obj is None)\n", + "stage": "LA56", + "stdout": "True True\n" + }, + { + "id": "generator-direct-gi_frame-delete", + "source": "def work():\n yield [7]\ng=work()\ntry: del g.gi_frame\nexcept AttributeError as e: print(e.name is None,e.obj is None)\n", + "stage": "LA56", + "stdout": "True True\n" + }, + { + "id": "generator-direct-gi_suspended-write", + "source": "def work():\n yield [7]\ng=work()\ntry: g.gi_suspended=[]\nexcept AttributeError as e: print(e.name is None,e.obj is None)\n", + "stage": "LA56", + "stdout": "True True\n" + }, + { + "id": "generator-direct-gi_suspended-delete", + "source": "def work():\n yield [7]\ng=work()\ntry: del g.gi_suspended\nexcept AttributeError as e: print(e.name is None,e.obj is None)\n", + "stage": "LA56", + "stdout": "True True\n" + }, + { + "id": "generator-direct-gi_running-write", + "source": "def work():\n yield [7]\ng=work()\ntry: g.gi_running=[]\nexcept AttributeError as e: print(e.name is None,e.obj is None)\n", + "stage": "LA56", + "stdout": "True True\n" + }, + { + "id": "generator-direct-gi_running-delete", + "source": "def work():\n yield [7]\ng=work()\ntry: del g.gi_running\nexcept AttributeError as e: print(e.name is None,e.obj is None)\n", + "stage": "LA56", + "stdout": "True True\n" + }, + { + "id": "generator-direct-gi_yieldfrom-write", + "source": "def work():\n yield [7]\ng=work()\ntry: g.gi_yieldfrom=[]\nexcept AttributeError as e: print(e.name is None,e.obj is None)\n", + "stage": "LA56", + "stdout": "True True\n" + }, + { + "id": "generator-direct-gi_yieldfrom-delete", + "source": "def work():\n yield [7]\ng=work()\ntry: del g.gi_yieldfrom\nexcept AttributeError as e: print(e.name is None,e.obj is None)\n", + "stage": "LA56", + "stdout": "True True\n" + }, + { + "id": "generator-__name__-subtype-name-builtin", + "source": "def work():\n yield [7]\nclass Name(str):\n def __str__(self): raise ValueError(\"unexpected\")\ng=work()\nname=Name(\"renamed\")\nsetattr(g,\"__name__\",name)\nprint(getattr(g,\"__name__\") is name,next(g)[0])\n", + "stage": "LA56", + "stdout": "True 7\n" + }, + { + "id": "generator-__name__-subtype-name-native", + "source": "def work():\n yield [7]\nclass Name(str):\n def __str__(self): raise ValueError(\"unexpected\")\ng=work()\nname=Name(\"renamed\")\nobject.__setattr__(g,\"__name__\",name)\nprint(getattr(g,\"__name__\") is name,next(g)[0])\n", + "stage": "LA56", + "stdout": "True 7\n" + }, + { + "id": "generator-__name__-validation", + "source": "def work():\n yield [7]\ng=work()\noriginal=getattr(g,\"__name__\")\nfor value in [None,7,[]]:\n try: setattr(g,\"__name__\",value)\n except TypeError: print(getattr(g,\"__name__\") is original)\ntry: delattr(g,\"__name__\")\nexcept TypeError: print(getattr(g,\"__name__\") is original)\n", + "stage": "LA56", + "stdout": "True\nTrue\nTrue\nTrue\n" + }, + { + "id": "generator-__qualname__-subtype-name-builtin", + "source": "def work():\n yield [7]\nclass Name(str):\n def __str__(self): raise ValueError(\"unexpected\")\ng=work()\nname=Name(\"renamed\")\nsetattr(g,\"__qualname__\",name)\nprint(getattr(g,\"__qualname__\") is name,next(g)[0])\n", + "stage": "LA56", + "stdout": "True 7\n" + }, + { + "id": "generator-__qualname__-subtype-name-native", + "source": "def work():\n yield [7]\nclass Name(str):\n def __str__(self): raise ValueError(\"unexpected\")\ng=work()\nname=Name(\"renamed\")\nobject.__setattr__(g,\"__qualname__\",name)\nprint(getattr(g,\"__qualname__\") is name,next(g)[0])\n", + "stage": "LA56", + "stdout": "True 7\n" + }, + { + "id": "generator-__qualname__-validation", + "source": "def work():\n yield [7]\ng=work()\noriginal=getattr(g,\"__qualname__\")\nfor value in [None,7,[]]:\n try: setattr(g,\"__qualname__\",value)\n except TypeError: print(getattr(g,\"__qualname__\") is original)\ntry: delattr(g,\"__qualname__\")\nexcept TypeError: print(getattr(g,\"__qualname__\") is original)\n", + "stage": "LA56", + "stdout": "True\nTrue\nTrue\nTrue\n" + }, + { + "id": "generator-direct-native-descriptor", + "source": "def work():\n yield [7]\ng=work()\nfor name in [\"gi_frame\",\"gi_code\",\"gi_running\"]:\n descriptor=type(g).__dict__[name]\n for remove in [False,True]:\n try:\n if remove: descriptor.__delete__(g)\n else: descriptor.__set__(g,[])\n except AttributeError as e: print(e.name is None,e.obj is None)\n", + "stage": "LA56", + "stdout": "True True\nTrue True\nTrue True\nTrue True\nTrue True\nTrue True\n" } ], "reference_boundaries": [ From ace4f0548436e596f83e7bb266230d3934f3b905 Mon Sep 17 00:00:00 2001 From: ellainng <299958882+ellainng@users.noreply.github.com> Date: Sun, 1 Nov 2026 18:37:36 -0800 Subject: [PATCH 6/9] feat(LA-57): adopt iterator mutation failure metadata --- docs/interpreter-exceptions.md | 10 + runtime/class-values.mjs | 2 + runtime/exceptions.test.mjs | 20 + src/pocket_python/_interpreter_lock.py | 4 +- tests/fixtures/interpreter/exceptions.json | 1299 +++++++++++++++++++- 5 files changed, 1332 insertions(+), 3 deletions(-) diff --git a/docs/interpreter-exceptions.md b/docs/interpreter-exceptions.md index d3dd6ef..a33a160 100644 --- a/docs/interpreter-exceptions.md +++ b/docs/interpreter-exceptions.md @@ -534,3 +534,13 @@ ordinary ownership/allocation rules. Frame tracing/jumping and wider generator throw remain separately gated. The pinned [generator implementation](https://github.com/python/cpython/blob/v3.14.7/Objects/genobject.c) defines the native state and name descriptors. + +LA-57 adopts mutation failure metadata for native list/tuple/text/range, +dictionary/set/binary/buffer, indexed and callable iterators. Missing names +retain the original iterator/key; existing native attributes leave fields +absent. Native object wrappers and direct syntax agree before and after +exhaustion. The 216 reference cases include both text layouts and both range +layouts. Retained errors keep detached dictionary subtype owners, memoryview +storage and indexed/callable sources alive and release cycles on close or +interruption. New iterator APIs and protocols remain under their existing +owners; core mutation admission stays unchanged. diff --git a/runtime/class-values.mjs b/runtime/class-values.mjs index a38a4b1..d62bb81 100644 --- a/runtime/class-values.mjs +++ b/runtime/class-values.mjs @@ -1106,6 +1106,8 @@ export class ClassRuntime { } if (receiver instanceof GeneratorValue) this.fixedMutationFailure(receiver, name, key, "Generator state is read-only"); + if (receiver instanceof IteratorValue || receiver instanceof ProtocolIteratorValue && ["indexed", "callable"].includes(receiver.kind)) + this.fixedMutationFailure(receiver, name, key, "Iterator attributes are read-only"); if (receiver instanceof FrameValue || receiver instanceof FrameLocalsValue) this.fail(receiver instanceof FrameValue && ["f_lineno", "f_trace"].includes(name) ? "UnsupportedOperationError" : "AttributeError", "Frame metadata is read-only; tracing and jumping are separate", receiver instanceof FrameValue && ["f_lineno", "f_trace"].includes(name) ? "UNSUPPORTED" : "PYTHON"); if (receiver instanceof Cell) { diff --git a/runtime/exceptions.test.mjs b/runtime/exceptions.test.mjs index 0e0c14a..c038458 100644 --- a/runtime/exceptions.test.mjs +++ b/runtime/exceptions.test.mjs @@ -97,6 +97,26 @@ test('retained generator mutation graphs share the allocation limit', async () = assert.equal(result.stdout, 'ready\n'); assert.equal(result.error?.type, 'MemoryError'); closed(result.closed); }); +test('iterator mutation errors retain detached dictionary subtype owners and cycles', async () => { + await lifetime('class D(dict): pass\nclass Key(str): pass\nd=D()\nd["value"]=d\nkey=Key("missing")\ntry: setattr(iter(d.values()),key,[])\nexcept AttributeError as error: e=error\nd.error=e\nkey.error=e\ne.args=()\nd=None\nkey=None\nprint("entered")\nprint(e.name.error is e,next(e.obj).error is e)\n', 'entered\nTrue True\n'); +}); + +for (const kind of ['indexed', 'callable']) test(kind + ' iterator mutation errors retain source objects and cycles', async () => { + const setup = kind === 'indexed' ? 'class Source:\n def __getitem__(self,i): return self\nsource=Source()\niterator=iter(source)\n' : + 'class Source:\n def __call__(self): return self\nsource=Source()\niterator=iter(source,None)\n'; + await lifetime(setup + 'try: iterator.missing=7\nexcept AttributeError as error: e=error\nsource.error=e\ne.args=()\nsource=None\niterator=None\nprint("entered")\nprint(e.name,next(e.obj).error is e)\n', 'entered\nmissing True\n'); +}); + +test('iterator mutation errors retain temporary memoryview backing storage', async () => { + await lifetime('try: iter(memoryview(bytearray(b"abc"))).missing=7\nexcept AttributeError as error: e=error\ne.args=()\nprint("entered")\nprint(e.name,next(e.obj))\n', 'entered\nmissing 97\n'); +}); + +test('retained iterator mutation graphs share the allocation limit', async () => { + const result = await runSourceRequest({modules: [unit('held=[]\nprint("ready")\nwhile True:\n try: iter([7]).missing=[]\n except AttributeError as e: held.append(e)\n')], + compileOptions: {languageProfile: corpus.profile}, options: {maxWork: 20000000}, memoryLimits: {maxBytes: 48000}}); + assert.equal(result.stdout, 'ready\n'); assert.equal(result.error?.type, 'MemoryError'); closed(result.closed); +}); + test('module mutation errors retain cyclic keys and the module after args replacement', async () => { await lifetime('import helper\nclass Key(str): pass\nkey=Key("missing")\ntry: delattr(helper,key)\nexcept AttributeError as error: e=error\nkey.error=e\ne.args=()\nhelper=None\nkey=None\nprint("entered")\nprint(e.name.error is e,e.obj.marker)\n', 'entered\nTrue 7\n', [{name: 'helper', source: 'marker=7\n'}]); diff --git a/src/pocket_python/_interpreter_lock.py b/src/pocket_python/_interpreter_lock.py index a4026e6..b0e5ecc 100644 --- a/src/pocket_python/_interpreter_lock.py +++ b/src/pocket_python/_interpreter_lock.py @@ -1,11 +1,11 @@ """Known source-console assets, canonical LF bytes; refresh with scripts/lock_interpreter.py.""" -ENGINE_FINGERPRINT = 'f672af08dbad2d0fb5dcceb585fc8e74cce675abf2d4c7533bbc0ff1d18b3107' +ENGINE_FINGERPRINT = 'c1224e520967b327d84e3232412cbb0260dacef6a3285b2b6ecc24c31838ecfd' ENGINE_FILES = ( ('browser-protocol.mjs', 10966, '85f50d640eaa5324cf62e75f5dc6050186bf167201208d26e84a49efd74bf499'), ('bytecode.mjs', 6836, 'e76fabeea879bc1da8016283f7e74df5974479666b100199c2d3c31a53b8ecf4'), ('class-policy.mjs', 5539, 'eb4fb02aba4f6c4fb237c4fd0bf5633d2b7e3d0de176d6cf8581690d8b1eceb4'), - ('class-values.mjs', 160342, 'ffe4f7aee069f9155a82d6907f8532ccfeef158e268120bdd016be6616fa277b'), + ('class-values.mjs', 160572, 'ede30331439414efce9f485395209ac3eb035796f5c53065deb8ee390e83d870'), ('dynamic-evaluation.mjs', 6820, '44ad82dae7fa185c524e163440a96db953e800e96baa372249dd5e9d613b9eca'), ('dynamic-imports.mjs', 9307, '9996c3f3f7e82cd440cf16244176d37e9456c8dd9bbac2e5ac675cf22497c951'), ('dynamic-source.mjs', 5723, '9040a40e8906723f01f496f30aafd329816f823ecad158dd29db8e21b844bf2c'), diff --git a/tests/fixtures/interpreter/exceptions.json b/tests/fixtures/interpreter/exceptions.json index 4190507..13ae78a 100644 --- a/tests/fixtures/interpreter/exceptions.json +++ b/tests/fixtures/interpreter/exceptions.json @@ -57,7 +57,8 @@ "LA54", "LA54A", "LA55", - "LA56" + "LA56", + "LA57" ], "cases": [ { @@ -11140,6 +11141,1302 @@ "source": "def work():\n yield [7]\ng=work()\nfor name in [\"gi_frame\",\"gi_code\",\"gi_running\"]:\n descriptor=type(g).__dict__[name]\n for remove in [False,True]:\n try:\n if remove: descriptor.__delete__(g)\n else: descriptor.__set__(g,[])\n except AttributeError as e: print(e.name is None,e.obj is None)\n", "stage": "LA56", "stdout": "True True\nTrue True\nTrue True\nTrue True\nTrue True\nTrue True\n" + }, + { + "id": "la57-list-write-missing-builtin", + "source": "obj=iter([7])\nkey=\"missing\"\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "True True False False\n" + }, + { + "id": "la57-list-write-missing-native", + "source": "obj=iter([7])\nkey=\"missing\"\ntry: object.__setattr__(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "True True False False\n" + }, + { + "id": "la57-list-write-__doc__-builtin", + "source": "obj=iter([7])\nkey=\"__doc__\"\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "False False True True\n" + }, + { + "id": "la57-list-write-__doc__-native", + "source": "obj=iter([7])\nkey=\"__doc__\"\ntry: object.__setattr__(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "False False True True\n" + }, + { + "id": "la57-list-write-subtype-key", + "source": "class Key(str):\n def __hash__(self): raise ValueError(\"unexpected hash\")\n def __str__(self): raise ValueError(\"unexpected string\")\nobj=iter([7])\nkey=Key(\"missing\")\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj)\n", + "stage": "LA57", + "stdout": "True True\n" + }, + { + "id": "la57-list-delete-missing-builtin", + "source": "obj=iter([7])\nkey=\"missing\"\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "True True False False\n" + }, + { + "id": "la57-list-delete-missing-native", + "source": "obj=iter([7])\nkey=\"missing\"\ntry: object.__delattr__(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "True True False False\n" + }, + { + "id": "la57-list-delete-__doc__-builtin", + "source": "obj=iter([7])\nkey=\"__doc__\"\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "False False True True\n" + }, + { + "id": "la57-list-delete-__doc__-native", + "source": "obj=iter([7])\nkey=\"__doc__\"\ntry: object.__delattr__(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "False False True True\n" + }, + { + "id": "la57-list-delete-subtype-key", + "source": "class Key(str):\n def __hash__(self): raise ValueError(\"unexpected hash\")\n def __str__(self): raise ValueError(\"unexpected string\")\nobj=iter([7])\nkey=Key(\"missing\")\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj)\n", + "stage": "LA57", + "stdout": "True True\n" + }, + { + "id": "la57-tuple-write-missing-builtin", + "source": "obj=iter((7,))\nkey=\"missing\"\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "True True False False\n" + }, + { + "id": "la57-tuple-write-missing-native", + "source": "obj=iter((7,))\nkey=\"missing\"\ntry: object.__setattr__(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "True True False False\n" + }, + { + "id": "la57-tuple-write-__doc__-builtin", + "source": "obj=iter((7,))\nkey=\"__doc__\"\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "False False True True\n" + }, + { + "id": "la57-tuple-write-__doc__-native", + "source": "obj=iter((7,))\nkey=\"__doc__\"\ntry: object.__setattr__(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "False False True True\n" + }, + { + "id": "la57-tuple-write-subtype-key", + "source": "class Key(str):\n def __hash__(self): raise ValueError(\"unexpected hash\")\n def __str__(self): raise ValueError(\"unexpected string\")\nobj=iter((7,))\nkey=Key(\"missing\")\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj)\n", + "stage": "LA57", + "stdout": "True True\n" + }, + { + "id": "la57-tuple-delete-missing-builtin", + "source": "obj=iter((7,))\nkey=\"missing\"\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "True True False False\n" + }, + { + "id": "la57-tuple-delete-missing-native", + "source": "obj=iter((7,))\nkey=\"missing\"\ntry: object.__delattr__(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "True True False False\n" + }, + { + "id": "la57-tuple-delete-__doc__-builtin", + "source": "obj=iter((7,))\nkey=\"__doc__\"\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "False False True True\n" + }, + { + "id": "la57-tuple-delete-__doc__-native", + "source": "obj=iter((7,))\nkey=\"__doc__\"\ntry: object.__delattr__(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "False False True True\n" + }, + { + "id": "la57-tuple-delete-subtype-key", + "source": "class Key(str):\n def __hash__(self): raise ValueError(\"unexpected hash\")\n def __str__(self): raise ValueError(\"unexpected string\")\nobj=iter((7,))\nkey=Key(\"missing\")\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj)\n", + "stage": "LA57", + "stdout": "True True\n" + }, + { + "id": "la57-ascii-write-missing-builtin", + "source": "obj=iter(\"ab\")\nkey=\"missing\"\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "True True False False\n" + }, + { + "id": "la57-ascii-write-missing-native", + "source": "obj=iter(\"ab\")\nkey=\"missing\"\ntry: object.__setattr__(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "True True False False\n" + }, + { + "id": "la57-ascii-write-__doc__-builtin", + "source": "obj=iter(\"ab\")\nkey=\"__doc__\"\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "False False True True\n" + }, + { + "id": "la57-ascii-write-__doc__-native", + "source": "obj=iter(\"ab\")\nkey=\"__doc__\"\ntry: object.__setattr__(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "False False True True\n" + }, + { + "id": "la57-ascii-write-subtype-key", + "source": "class Key(str):\n def __hash__(self): raise ValueError(\"unexpected hash\")\n def __str__(self): raise ValueError(\"unexpected string\")\nobj=iter(\"ab\")\nkey=Key(\"missing\")\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj)\n", + "stage": "LA57", + "stdout": "True True\n" + }, + { + "id": "la57-ascii-delete-missing-builtin", + "source": "obj=iter(\"ab\")\nkey=\"missing\"\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "True True False False\n" + }, + { + "id": "la57-ascii-delete-missing-native", + "source": "obj=iter(\"ab\")\nkey=\"missing\"\ntry: object.__delattr__(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "True True False False\n" + }, + { + "id": "la57-ascii-delete-__doc__-builtin", + "source": "obj=iter(\"ab\")\nkey=\"__doc__\"\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "False False True True\n" + }, + { + "id": "la57-ascii-delete-__doc__-native", + "source": "obj=iter(\"ab\")\nkey=\"__doc__\"\ntry: object.__delattr__(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "False False True True\n" + }, + { + "id": "la57-ascii-delete-subtype-key", + "source": "class Key(str):\n def __hash__(self): raise ValueError(\"unexpected hash\")\n def __str__(self): raise ValueError(\"unexpected string\")\nobj=iter(\"ab\")\nkey=Key(\"missing\")\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj)\n", + "stage": "LA57", + "stdout": "True True\n" + }, + { + "id": "la57-unicode-write-missing-builtin", + "source": "obj=iter(\"\u03bb\u96ea\")\nkey=\"missing\"\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "True True False False\n" + }, + { + "id": "la57-unicode-write-missing-native", + "source": "obj=iter(\"\u03bb\u96ea\")\nkey=\"missing\"\ntry: object.__setattr__(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "True True False False\n" + }, + { + "id": "la57-unicode-write-__doc__-builtin", + "source": "obj=iter(\"\u03bb\u96ea\")\nkey=\"__doc__\"\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "False False True True\n" + }, + { + "id": "la57-unicode-write-__doc__-native", + "source": "obj=iter(\"\u03bb\u96ea\")\nkey=\"__doc__\"\ntry: object.__setattr__(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "False False True True\n" + }, + { + "id": "la57-unicode-write-subtype-key", + "source": "class Key(str):\n def __hash__(self): raise ValueError(\"unexpected hash\")\n def __str__(self): raise ValueError(\"unexpected string\")\nobj=iter(\"\u03bb\u96ea\")\nkey=Key(\"missing\")\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj)\n", + "stage": "LA57", + "stdout": "True True\n" + }, + { + "id": "la57-unicode-delete-missing-builtin", + "source": "obj=iter(\"\u03bb\u96ea\")\nkey=\"missing\"\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "True True False False\n" + }, + { + "id": "la57-unicode-delete-missing-native", + "source": "obj=iter(\"\u03bb\u96ea\")\nkey=\"missing\"\ntry: object.__delattr__(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "True True False False\n" + }, + { + "id": "la57-unicode-delete-__doc__-builtin", + "source": "obj=iter(\"\u03bb\u96ea\")\nkey=\"__doc__\"\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "False False True True\n" + }, + { + "id": "la57-unicode-delete-__doc__-native", + "source": "obj=iter(\"\u03bb\u96ea\")\nkey=\"__doc__\"\ntry: object.__delattr__(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "False False True True\n" + }, + { + "id": "la57-unicode-delete-subtype-key", + "source": "class Key(str):\n def __hash__(self): raise ValueError(\"unexpected hash\")\n def __str__(self): raise ValueError(\"unexpected string\")\nobj=iter(\"\u03bb\u96ea\")\nkey=Key(\"missing\")\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj)\n", + "stage": "LA57", + "stdout": "True True\n" + }, + { + "id": "la57-range-write-missing-builtin", + "source": "obj=iter(range(3))\nkey=\"missing\"\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "True True False False\n" + }, + { + "id": "la57-range-write-missing-native", + "source": "obj=iter(range(3))\nkey=\"missing\"\ntry: object.__setattr__(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "True True False False\n" + }, + { + "id": "la57-range-write-__doc__-builtin", + "source": "obj=iter(range(3))\nkey=\"__doc__\"\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "False False True True\n" + }, + { + "id": "la57-range-write-__doc__-native", + "source": "obj=iter(range(3))\nkey=\"__doc__\"\ntry: object.__setattr__(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "False False True True\n" + }, + { + "id": "la57-range-write-subtype-key", + "source": "class Key(str):\n def __hash__(self): raise ValueError(\"unexpected hash\")\n def __str__(self): raise ValueError(\"unexpected string\")\nobj=iter(range(3))\nkey=Key(\"missing\")\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj)\n", + "stage": "LA57", + "stdout": "True True\n" + }, + { + "id": "la57-range-delete-missing-builtin", + "source": "obj=iter(range(3))\nkey=\"missing\"\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "True True False False\n" + }, + { + "id": "la57-range-delete-missing-native", + "source": "obj=iter(range(3))\nkey=\"missing\"\ntry: object.__delattr__(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "True True False False\n" + }, + { + "id": "la57-range-delete-__doc__-builtin", + "source": "obj=iter(range(3))\nkey=\"__doc__\"\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "False False True True\n" + }, + { + "id": "la57-range-delete-__doc__-native", + "source": "obj=iter(range(3))\nkey=\"__doc__\"\ntry: object.__delattr__(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "False False True True\n" + }, + { + "id": "la57-range-delete-subtype-key", + "source": "class Key(str):\n def __hash__(self): raise ValueError(\"unexpected hash\")\n def __str__(self): raise ValueError(\"unexpected string\")\nobj=iter(range(3))\nkey=Key(\"missing\")\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj)\n", + "stage": "LA57", + "stdout": "True True\n" + }, + { + "id": "la57-longrange-write-missing-builtin", + "source": "obj=iter(range(10**25,10**25+3))\nkey=\"missing\"\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "True True False False\n" + }, + { + "id": "la57-longrange-write-missing-native", + "source": "obj=iter(range(10**25,10**25+3))\nkey=\"missing\"\ntry: object.__setattr__(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "True True False False\n" + }, + { + "id": "la57-longrange-write-__doc__-builtin", + "source": "obj=iter(range(10**25,10**25+3))\nkey=\"__doc__\"\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "False False True True\n" + }, + { + "id": "la57-longrange-write-__doc__-native", + "source": "obj=iter(range(10**25,10**25+3))\nkey=\"__doc__\"\ntry: object.__setattr__(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "False False True True\n" + }, + { + "id": "la57-longrange-write-subtype-key", + "source": "class Key(str):\n def __hash__(self): raise ValueError(\"unexpected hash\")\n def __str__(self): raise ValueError(\"unexpected string\")\nobj=iter(range(10**25,10**25+3))\nkey=Key(\"missing\")\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj)\n", + "stage": "LA57", + "stdout": "True True\n" + }, + { + "id": "la57-longrange-delete-missing-builtin", + "source": "obj=iter(range(10**25,10**25+3))\nkey=\"missing\"\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "True True False False\n" + }, + { + "id": "la57-longrange-delete-missing-native", + "source": "obj=iter(range(10**25,10**25+3))\nkey=\"missing\"\ntry: object.__delattr__(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "True True False False\n" + }, + { + "id": "la57-longrange-delete-__doc__-builtin", + "source": "obj=iter(range(10**25,10**25+3))\nkey=\"__doc__\"\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "False False True True\n" + }, + { + "id": "la57-longrange-delete-__doc__-native", + "source": "obj=iter(range(10**25,10**25+3))\nkey=\"__doc__\"\ntry: object.__delattr__(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "False False True True\n" + }, + { + "id": "la57-longrange-delete-subtype-key", + "source": "class Key(str):\n def __hash__(self): raise ValueError(\"unexpected hash\")\n def __str__(self): raise ValueError(\"unexpected string\")\nobj=iter(range(10**25,10**25+3))\nkey=Key(\"missing\")\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj)\n", + "stage": "LA57", + "stdout": "True True\n" + }, + { + "id": "la57-dict-keys-write-missing-builtin", + "source": "obj=iter({\"field\":7})\nkey=\"missing\"\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "True True False False\n" + }, + { + "id": "la57-dict-keys-write-missing-native", + "source": "obj=iter({\"field\":7})\nkey=\"missing\"\ntry: object.__setattr__(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "True True False False\n" + }, + { + "id": "la57-dict-keys-write-__doc__-builtin", + "source": "obj=iter({\"field\":7})\nkey=\"__doc__\"\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "False False True True\n" + }, + { + "id": "la57-dict-keys-write-__doc__-native", + "source": "obj=iter({\"field\":7})\nkey=\"__doc__\"\ntry: object.__setattr__(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "False False True True\n" + }, + { + "id": "la57-dict-keys-write-subtype-key", + "source": "class Key(str):\n def __hash__(self): raise ValueError(\"unexpected hash\")\n def __str__(self): raise ValueError(\"unexpected string\")\nobj=iter({\"field\":7})\nkey=Key(\"missing\")\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj)\n", + "stage": "LA57", + "stdout": "True True\n" + }, + { + "id": "la57-dict-keys-delete-missing-builtin", + "source": "obj=iter({\"field\":7})\nkey=\"missing\"\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "True True False False\n" + }, + { + "id": "la57-dict-keys-delete-missing-native", + "source": "obj=iter({\"field\":7})\nkey=\"missing\"\ntry: object.__delattr__(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "True True False False\n" + }, + { + "id": "la57-dict-keys-delete-__doc__-builtin", + "source": "obj=iter({\"field\":7})\nkey=\"__doc__\"\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "False False True True\n" + }, + { + "id": "la57-dict-keys-delete-__doc__-native", + "source": "obj=iter({\"field\":7})\nkey=\"__doc__\"\ntry: object.__delattr__(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "False False True True\n" + }, + { + "id": "la57-dict-keys-delete-subtype-key", + "source": "class Key(str):\n def __hash__(self): raise ValueError(\"unexpected hash\")\n def __str__(self): raise ValueError(\"unexpected string\")\nobj=iter({\"field\":7})\nkey=Key(\"missing\")\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj)\n", + "stage": "LA57", + "stdout": "True True\n" + }, + { + "id": "la57-dict-values-write-missing-builtin", + "source": "obj=iter({\"field\":7}.values())\nkey=\"missing\"\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "True True False False\n" + }, + { + "id": "la57-dict-values-write-missing-native", + "source": "obj=iter({\"field\":7}.values())\nkey=\"missing\"\ntry: object.__setattr__(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "True True False False\n" + }, + { + "id": "la57-dict-values-write-__doc__-builtin", + "source": "obj=iter({\"field\":7}.values())\nkey=\"__doc__\"\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "False False True True\n" + }, + { + "id": "la57-dict-values-write-__doc__-native", + "source": "obj=iter({\"field\":7}.values())\nkey=\"__doc__\"\ntry: object.__setattr__(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "False False True True\n" + }, + { + "id": "la57-dict-values-write-subtype-key", + "source": "class Key(str):\n def __hash__(self): raise ValueError(\"unexpected hash\")\n def __str__(self): raise ValueError(\"unexpected string\")\nobj=iter({\"field\":7}.values())\nkey=Key(\"missing\")\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj)\n", + "stage": "LA57", + "stdout": "True True\n" + }, + { + "id": "la57-dict-values-delete-missing-builtin", + "source": "obj=iter({\"field\":7}.values())\nkey=\"missing\"\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "True True False False\n" + }, + { + "id": "la57-dict-values-delete-missing-native", + "source": "obj=iter({\"field\":7}.values())\nkey=\"missing\"\ntry: object.__delattr__(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "True True False False\n" + }, + { + "id": "la57-dict-values-delete-__doc__-builtin", + "source": "obj=iter({\"field\":7}.values())\nkey=\"__doc__\"\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "False False True True\n" + }, + { + "id": "la57-dict-values-delete-__doc__-native", + "source": "obj=iter({\"field\":7}.values())\nkey=\"__doc__\"\ntry: object.__delattr__(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "False False True True\n" + }, + { + "id": "la57-dict-values-delete-subtype-key", + "source": "class Key(str):\n def __hash__(self): raise ValueError(\"unexpected hash\")\n def __str__(self): raise ValueError(\"unexpected string\")\nobj=iter({\"field\":7}.values())\nkey=Key(\"missing\")\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj)\n", + "stage": "LA57", + "stdout": "True True\n" + }, + { + "id": "la57-dict-items-write-missing-builtin", + "source": "obj=iter({\"field\":7}.items())\nkey=\"missing\"\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "True True False False\n" + }, + { + "id": "la57-dict-items-write-missing-native", + "source": "obj=iter({\"field\":7}.items())\nkey=\"missing\"\ntry: object.__setattr__(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "True True False False\n" + }, + { + "id": "la57-dict-items-write-__doc__-builtin", + "source": "obj=iter({\"field\":7}.items())\nkey=\"__doc__\"\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "False False True True\n" + }, + { + "id": "la57-dict-items-write-__doc__-native", + "source": "obj=iter({\"field\":7}.items())\nkey=\"__doc__\"\ntry: object.__setattr__(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "False False True True\n" + }, + { + "id": "la57-dict-items-write-subtype-key", + "source": "class Key(str):\n def __hash__(self): raise ValueError(\"unexpected hash\")\n def __str__(self): raise ValueError(\"unexpected string\")\nobj=iter({\"field\":7}.items())\nkey=Key(\"missing\")\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj)\n", + "stage": "LA57", + "stdout": "True True\n" + }, + { + "id": "la57-dict-items-delete-missing-builtin", + "source": "obj=iter({\"field\":7}.items())\nkey=\"missing\"\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "True True False False\n" + }, + { + "id": "la57-dict-items-delete-missing-native", + "source": "obj=iter({\"field\":7}.items())\nkey=\"missing\"\ntry: object.__delattr__(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "True True False False\n" + }, + { + "id": "la57-dict-items-delete-__doc__-builtin", + "source": "obj=iter({\"field\":7}.items())\nkey=\"__doc__\"\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "False False True True\n" + }, + { + "id": "la57-dict-items-delete-__doc__-native", + "source": "obj=iter({\"field\":7}.items())\nkey=\"__doc__\"\ntry: object.__delattr__(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "False False True True\n" + }, + { + "id": "la57-dict-items-delete-subtype-key", + "source": "class Key(str):\n def __hash__(self): raise ValueError(\"unexpected hash\")\n def __str__(self): raise ValueError(\"unexpected string\")\nobj=iter({\"field\":7}.items())\nkey=Key(\"missing\")\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj)\n", + "stage": "LA57", + "stdout": "True True\n" + }, + { + "id": "la57-set-write-missing-builtin", + "source": "obj=iter({7})\nkey=\"missing\"\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "True True False False\n" + }, + { + "id": "la57-set-write-missing-native", + "source": "obj=iter({7})\nkey=\"missing\"\ntry: object.__setattr__(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "True True False False\n" + }, + { + "id": "la57-set-write-__doc__-builtin", + "source": "obj=iter({7})\nkey=\"__doc__\"\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "False False True True\n" + }, + { + "id": "la57-set-write-__doc__-native", + "source": "obj=iter({7})\nkey=\"__doc__\"\ntry: object.__setattr__(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "False False True True\n" + }, + { + "id": "la57-set-write-subtype-key", + "source": "class Key(str):\n def __hash__(self): raise ValueError(\"unexpected hash\")\n def __str__(self): raise ValueError(\"unexpected string\")\nobj=iter({7})\nkey=Key(\"missing\")\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj)\n", + "stage": "LA57", + "stdout": "True True\n" + }, + { + "id": "la57-set-delete-missing-builtin", + "source": "obj=iter({7})\nkey=\"missing\"\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "True True False False\n" + }, + { + "id": "la57-set-delete-missing-native", + "source": "obj=iter({7})\nkey=\"missing\"\ntry: object.__delattr__(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "True True False False\n" + }, + { + "id": "la57-set-delete-__doc__-builtin", + "source": "obj=iter({7})\nkey=\"__doc__\"\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "False False True True\n" + }, + { + "id": "la57-set-delete-__doc__-native", + "source": "obj=iter({7})\nkey=\"__doc__\"\ntry: object.__delattr__(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "False False True True\n" + }, + { + "id": "la57-set-delete-subtype-key", + "source": "class Key(str):\n def __hash__(self): raise ValueError(\"unexpected hash\")\n def __str__(self): raise ValueError(\"unexpected string\")\nobj=iter({7})\nkey=Key(\"missing\")\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj)\n", + "stage": "LA57", + "stdout": "True True\n" + }, + { + "id": "la57-frozenset-write-missing-builtin", + "source": "obj=iter(frozenset([7]))\nkey=\"missing\"\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "True True False False\n" + }, + { + "id": "la57-frozenset-write-missing-native", + "source": "obj=iter(frozenset([7]))\nkey=\"missing\"\ntry: object.__setattr__(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "True True False False\n" + }, + { + "id": "la57-frozenset-write-__doc__-builtin", + "source": "obj=iter(frozenset([7]))\nkey=\"__doc__\"\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "False False True True\n" + }, + { + "id": "la57-frozenset-write-__doc__-native", + "source": "obj=iter(frozenset([7]))\nkey=\"__doc__\"\ntry: object.__setattr__(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "False False True True\n" + }, + { + "id": "la57-frozenset-write-subtype-key", + "source": "class Key(str):\n def __hash__(self): raise ValueError(\"unexpected hash\")\n def __str__(self): raise ValueError(\"unexpected string\")\nobj=iter(frozenset([7]))\nkey=Key(\"missing\")\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj)\n", + "stage": "LA57", + "stdout": "True True\n" + }, + { + "id": "la57-frozenset-delete-missing-builtin", + "source": "obj=iter(frozenset([7]))\nkey=\"missing\"\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "True True False False\n" + }, + { + "id": "la57-frozenset-delete-missing-native", + "source": "obj=iter(frozenset([7]))\nkey=\"missing\"\ntry: object.__delattr__(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "True True False False\n" + }, + { + "id": "la57-frozenset-delete-__doc__-builtin", + "source": "obj=iter(frozenset([7]))\nkey=\"__doc__\"\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "False False True True\n" + }, + { + "id": "la57-frozenset-delete-__doc__-native", + "source": "obj=iter(frozenset([7]))\nkey=\"__doc__\"\ntry: object.__delattr__(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "False False True True\n" + }, + { + "id": "la57-frozenset-delete-subtype-key", + "source": "class Key(str):\n def __hash__(self): raise ValueError(\"unexpected hash\")\n def __str__(self): raise ValueError(\"unexpected string\")\nobj=iter(frozenset([7]))\nkey=Key(\"missing\")\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj)\n", + "stage": "LA57", + "stdout": "True True\n" + }, + { + "id": "la57-bytes-write-missing-builtin", + "source": "obj=iter(b\"abc\")\nkey=\"missing\"\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "True True False False\n" + }, + { + "id": "la57-bytes-write-missing-native", + "source": "obj=iter(b\"abc\")\nkey=\"missing\"\ntry: object.__setattr__(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "True True False False\n" + }, + { + "id": "la57-bytes-write-__doc__-builtin", + "source": "obj=iter(b\"abc\")\nkey=\"__doc__\"\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "False False True True\n" + }, + { + "id": "la57-bytes-write-__doc__-native", + "source": "obj=iter(b\"abc\")\nkey=\"__doc__\"\ntry: object.__setattr__(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "False False True True\n" + }, + { + "id": "la57-bytes-write-subtype-key", + "source": "class Key(str):\n def __hash__(self): raise ValueError(\"unexpected hash\")\n def __str__(self): raise ValueError(\"unexpected string\")\nobj=iter(b\"abc\")\nkey=Key(\"missing\")\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj)\n", + "stage": "LA57", + "stdout": "True True\n" + }, + { + "id": "la57-bytes-delete-missing-builtin", + "source": "obj=iter(b\"abc\")\nkey=\"missing\"\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "True True False False\n" + }, + { + "id": "la57-bytes-delete-missing-native", + "source": "obj=iter(b\"abc\")\nkey=\"missing\"\ntry: object.__delattr__(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "True True False False\n" + }, + { + "id": "la57-bytes-delete-__doc__-builtin", + "source": "obj=iter(b\"abc\")\nkey=\"__doc__\"\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "False False True True\n" + }, + { + "id": "la57-bytes-delete-__doc__-native", + "source": "obj=iter(b\"abc\")\nkey=\"__doc__\"\ntry: object.__delattr__(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "False False True True\n" + }, + { + "id": "la57-bytes-delete-subtype-key", + "source": "class Key(str):\n def __hash__(self): raise ValueError(\"unexpected hash\")\n def __str__(self): raise ValueError(\"unexpected string\")\nobj=iter(b\"abc\")\nkey=Key(\"missing\")\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj)\n", + "stage": "LA57", + "stdout": "True True\n" + }, + { + "id": "la57-bytearray-write-missing-builtin", + "source": "obj=iter(bytearray(b\"abc\"))\nkey=\"missing\"\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "True True False False\n" + }, + { + "id": "la57-bytearray-write-missing-native", + "source": "obj=iter(bytearray(b\"abc\"))\nkey=\"missing\"\ntry: object.__setattr__(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "True True False False\n" + }, + { + "id": "la57-bytearray-write-__doc__-builtin", + "source": "obj=iter(bytearray(b\"abc\"))\nkey=\"__doc__\"\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "False False True True\n" + }, + { + "id": "la57-bytearray-write-__doc__-native", + "source": "obj=iter(bytearray(b\"abc\"))\nkey=\"__doc__\"\ntry: object.__setattr__(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "False False True True\n" + }, + { + "id": "la57-bytearray-write-subtype-key", + "source": "class Key(str):\n def __hash__(self): raise ValueError(\"unexpected hash\")\n def __str__(self): raise ValueError(\"unexpected string\")\nobj=iter(bytearray(b\"abc\"))\nkey=Key(\"missing\")\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj)\n", + "stage": "LA57", + "stdout": "True True\n" + }, + { + "id": "la57-bytearray-delete-missing-builtin", + "source": "obj=iter(bytearray(b\"abc\"))\nkey=\"missing\"\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "True True False False\n" + }, + { + "id": "la57-bytearray-delete-missing-native", + "source": "obj=iter(bytearray(b\"abc\"))\nkey=\"missing\"\ntry: object.__delattr__(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "True True False False\n" + }, + { + "id": "la57-bytearray-delete-__doc__-builtin", + "source": "obj=iter(bytearray(b\"abc\"))\nkey=\"__doc__\"\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "False False True True\n" + }, + { + "id": "la57-bytearray-delete-__doc__-native", + "source": "obj=iter(bytearray(b\"abc\"))\nkey=\"__doc__\"\ntry: object.__delattr__(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "False False True True\n" + }, + { + "id": "la57-bytearray-delete-subtype-key", + "source": "class Key(str):\n def __hash__(self): raise ValueError(\"unexpected hash\")\n def __str__(self): raise ValueError(\"unexpected string\")\nobj=iter(bytearray(b\"abc\"))\nkey=Key(\"missing\")\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj)\n", + "stage": "LA57", + "stdout": "True True\n" + }, + { + "id": "la57-memoryview-write-missing-builtin", + "source": "obj=iter(memoryview(b\"abc\"))\nkey=\"missing\"\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "True True False False\n" + }, + { + "id": "la57-memoryview-write-missing-native", + "source": "obj=iter(memoryview(b\"abc\"))\nkey=\"missing\"\ntry: object.__setattr__(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "True True False False\n" + }, + { + "id": "la57-memoryview-write-__doc__-builtin", + "source": "obj=iter(memoryview(b\"abc\"))\nkey=\"__doc__\"\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "False False True True\n" + }, + { + "id": "la57-memoryview-write-__doc__-native", + "source": "obj=iter(memoryview(b\"abc\"))\nkey=\"__doc__\"\ntry: object.__setattr__(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "False False True True\n" + }, + { + "id": "la57-memoryview-write-subtype-key", + "source": "class Key(str):\n def __hash__(self): raise ValueError(\"unexpected hash\")\n def __str__(self): raise ValueError(\"unexpected string\")\nobj=iter(memoryview(b\"abc\"))\nkey=Key(\"missing\")\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj)\n", + "stage": "LA57", + "stdout": "True True\n" + }, + { + "id": "la57-memoryview-delete-missing-builtin", + "source": "obj=iter(memoryview(b\"abc\"))\nkey=\"missing\"\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "True True False False\n" + }, + { + "id": "la57-memoryview-delete-missing-native", + "source": "obj=iter(memoryview(b\"abc\"))\nkey=\"missing\"\ntry: object.__delattr__(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "True True False False\n" + }, + { + "id": "la57-memoryview-delete-__doc__-builtin", + "source": "obj=iter(memoryview(b\"abc\"))\nkey=\"__doc__\"\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "False False True True\n" + }, + { + "id": "la57-memoryview-delete-__doc__-native", + "source": "obj=iter(memoryview(b\"abc\"))\nkey=\"__doc__\"\ntry: object.__delattr__(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "False False True True\n" + }, + { + "id": "la57-memoryview-delete-subtype-key", + "source": "class Key(str):\n def __hash__(self): raise ValueError(\"unexpected hash\")\n def __str__(self): raise ValueError(\"unexpected string\")\nobj=iter(memoryview(b\"abc\"))\nkey=Key(\"missing\")\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj)\n", + "stage": "LA57", + "stdout": "True True\n" + }, + { + "id": "la57-indexed-write-missing-builtin", + "source": "class Indexed:\n def __getitem__(self,i):\n if i>0: raise IndexError\n return 7\nobj=iter(Indexed())\nkey=\"missing\"\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "True True False False\n" + }, + { + "id": "la57-indexed-write-missing-native", + "source": "class Indexed:\n def __getitem__(self,i):\n if i>0: raise IndexError\n return 7\nobj=iter(Indexed())\nkey=\"missing\"\ntry: object.__setattr__(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "True True False False\n" + }, + { + "id": "la57-indexed-write-__doc__-builtin", + "source": "class Indexed:\n def __getitem__(self,i):\n if i>0: raise IndexError\n return 7\nobj=iter(Indexed())\nkey=\"__doc__\"\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "False False True True\n" + }, + { + "id": "la57-indexed-write-__doc__-native", + "source": "class Indexed:\n def __getitem__(self,i):\n if i>0: raise IndexError\n return 7\nobj=iter(Indexed())\nkey=\"__doc__\"\ntry: object.__setattr__(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "False False True True\n" + }, + { + "id": "la57-indexed-write-subtype-key", + "source": "class Indexed:\n def __getitem__(self,i):\n if i>0: raise IndexError\n return 7\nclass Key(str):\n def __hash__(self): raise ValueError(\"unexpected hash\")\n def __str__(self): raise ValueError(\"unexpected string\")\nobj=iter(Indexed())\nkey=Key(\"missing\")\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj)\n", + "stage": "LA57", + "stdout": "True True\n" + }, + { + "id": "la57-indexed-delete-missing-builtin", + "source": "class Indexed:\n def __getitem__(self,i):\n if i>0: raise IndexError\n return 7\nobj=iter(Indexed())\nkey=\"missing\"\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "True True False False\n" + }, + { + "id": "la57-indexed-delete-missing-native", + "source": "class Indexed:\n def __getitem__(self,i):\n if i>0: raise IndexError\n return 7\nobj=iter(Indexed())\nkey=\"missing\"\ntry: object.__delattr__(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "True True False False\n" + }, + { + "id": "la57-indexed-delete-__doc__-builtin", + "source": "class Indexed:\n def __getitem__(self,i):\n if i>0: raise IndexError\n return 7\nobj=iter(Indexed())\nkey=\"__doc__\"\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "False False True True\n" + }, + { + "id": "la57-indexed-delete-__doc__-native", + "source": "class Indexed:\n def __getitem__(self,i):\n if i>0: raise IndexError\n return 7\nobj=iter(Indexed())\nkey=\"__doc__\"\ntry: object.__delattr__(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "False False True True\n" + }, + { + "id": "la57-indexed-delete-subtype-key", + "source": "class Indexed:\n def __getitem__(self,i):\n if i>0: raise IndexError\n return 7\nclass Key(str):\n def __hash__(self): raise ValueError(\"unexpected hash\")\n def __str__(self): raise ValueError(\"unexpected string\")\nobj=iter(Indexed())\nkey=Key(\"missing\")\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj)\n", + "stage": "LA57", + "stdout": "True True\n" + }, + { + "id": "la57-callable-write-missing-builtin", + "source": "obj=iter(lambda:7,0)\nkey=\"missing\"\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "True True False False\n" + }, + { + "id": "la57-callable-write-missing-native", + "source": "obj=iter(lambda:7,0)\nkey=\"missing\"\ntry: object.__setattr__(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "True True False False\n" + }, + { + "id": "la57-callable-write-__doc__-builtin", + "source": "obj=iter(lambda:7,0)\nkey=\"__doc__\"\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "False False True True\n" + }, + { + "id": "la57-callable-write-__doc__-native", + "source": "obj=iter(lambda:7,0)\nkey=\"__doc__\"\ntry: object.__setattr__(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "False False True True\n" + }, + { + "id": "la57-callable-write-subtype-key", + "source": "class Key(str):\n def __hash__(self): raise ValueError(\"unexpected hash\")\n def __str__(self): raise ValueError(\"unexpected string\")\nobj=iter(lambda:7,0)\nkey=Key(\"missing\")\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj)\n", + "stage": "LA57", + "stdout": "True True\n" + }, + { + "id": "la57-callable-delete-missing-builtin", + "source": "obj=iter(lambda:7,0)\nkey=\"missing\"\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "True True False False\n" + }, + { + "id": "la57-callable-delete-missing-native", + "source": "obj=iter(lambda:7,0)\nkey=\"missing\"\ntry: object.__delattr__(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "True True False False\n" + }, + { + "id": "la57-callable-delete-__doc__-builtin", + "source": "obj=iter(lambda:7,0)\nkey=\"__doc__\"\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "False False True True\n" + }, + { + "id": "la57-callable-delete-__doc__-native", + "source": "obj=iter(lambda:7,0)\nkey=\"__doc__\"\ntry: object.__delattr__(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "False False True True\n" + }, + { + "id": "la57-callable-delete-subtype-key", + "source": "class Key(str):\n def __hash__(self): raise ValueError(\"unexpected hash\")\n def __str__(self): raise ValueError(\"unexpected string\")\nobj=iter(lambda:7,0)\nkey=Key(\"missing\")\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj)\n", + "stage": "LA57", + "stdout": "True True\n" + }, + { + "id": "la57-exhausted-write-missing-builtin", + "source": "obj=iter([7])\nnext(obj)\nnext(obj,None)\nobj=obj\nkey=\"missing\"\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "True True False False\n" + }, + { + "id": "la57-exhausted-write-missing-native", + "source": "obj=iter([7])\nnext(obj)\nnext(obj,None)\nobj=obj\nkey=\"missing\"\ntry: object.__setattr__(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "True True False False\n" + }, + { + "id": "la57-exhausted-write-__doc__-builtin", + "source": "obj=iter([7])\nnext(obj)\nnext(obj,None)\nobj=obj\nkey=\"__doc__\"\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "False False True True\n" + }, + { + "id": "la57-exhausted-write-__doc__-native", + "source": "obj=iter([7])\nnext(obj)\nnext(obj,None)\nobj=obj\nkey=\"__doc__\"\ntry: object.__setattr__(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "False False True True\n" + }, + { + "id": "la57-exhausted-write-subtype-key", + "source": "obj=iter([7])\nnext(obj)\nnext(obj,None)\nclass Key(str):\n def __hash__(self): raise ValueError(\"unexpected hash\")\n def __str__(self): raise ValueError(\"unexpected string\")\nobj=obj\nkey=Key(\"missing\")\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj)\n", + "stage": "LA57", + "stdout": "True True\n" + }, + { + "id": "la57-exhausted-delete-missing-builtin", + "source": "obj=iter([7])\nnext(obj)\nnext(obj,None)\nobj=obj\nkey=\"missing\"\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "True True False False\n" + }, + { + "id": "la57-exhausted-delete-missing-native", + "source": "obj=iter([7])\nnext(obj)\nnext(obj,None)\nobj=obj\nkey=\"missing\"\ntry: object.__delattr__(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "True True False False\n" + }, + { + "id": "la57-exhausted-delete-__doc__-builtin", + "source": "obj=iter([7])\nnext(obj)\nnext(obj,None)\nobj=obj\nkey=\"__doc__\"\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "False False True True\n" + }, + { + "id": "la57-exhausted-delete-__doc__-native", + "source": "obj=iter([7])\nnext(obj)\nnext(obj,None)\nobj=obj\nkey=\"__doc__\"\ntry: object.__delattr__(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "False False True True\n" + }, + { + "id": "la57-exhausted-delete-subtype-key", + "source": "obj=iter([7])\nnext(obj)\nnext(obj,None)\nclass Key(str):\n def __hash__(self): raise ValueError(\"unexpected hash\")\n def __str__(self): raise ValueError(\"unexpected string\")\nobj=obj\nkey=Key(\"missing\")\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj)\n", + "stage": "LA57", + "stdout": "True True\n" + }, + { + "id": "la57-exhausted-dict-write-missing-builtin", + "source": "obj=iter({\"field\":7})\nnext(obj)\nnext(obj,None)\nobj=obj\nkey=\"missing\"\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "True True False False\n" + }, + { + "id": "la57-exhausted-dict-write-missing-native", + "source": "obj=iter({\"field\":7})\nnext(obj)\nnext(obj,None)\nobj=obj\nkey=\"missing\"\ntry: object.__setattr__(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "True True False False\n" + }, + { + "id": "la57-exhausted-dict-write-__doc__-builtin", + "source": "obj=iter({\"field\":7})\nnext(obj)\nnext(obj,None)\nobj=obj\nkey=\"__doc__\"\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "False False True True\n" + }, + { + "id": "la57-exhausted-dict-write-__doc__-native", + "source": "obj=iter({\"field\":7})\nnext(obj)\nnext(obj,None)\nobj=obj\nkey=\"__doc__\"\ntry: object.__setattr__(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "False False True True\n" + }, + { + "id": "la57-exhausted-dict-write-subtype-key", + "source": "obj=iter({\"field\":7})\nnext(obj)\nnext(obj,None)\nclass Key(str):\n def __hash__(self): raise ValueError(\"unexpected hash\")\n def __str__(self): raise ValueError(\"unexpected string\")\nobj=obj\nkey=Key(\"missing\")\ntry: setattr(obj,key,7)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj)\n", + "stage": "LA57", + "stdout": "True True\n" + }, + { + "id": "la57-exhausted-dict-delete-missing-builtin", + "source": "obj=iter({\"field\":7})\nnext(obj)\nnext(obj,None)\nobj=obj\nkey=\"missing\"\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "True True False False\n" + }, + { + "id": "la57-exhausted-dict-delete-missing-native", + "source": "obj=iter({\"field\":7})\nnext(obj)\nnext(obj,None)\nobj=obj\nkey=\"missing\"\ntry: object.__delattr__(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "True True False False\n" + }, + { + "id": "la57-exhausted-dict-delete-__doc__-builtin", + "source": "obj=iter({\"field\":7})\nnext(obj)\nnext(obj,None)\nobj=obj\nkey=\"__doc__\"\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "False False True True\n" + }, + { + "id": "la57-exhausted-dict-delete-__doc__-native", + "source": "obj=iter({\"field\":7})\nnext(obj)\nnext(obj,None)\nobj=obj\nkey=\"__doc__\"\ntry: object.__delattr__(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj,e.name is None,e.obj is None)\n", + "stage": "LA57", + "stdout": "False False True True\n" + }, + { + "id": "la57-exhausted-dict-delete-subtype-key", + "source": "obj=iter({\"field\":7})\nnext(obj)\nnext(obj,None)\nclass Key(str):\n def __hash__(self): raise ValueError(\"unexpected hash\")\n def __str__(self): raise ValueError(\"unexpected string\")\nobj=obj\nkey=Key(\"missing\")\ntry: delattr(obj,key)\nexcept AttributeError as e:\n e.args=()\n print(e.name is key,e.obj is obj)\n", + "stage": "LA57", + "stdout": "True True\n" + }, + { + "id": "iterator-direct-list-write", + "source": "obj=iter([7])\ntry: obj.missing=7\nexcept AttributeError as e:\n e.args=()\n print(e.name,e.obj is obj)\n", + "stage": "LA57", + "stdout": "missing True\n" + }, + { + "id": "iterator-direct-list-delete", + "source": "obj=iter([7])\ntry: del obj.missing\nexcept AttributeError as e:\n e.args=()\n print(e.name,e.obj is obj)\n", + "stage": "LA57", + "stdout": "missing True\n" + }, + { + "id": "iterator-direct-tuple-write", + "source": "obj=iter((7,))\ntry: obj.missing=7\nexcept AttributeError as e:\n e.args=()\n print(e.name,e.obj is obj)\n", + "stage": "LA57", + "stdout": "missing True\n" + }, + { + "id": "iterator-direct-tuple-delete", + "source": "obj=iter((7,))\ntry: del obj.missing\nexcept AttributeError as e:\n e.args=()\n print(e.name,e.obj is obj)\n", + "stage": "LA57", + "stdout": "missing True\n" + }, + { + "id": "iterator-direct-ascii-write", + "source": "obj=iter(\"ab\")\ntry: obj.missing=7\nexcept AttributeError as e:\n e.args=()\n print(e.name,e.obj is obj)\n", + "stage": "LA57", + "stdout": "missing True\n" + }, + { + "id": "iterator-direct-ascii-delete", + "source": "obj=iter(\"ab\")\ntry: del obj.missing\nexcept AttributeError as e:\n e.args=()\n print(e.name,e.obj is obj)\n", + "stage": "LA57", + "stdout": "missing True\n" + }, + { + "id": "iterator-direct-unicode-write", + "source": "obj=iter(\"\u03bb\u96ea\")\ntry: obj.missing=7\nexcept AttributeError as e:\n e.args=()\n print(e.name,e.obj is obj)\n", + "stage": "LA57", + "stdout": "missing True\n" + }, + { + "id": "iterator-direct-unicode-delete", + "source": "obj=iter(\"\u03bb\u96ea\")\ntry: del obj.missing\nexcept AttributeError as e:\n e.args=()\n print(e.name,e.obj is obj)\n", + "stage": "LA57", + "stdout": "missing True\n" + }, + { + "id": "iterator-direct-range-write", + "source": "obj=iter(range(3))\ntry: obj.missing=7\nexcept AttributeError as e:\n e.args=()\n print(e.name,e.obj is obj)\n", + "stage": "LA57", + "stdout": "missing True\n" + }, + { + "id": "iterator-direct-range-delete", + "source": "obj=iter(range(3))\ntry: del obj.missing\nexcept AttributeError as e:\n e.args=()\n print(e.name,e.obj is obj)\n", + "stage": "LA57", + "stdout": "missing True\n" + }, + { + "id": "iterator-direct-longrange-write", + "source": "obj=iter(range(10**25,10**25+3))\ntry: obj.missing=7\nexcept AttributeError as e:\n e.args=()\n print(e.name,e.obj is obj)\n", + "stage": "LA57", + "stdout": "missing True\n" + }, + { + "id": "iterator-direct-longrange-delete", + "source": "obj=iter(range(10**25,10**25+3))\ntry: del obj.missing\nexcept AttributeError as e:\n e.args=()\n print(e.name,e.obj is obj)\n", + "stage": "LA57", + "stdout": "missing True\n" + }, + { + "id": "iterator-direct-dict-keys-write", + "source": "obj=iter({\"field\":7})\ntry: obj.missing=7\nexcept AttributeError as e:\n e.args=()\n print(e.name,e.obj is obj)\n", + "stage": "LA57", + "stdout": "missing True\n" + }, + { + "id": "iterator-direct-dict-keys-delete", + "source": "obj=iter({\"field\":7})\ntry: del obj.missing\nexcept AttributeError as e:\n e.args=()\n print(e.name,e.obj is obj)\n", + "stage": "LA57", + "stdout": "missing True\n" + }, + { + "id": "iterator-direct-dict-values-write", + "source": "obj=iter({\"field\":7}.values())\ntry: obj.missing=7\nexcept AttributeError as e:\n e.args=()\n print(e.name,e.obj is obj)\n", + "stage": "LA57", + "stdout": "missing True\n" + }, + { + "id": "iterator-direct-dict-values-delete", + "source": "obj=iter({\"field\":7}.values())\ntry: del obj.missing\nexcept AttributeError as e:\n e.args=()\n print(e.name,e.obj is obj)\n", + "stage": "LA57", + "stdout": "missing True\n" + }, + { + "id": "iterator-direct-dict-items-write", + "source": "obj=iter({\"field\":7}.items())\ntry: obj.missing=7\nexcept AttributeError as e:\n e.args=()\n print(e.name,e.obj is obj)\n", + "stage": "LA57", + "stdout": "missing True\n" + }, + { + "id": "iterator-direct-dict-items-delete", + "source": "obj=iter({\"field\":7}.items())\ntry: del obj.missing\nexcept AttributeError as e:\n e.args=()\n print(e.name,e.obj is obj)\n", + "stage": "LA57", + "stdout": "missing True\n" + }, + { + "id": "iterator-direct-set-write", + "source": "obj=iter({7})\ntry: obj.missing=7\nexcept AttributeError as e:\n e.args=()\n print(e.name,e.obj is obj)\n", + "stage": "LA57", + "stdout": "missing True\n" + }, + { + "id": "iterator-direct-set-delete", + "source": "obj=iter({7})\ntry: del obj.missing\nexcept AttributeError as e:\n e.args=()\n print(e.name,e.obj is obj)\n", + "stage": "LA57", + "stdout": "missing True\n" + }, + { + "id": "iterator-direct-frozenset-write", + "source": "obj=iter(frozenset([7]))\ntry: obj.missing=7\nexcept AttributeError as e:\n e.args=()\n print(e.name,e.obj is obj)\n", + "stage": "LA57", + "stdout": "missing True\n" + }, + { + "id": "iterator-direct-frozenset-delete", + "source": "obj=iter(frozenset([7]))\ntry: del obj.missing\nexcept AttributeError as e:\n e.args=()\n print(e.name,e.obj is obj)\n", + "stage": "LA57", + "stdout": "missing True\n" + }, + { + "id": "iterator-direct-bytes-write", + "source": "obj=iter(b\"abc\")\ntry: obj.missing=7\nexcept AttributeError as e:\n e.args=()\n print(e.name,e.obj is obj)\n", + "stage": "LA57", + "stdout": "missing True\n" + }, + { + "id": "iterator-direct-bytes-delete", + "source": "obj=iter(b\"abc\")\ntry: del obj.missing\nexcept AttributeError as e:\n e.args=()\n print(e.name,e.obj is obj)\n", + "stage": "LA57", + "stdout": "missing True\n" + }, + { + "id": "iterator-direct-bytearray-write", + "source": "obj=iter(bytearray(b\"abc\"))\ntry: obj.missing=7\nexcept AttributeError as e:\n e.args=()\n print(e.name,e.obj is obj)\n", + "stage": "LA57", + "stdout": "missing True\n" + }, + { + "id": "iterator-direct-bytearray-delete", + "source": "obj=iter(bytearray(b\"abc\"))\ntry: del obj.missing\nexcept AttributeError as e:\n e.args=()\n print(e.name,e.obj is obj)\n", + "stage": "LA57", + "stdout": "missing True\n" + }, + { + "id": "iterator-direct-memoryview-write", + "source": "obj=iter(memoryview(b\"abc\"))\ntry: obj.missing=7\nexcept AttributeError as e:\n e.args=()\n print(e.name,e.obj is obj)\n", + "stage": "LA57", + "stdout": "missing True\n" + }, + { + "id": "iterator-direct-memoryview-delete", + "source": "obj=iter(memoryview(b\"abc\"))\ntry: del obj.missing\nexcept AttributeError as e:\n e.args=()\n print(e.name,e.obj is obj)\n", + "stage": "LA57", + "stdout": "missing True\n" + }, + { + "id": "iterator-direct-indexed-write", + "source": "class Indexed:\n def __getitem__(self,i):\n if i>0: raise IndexError\n return 7\nobj=iter(Indexed())\ntry: obj.missing=7\nexcept AttributeError as e:\n e.args=()\n print(e.name,e.obj is obj)\n", + "stage": "LA57", + "stdout": "missing True\n" + }, + { + "id": "iterator-direct-indexed-delete", + "source": "class Indexed:\n def __getitem__(self,i):\n if i>0: raise IndexError\n return 7\nobj=iter(Indexed())\ntry: del obj.missing\nexcept AttributeError as e:\n e.args=()\n print(e.name,e.obj is obj)\n", + "stage": "LA57", + "stdout": "missing True\n" + }, + { + "id": "iterator-direct-callable-write", + "source": "obj=iter(lambda:7,0)\ntry: obj.missing=7\nexcept AttributeError as e:\n e.args=()\n print(e.name,e.obj is obj)\n", + "stage": "LA57", + "stdout": "missing True\n" + }, + { + "id": "iterator-direct-callable-delete", + "source": "obj=iter(lambda:7,0)\ntry: del obj.missing\nexcept AttributeError as e:\n e.args=()\n print(e.name,e.obj is obj)\n", + "stage": "LA57", + "stdout": "missing True\n" + }, + { + "id": "iterator-direct-exhausted-write", + "source": "obj=iter([7])\nnext(obj)\nnext(obj,None)\nobj=obj\ntry: obj.missing=7\nexcept AttributeError as e:\n e.args=()\n print(e.name,e.obj is obj)\n", + "stage": "LA57", + "stdout": "missing True\n" + }, + { + "id": "iterator-direct-exhausted-delete", + "source": "obj=iter([7])\nnext(obj)\nnext(obj,None)\nobj=obj\ntry: del obj.missing\nexcept AttributeError as e:\n e.args=()\n print(e.name,e.obj is obj)\n", + "stage": "LA57", + "stdout": "missing True\n" + }, + { + "id": "iterator-direct-exhausted-dict-write", + "source": "obj=iter({\"field\":7})\nnext(obj)\nnext(obj,None)\nobj=obj\ntry: obj.missing=7\nexcept AttributeError as e:\n e.args=()\n print(e.name,e.obj is obj)\n", + "stage": "LA57", + "stdout": "missing True\n" + }, + { + "id": "iterator-direct-exhausted-dict-delete", + "source": "obj=iter({\"field\":7})\nnext(obj)\nnext(obj,None)\nobj=obj\ntry: del obj.missing\nexcept AttributeError as e:\n e.args=()\n print(e.name,e.obj is obj)\n", + "stage": "LA57", + "stdout": "missing True\n" } ], "reference_boundaries": [ From 2e83d58136257a45a8dead7e54b5d27324eaebca Mon Sep 17 00:00:00 2001 From: ellainng <299958882+ellainng@users.noreply.github.com> Date: Sun, 1 Nov 2026 18:41:01 -0800 Subject: [PATCH 7/9] test(LA-58): accept iterator mutation failures offline --- docs/interpreter-exceptions.md | 15 ++++ examples/interpreter_iterator_failures.py | 23 +++++++ runtime/exceptions.test.mjs | 19 ++++++ scripts/object_integration_browser_smoke.py | 14 +++- tests/fixtures/interpreter/exceptions.json | 76 ++++++++++++++++++++- 5 files changed, 145 insertions(+), 2 deletions(-) create mode 100644 examples/interpreter_iterator_failures.py diff --git a/docs/interpreter-exceptions.md b/docs/interpreter-exceptions.md index a33a160..49c713b 100644 --- a/docs/interpreter-exceptions.md +++ b/docs/interpreter-exceptions.md @@ -544,3 +544,18 @@ layouts. Retained errors keep detached dictionary subtype owners, memoryview storage and indexed/callable sources alive and release cycles on close or interruption. New iterator APIs and protocols remain under their existing owners; core mutation admission stays unchanged. + +LA-58 accepts generator/iterator mutation failures across modules, dynamic +source, causes/notes, running generators, exhaustion, owner cycles and +suspended cleanup. Run `examples/interpreter_iterator_failures.py` unchanged +on the pinned reference or offline console. Installed acceptance checks original +failure locations, Stop/Restart during generator cleanup and retained indexed +callbacks, and fresh targets after restart. The corpus has 1,741 cases: 1,730 +supported semantic/error comparisons, 11 explicit refusals and 267 explicitly +shared core-source/1 cases. Six host/crash/cache qualifications remain separate; +these finite observations are not an overall Python-completion percentage. +Frame/code/cell/property/super mutation layouts, direct scalar storage +descriptors, original-instance shared-key deletion, other native/import/parser +metadata, groups/except*, tracebacks and note export keep #124/A2 open. Broader +formatting, iterator/container/codec APIs and library/package/candidate gates +retain their existing owners. diff --git a/examples/interpreter_iterator_failures.py b/examples/interpreter_iterator_failures.py new file mode 100644 index 0000000..b7272e6 --- /dev/null +++ b/examples/interpreter_iterator_failures.py @@ -0,0 +1,23 @@ +"""Keep rejected iterator targets available for recovery.""" + + +def retain_failure(target): + try: + target.missing = 7 + except AttributeError as error: + error.args = () + error.add_note("the original iterator is still available") + return error + + +def records(value): + yield value + + +for target in [records([7]), iter([[8]]), iter({"record": [9]}.values())]: + error = retain_failure(target) + print(error.name, error.obj is target, next(error.obj)[0]) + print(error.__notes__[0]) + +error = retain_failure(iter(memoryview(bytearray(b"abc")))) +print(error.name, next(error.obj)) diff --git a/runtime/exceptions.test.mjs b/runtime/exceptions.test.mjs index c038458..29539cb 100644 --- a/runtime/exceptions.test.mjs +++ b/runtime/exceptions.test.mjs @@ -117,6 +117,25 @@ test('retained iterator mutation graphs share the allocation limit', async () => assert.equal(result.stdout, 'ready\n'); assert.equal(result.error?.type, 'MemoryError'); closed(result.closed); }); +for (const mode of ['generator', 'iterator']) test(mode + ' mutation failures preserve original dynamic frames', async () => { + const item = corpus.cases.find(item => item.id === 'iterators-' + mode + '-dynamic-failure'); + const result = await runSourceRequest({modules: [unit(item.source)], compileOptions: {languageProfile: corpus.profile}}); + assert.equal(result.error.type, 'AttributeError'); + assert.deepEqual(result.error.traceback.map(frame => [frame.function, frame.location.filename, frame.location.span[0]]), [ + ['', 'sources/app.py', 1], ['', mode + '-failure-original.py', 7], ['fail', mode + '-failure-original.py', 3]]); + closed(result.closed); +}); + +test('self-retained generator mutation errors survive interrupted cleanup', async () => { + await lifetime('def work():\n try: g.missing=7\n except AttributeError as e:\n try: yield e\n finally:\n print("entered")\n print(e.name,e.obj is g,e.obj.gi_running)\ng=work()\ne=next(g)\ne.args=()\ng.close()\n', 'entered\nmissing True True\n'); +}); + +test('retained iterator callbacks share execution limits', async () => { + const result = await runSourceRequest({modules: [unit('class Source:\n def __getitem__(self,index):\n print("entered")\n while True: pass\ntry: iter(Source()).missing=7\nexcept AttributeError as e: next(e.obj)\n')], + compileOptions: {languageProfile: corpus.profile}, options: {maxSteps: 1000}}); + assert.equal(result.stdout, 'entered\n'); assert.equal(result.error?.code, 'BUDGET'); closed(result.closed); +}); + test('module mutation errors retain cyclic keys and the module after args replacement', async () => { await lifetime('import helper\nclass Key(str): pass\nkey=Key("missing")\ntry: delattr(helper,key)\nexcept AttributeError as error: e=error\nkey.error=e\ne.args=()\nhelper=None\nkey=None\nprint("entered")\nprint(e.name.error is e,e.obj.marker)\n', 'entered\nTrue 7\n', [{name: 'helper', source: 'marker=7\n'}]); diff --git a/scripts/object_integration_browser_smoke.py b/scripts/object_integration_browser_smoke.py index fdafc06..713e136 100644 --- a/scripts/object_integration_browser_smoke.py +++ b/scripts/object_integration_browser_smoke.py @@ -66,7 +66,7 @@ def run(source, expected, error=None): for case in corpus['cases']: if (case.get('modules') or containers and case['stage'] not in {'LA-7', 'LA-6A'} or - exceptions and case['stage'] not in {'LA13', 'LA16', 'LA19', 'LA22', 'LA25', 'LA28', 'LA31', 'LA34', 'LA37', 'LA40', 'LA43', 'LA46', 'LA49', 'LA52', 'LA55'}): + exceptions and case['stage'] not in {'LA13', 'LA16', 'LA19', 'LA22', 'LA25', 'LA28', 'LA31', 'LA34', 'LA37', 'LA40', 'LA43', 'LA46', 'LA49', 'LA52', 'LA55', 'LA58'}): continue run(case['source'], case['stdout'], case.get('error_type')) if case['id'] == 'dynamic-original-source-failure': @@ -105,6 +105,9 @@ def run(source, expected, error=None): if case['id'] in {'values-scalar-dynamic-failure', 'values-buffer-dynamic-failure'}: filename = 'scalar' if 'scalar' in case['id'] else 'buffer' assert f'fail at {filename}-value-original.py:3' in page.locator('#diagnostic').inner_text() + if case['id'] in {'iterators-generator-dynamic-failure', 'iterators-iterator-dynamic-failure'}: + filename = 'generator' if 'generator' in case['id'] else 'iterator' + assert f'fail at {filename}-failure-original.py:3' in page.locator('#diagnostic').inner_text() records.append({'id': case['id'], 'status': 'pass'}) if exceptions: for case in corpus.get('reference_boundaries', []): @@ -154,6 +157,10 @@ def run(source, expected, error=None): value_failures_expected = subprocess.check_output([sys.executable, '-I', '-S', '-c', value_failures], text=True, encoding='utf-8') run(value_failures, value_failures_expected) records.append({'id': 'value-mutation-example', 'status': 'pass'}) + iterator_failures = (ROOT / 'examples/interpreter_iterator_failures.py').read_text(encoding='utf-8') + iterator_failures_expected = subprocess.check_output([sys.executable, '-I', '-S', '-c', iterator_failures], text=True, encoding='utf-8') + run(iterator_failures, iterator_failures_expected) + records.append({'id': 'iterator-mutation-example', 'status': 'pass'}) mutations = (ROOT / 'examples/interpreter_attribute_mutations.py').read_text(encoding='utf-8') mutations_expected = subprocess.check_output([sys.executable, '-I', '-S', '-c', mutations], text=True, encoding='utf-8') run(mutations, mutations_expected) @@ -195,6 +202,8 @@ def run(source, expected, error=None): busy_sources += [ 'try: (1.25).missing=7\nexcept AttributeError as e:\n e.args=()\n print("entered")\n while True: e.obj\n', 'def work():\n try: memoryview(bytearray(b"abc")).missing=7\n except AttributeError as e:\n try: yield e\n finally:\n print("entered")\n while True: e.obj[0]\ni=work()\ne=next(i)\ne.args=()\ni.close()\n', + 'def target(): yield [7]\ndef work():\n try: target().missing=7\n except AttributeError as e:\n try: yield e\n finally:\n print("entered")\n while True: e.obj\ni=work()\ne=next(i)\ne.args=()\ni.close()\n', + 'class Source:\n def __getitem__(self,index):\n print("entered")\n while True: pass\ntry: iter(Source()).missing=7\nexcept AttributeError as e:\n e.args=()\n next(e.obj)\n', ] for source in busy_sources: page.fill('#input', source) @@ -211,6 +220,7 @@ def run(source, expected, error=None): if exceptions: run(callable_views, callable_views_expected) run(value_failures, value_failures_expected) + run(iterator_failures, iterator_failures_expected) context.set_offline(False) shutil.rmtree(root / 'site/console') shutil.copytree(root / 'reduced', root / 'site/console') @@ -281,6 +291,8 @@ def run(source, expected, error=None): 'callable-view-original-diagnostics', 'callable-view-fresh-restart', 'stop-during-scalar-error-retention', 'stop-during-buffer-error-cleanup', 'value-mutation-original-diagnostics', 'value-mutation-fresh-restart', + 'stop-during-generator-error-cleanup', 'stop-during-retained-iterator-callback', + 'iterator-mutation-original-diagnostics', 'iterator-mutation-fresh-restart', 'restart-fresh-owner', 'reduced-profile-update', 'old-cache-removed', 'core-error-arguments', 'core-source-boundary'] return {'schema_version': 1, 'suite': 'interpreter-exceptions-offline/1' if exceptions else 'interpreter-containers-offline/1' if containers else 'interpreter-object-offline/1', diff --git a/tests/fixtures/interpreter/exceptions.json b/tests/fixtures/interpreter/exceptions.json index 13ae78a..02b7a5c 100644 --- a/tests/fixtures/interpreter/exceptions.json +++ b/tests/fixtures/interpreter/exceptions.json @@ -58,7 +58,8 @@ "LA54A", "LA55", "LA56", - "LA57" + "LA57", + "LA58" ], "cases": [ { @@ -12437,6 +12438,79 @@ "source": "obj=iter({\"field\":7})\nnext(obj)\nnext(obj,None)\nobj=obj\ntry: del obj.missing\nexcept AttributeError as e:\n e.args=()\n print(e.name,e.obj is obj)\n", "stage": "LA57", "stdout": "missing True\n" + }, + { + "id": "iterators-composed-example", + "source": "\"\"\"Keep rejected iterator targets available for recovery.\"\"\"\n\n\ndef retain_failure(target):\n try:\n target.missing = 7\n except AttributeError as error:\n error.args = ()\n error.add_note(\"the original iterator is still available\")\n return error\n\n\ndef records(value):\n yield value\n\n\nfor target in [records([7]), iter([[8]]), iter({\"record\": [9]}.values())]:\n error = retain_failure(target)\n print(error.name, error.obj is target, next(error.obj)[0])\n print(error.__notes__[0])\n\nerror = retain_failure(iter(memoryview(bytearray(b\"abc\"))))\nprint(error.name, next(error.obj))\n", + "stage": "LA58", + "stdout": "missing True 7\nthe original iterator is still available\nmissing True 8\nthe original iterator is still available\nmissing True 9\nthe original iterator is still available\nmissing 97\n" + }, + { + "id": "iterators-composed-module", + "imports": [ + "helper" + ], + "modules": [ + { + "name": "helper", + "package": false, + "imports": [], + "source": "def fail(target):\n try: target.missing=7\n except AttributeError as e:\n e.add_note(\"retained iterator\")\n raise e from ValueError(8)\n" + } + ], + "source": "import helper\ntarget=(x for x in [[7]])\ntry: helper.fail(target)\nexcept AttributeError as e:\n e.args=()\n print(e.name,e.obj is target,next(e.obj)[0],e.__cause__.args[0],e.__notes__[0])\n", + "stage": "LA58", + "stdout": "missing True 7 8 retained iterator\n" + }, + { + "id": "iterators-generator-dynamic-failure", + "source": "exec(compile('def fail():\\n target=(x for x in [7])\\n try: target.missing=7\\n except AttributeError as e:\\n print(e.name,e.obj is target)\\n raise\\nfail()',\"generator-failure-original.py\",\"exec\"))\n", + "error_type": "AttributeError", + "stage": "LA58", + "stdout": "missing True\n" + }, + { + "id": "iterators-iterator-dynamic-failure", + "source": "exec(compile('def fail():\\n target=iter([7])\\n try: target.missing=7\\n except AttributeError as e:\\n print(e.name,e.obj is target)\\n raise\\nfail()',\"iterator-failure-original.py\",\"exec\"))\n", + "error_type": "AttributeError", + "stage": "LA58", + "stdout": "missing True\n" + }, + { + "id": "iterators-running-generator", + "source": "def work():\n try: g.missing=7\n except AttributeError as e:\n print(e.name,e.obj is g,e.obj.gi_running)\n yield e\ng=work()\ne=next(g)\ne.args=()\nprint(e.obj is g,g.gi_suspended)\ng.close()\n", + "stage": "LA58", + "stdout": "missing True True\nTrue True\n" + }, + { + "id": "iterators-suspended-cleanup", + "source": "def target(): yield [7]\ndef work():\n try: target().missing=7\n except AttributeError as e:\n try: yield e\n finally: print(e.name,next(e.obj)[0],len(e.args))\ni=work()\ne=next(i)\ne.args=()\ni.close()\n", + "stage": "LA58", + "stdout": "missing 7 0\n" + }, + { + "id": "iterators-dictionary-owner-cycle", + "source": "class D(dict): pass\nd=D()\nd[\"record\"]=d\ntry: iter(d.values()).missing=7\nexcept AttributeError as error: e=error\nd.error=e\ne.args=()\nd=None\nprint(e.name,next(e.obj).error is e)\n", + "stage": "LA58", + "stdout": "missing True\n" + }, + { + "id": "iterators-indexed-source-cycle", + "source": "class Source:\n def __getitem__(self,index): return self\nsource=Source()\ntry: iter(source).missing=7\nexcept AttributeError as error: e=error\nsource.error=e\ne.args=()\nsource=None\nprint(e.name,next(e.obj).error is e)\n", + "stage": "LA58", + "stdout": "missing True\n" + }, + { + "id": "iterators-callable-source-cycle", + "source": "class Source:\n def __call__(self): return self\nsource=Source()\ntry: iter(source,None).missing=7\nexcept AttributeError as error: e=error\nsource.error=e\ne.args=()\nsource=None\nprint(e.name,next(e.obj).error is e)\n", + "stage": "LA58", + "stdout": "missing True\n" + }, + { + "id": "iterators-exhaustion-control", + "source": "target=iter([7])\ntry: target.missing=8\nexcept AttributeError as error: e=error\ne.args=()\nprint(next(e.obj),next(e.obj,None) is None,e.obj is target)\ntry: del target.missing\nexcept AttributeError as other: print(other.name,other.obj is e.obj,next(other.obj,None) is None)\n", + "stage": "LA58", + "stdout": "7 True True\nmissing True True\n" } ], "reference_boundaries": [ From 1a2bf69d44c1900e7dc85ad4f9d3263e231832db Mon Sep 17 00:00:00 2001 From: ellainng <299958882+ellainng@users.noreply.github.com> Date: Sun, 1 Nov 2026 18:47:33 -0800 Subject: [PATCH 8/9] fix(LA-58A): scale differential process deadlines --- docs/interpreter-exceptions.md | 7 +++++++ runtime/check_classes.py | 5 ++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/docs/interpreter-exceptions.md b/docs/interpreter-exceptions.md index 49c713b..61b5aff 100644 --- a/docs/interpreter-exceptions.md +++ b/docs/interpreter-exceptions.md @@ -559,3 +559,10 @@ descriptors, original-instance shared-key deletion, other native/import/parser metadata, groups/except*, tracebacks and note export keep #124/A2 open. Broader formatting, iterator/container/codec APIs and library/package/candidate gates retain their existing owners. + +LA-58A keeps the complete differential Node request bounded as the corpus +grows: its process deadline is the larger of 30 seconds and the selected case +count divided by ten, matching the shared validation inventory's cost basis. +Small selections retain the existing bound; semantic execution, work/allocation +limits and timeout/failure diagnostics are unchanged. This addresses the two +fixed-deadline Linux CI failures in the first batch's stepped/parked matrix. diff --git a/runtime/check_classes.py b/runtime/check_classes.py index 82abb8e..c423cde 100644 --- a/runtime/check_classes.py +++ b/runtime/check_classes.py @@ -88,8 +88,11 @@ def check(target, channel=None, corpus_path=CORPUS, stages=None, stepped=False, 'options': {'maxSteps': 50000, 'maxWork': 20000000}, **({'parked': True} if parked else {})} for case in cases] stepped = stepped or parked if target == 'node': + # Match the shared inventory's case-count cost estimate while retaining + # the small-corpus bound and a finite deadline for the complete request. + timeout = max(30, len(requests) / 10) response = json.loads(subprocess.check_output(['node', str(ROOT / ('execution-runner.mjs' if stepped else 'source-runner.mjs'))], - input=json.dumps(requests), text=True, encoding='utf-8', timeout=30)) + input=json.dumps(requests), text=True, encoding='utf-8', timeout=timeout)) host, results = response['host'], response['results'] else: host, results = run_browser({'worker': 'execution-worker.mjs' if stepped else 'source-worker.mjs'}, requests, channel) From 3f980481487136833a9dc1705e1cde290fff2828 Mon Sep 17 00:00:00 2001 From: ellainng <299958882+ellainng@users.noreply.github.com> Date: Sun, 1 Nov 2026 19:23:44 -0800 Subject: [PATCH 9/9] fix(LA-58B): allow complete browser conformance shards --- .github/workflows/ci.yml | 2 +- docs/interpreter-exceptions.md | 8 ++++++++ tests/test_test_runner.py | 2 +- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 589c0bb..2affde2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -112,7 +112,7 @@ jobs: - {suite: browser-integration, shard: 2, shards: 3} - {suite: browser-integration, shard: 3, shards: 3} runs-on: ubuntu-latest - timeout-minutes: 15 + timeout-minutes: ${{ matrix.suite == 'browser-conformance' && 20 || 15 }} env: UV_PYTHON: '3.14.7' steps: diff --git a/docs/interpreter-exceptions.md b/docs/interpreter-exceptions.md index 61b5aff..b8246a7 100644 --- a/docs/interpreter-exceptions.md +++ b/docs/interpreter-exceptions.md @@ -566,3 +566,11 @@ count divided by ten, matching the shared validation inventory's cost basis. Small selections retain the existing bound; semantic execution, work/allocation limits and timeout/failure diagnostics are unchanged. This addresses the two fixed-deadline Linux CI failures in the first batch's stepped/parked matrix. + +LA-58B retains the complete browser matrix with a finite 20-minute conformance +job bound; integration jobs keep their 15-minute limit. Two 15-minute CI +attempts reached the final compatibility check after their preceding checks +passed. The identical complete 44-check shard passes locally in 485.824 seconds. +All six conformance and three integration shards, reports and semantic/resource +limits are retained; the deterministic partition test uses the actual six +conformance shards. Interpreter/package/offline acceptance evidence is reused. diff --git a/tests/test_test_runner.py b/tests/test_test_runner.py index a0f964b..29a8307 100644 --- a/tests/test_test_runner.py +++ b/tests/test_test_runner.py @@ -18,7 +18,7 @@ def test_focused_selection_contains_only_matching_node_checks(self): self.assertEqual({check.suite for check in selected}, {'node-unit', 'node-conformance'}) def test_shards_are_complete_disjoint_balanced_and_deterministic(self): - for suite, count in [('interpreter', 4), ('browser-conformance', 4), ('browser-integration', 3)]: + for suite, count in [('interpreter', 4), ('browser-conformance', 6), ('browser-integration', 3)]: with self.subTest(suite=suite): selected = run_tests.select_checks([suite]) groups = run_tests.shards(selected, count)