Skip to content

fix: prevent silent NULL-handle creation in Object.__init__#449

Draft
junrushao wants to merge 1 commit intoapache:mainfrom
junrushao:2026-02-14/disable-init
Draft

fix: prevent silent NULL-handle creation in Object.__init__#449
junrushao wants to merge 1 commit intoapache:mainfrom
junrushao:2026-02-14/disable-init

Conversation

@junrushao
Copy link
Member

@junrushao junrushao commented Feb 15, 2026

blocked by cuteDSL usage of this private API

Previously, calling Object() or constructing an unregistered subclass without __init__ would silently succeed and produce an object with a NULL handle. Add Object.__init__ that raises TypeError to catch this misuse early. Remove the now-redundant super().__init__() call in AccessPath.init.

Previously, calling `Object()` or constructing an unregistered subclass
without `__init__` would silently succeed and produce an object with a
NULL handle. Add `Object.__init__` that raises TypeError to catch this
misuse early. Remove the now-redundant `super().__init__()` call in
AccessPath.__init__.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @junrushao, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request enhances the robustness of object instantiation by explicitly preventing the creation of Object instances with NULL handles through direct calls or unregistered subclasses. By raising a TypeError early, it provides clearer feedback to developers about incorrect usage patterns, thereby improving debugging and code reliability.

Highlights

  • Error Prevention: Introduced a TypeError in Object.__init__ to prevent silent creation of objects with NULL handles when Object() is called directly or an unregistered subclass without its own __init__ is instantiated.
  • Code Refinement: Removed a redundant super().__init__() call from AccessPath.__init__ as it is no longer necessary with the new Object.__init__ behavior.
  • Test Coverage: Added a new test case, test_object_direct_init_disabled, to verify that direct instantiation of Object or unregistered subclasses now correctly raises a TypeError.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • python/tvm_ffi/access_path.py
    • Removed super().__init__() call from AccessPath.__init__.
  • python/tvm_ffi/cython/object.pxi
    • Added an __init__ method to the Object class that raises a TypeError.
  • tests/python/test_object.py
    • Added test_object_direct_init_disabled to validate the new Object initialization behavior.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

The pull request effectively addresses the issue of silent NULL-handle creation by introducing a TypeError in the base Object.__init__. This ensures that direct instantiation of Object or unregistered subclasses is caught early with a descriptive error message. The removal of the redundant super().__init__() call in AccessPath is a correct cleanup to avoid conflicting exceptions, and the new test cases provide comprehensive coverage for the various instantiation scenarios (base class, unregistered subclasses, and registered classes with or without constructors). The implementation is clean and follows the existing patterns in the codebase.

Copy link
Member Author

@junrushao junrushao left a comment

Choose a reason for hiding this comment

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

Synthesized Code Review Report

Scope: PR #449 (all commits since diverging from main) | Files changed: 3 | Reviewers: Claude Code, Codex

Summary: 2 high, 3 medium, 5 low/nit findings. Core mechanism is sound and well-motivated — __cinit__ still initializes chandle = NULL, __init__ now raises early, and all legitimate construction paths go through __new__ + handle setup.

General Findings (not mapped to specific diff lines)

[high | API Design | Consensus] Breaking change: Any code calling Object() or super().__init__() on unregistered subclasses will now fail with TypeError. This is intentional but should be documented in release notes.

[low | Maintainability | Claude] registry.py:387__init__invalid message says "The __init__ method of this class is not implemented." without naming the class. The new Object.__init__ demonstrates how to include type(self).__name__; consider aligning for consistency in a follow-up.

[low | Test Coverage | Codex] Missing test for a registered subclass that naively overrides __init__ — would help document the expected behavior.

[nit | Test | Claude] # ty: ignore[too-many-positional-arguments] appears in multiple test locations; may indicate a stub issue for TestIntPair.__init__.

Questions for Author

  1. Pickle path: __reduce__ returns (_new_object, (cls,), self.__getstate__()) which uses cls.__new__(cls), bypassing __init__. Have you verified pickle round-trips still work?
  2. Cython MRO: If a Python subclass doesn't define __init__, the Cython base Object.__init__ fires. Registry replaces __init__ via setattr on the Python type's __dict__. Confirmed for all registered types?

Generated by multi-model code review (Claude Code + Codex)

def __init__(self) -> None:
"""Disallow direct construction; use `AccessPath.root()` instead."""
super().__init__()
raise ValueError(
Copy link
Member Author

Choose a reason for hiding this comment

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

[high | API Consistency | Consensus] AccessPath.__init__ raises ValueError while the base Object.__init__ now raises TypeError. Three different exception types are used for "cannot construct":

  • Object.__init__TypeError
  • __init__invalid (registry) → RuntimeError
  • AccessPath.__init__ValueError

TypeError is the most Pythonic choice for "this class cannot be instantiated this way" (matches abstract classes and __new__ guards). Consider unifying:

def __init__(self) -> None:
    """Disallow direct construction; use `AccessPath.root()` instead."""
    raise TypeError(
        "AccessPath cannot be initialized directly. "
        "Use AccessPath.root() to create a path to the root object"
    )

Source: Consensus (Claude + Codex)

# case of error before chandle is set
self.chandle = NULL

def __init__(self):
Copy link
Member Author

Choose a reason for hiding this comment

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

[low | Maintainability | Codex] The error message hardcodes "FFI constructors" and "@register_object" as the solutions. If new mechanisms for object creation are added, this message won't mention them. Minor suggestion — consider making it slightly more generic, e.g. adding "factory methods" as an option.

Source: Codex

UnregisteredObj()

# Registered class without __c_ffi_init__ should still raise
with pytest.raises(RuntimeError, match="__init__ method of this class is not implemented"):
Copy link
Member Author

Choose a reason for hiding this comment

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

[medium | Correctness | Claude] The match string "__init__ method of this class is not implemented" is a fragile substring — the actual message in registry.py:387 is "The __init__ method of this class is not implemented." (starts with "The"). While re.search makes this work, a reader may think the full message starts with "__init__".

[medium | Test Clarity | Codex] It's also unclear from the test whether the RuntimeError comes from the missing __c_ffi_init__ path or from the new __init__ guard. A brief comment clarifying the error path would help.

Suggested fix:

# Registered class without __c_ffi_init__: the registry replaces __init__
# with __init__invalid, which raises RuntimeError (not the base TypeError)
with pytest.raises(RuntimeError, match="The __init__ method of this class is not implemented"):
    tvm_ffi.testing.TestObjectBase()

Source: Claude (match string) + Codex (clarity)

)


def test_object_direct_init_disabled() -> None:
Copy link
Member Author

Choose a reason for hiding this comment

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

[medium | Test Coverage | Claude] Missing test for AccessPath.__init__ after removing super().__init__(). The removal changes exception ordering — previously the base Object.__init__ (no-op) ran first, then ValueError fired. Now only ValueError fires directly. If someone accidentally removes the raise ValueError, the base TypeError would kick in with different semantics.

Suggested addition:

def test_access_path_direct_init_raises():
    """AccessPath() must raise ValueError directing users to root()."""
    from tvm_ffi.access_path import AccessPath
    with pytest.raises(ValueError, match="AccessPath can.t be initialized directly"):
        AccessPath()

Source: Claude


# Registered class with __c_ffi_init__ should work fine
pair = tvm_ffi.testing.TestIntPair(3, 4) # ty: ignore[too-many-positional-arguments]
assert pair.a == 3 and pair.b == 4
Copy link
Member Author

Choose a reason for hiding this comment

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

[nit | Readability | Claude] If pair.a == 3 passes but pair.b == 4 fails, pytest will only report the combined expression failed without showing which half. Consider splitting:

assert pair.a == 3
assert pair.b == 4

Source: Claude

# case of error before chandle is set
self.chandle = NULL

def __init__(self):
Copy link
Member

Choose a reason for hiding this comment

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

there might be a cases that depends on class construction and handle move, so we need to cross check downstream for this change

@junrushao
Copy link
Member Author

blocked by cuteDSL usage of this private API

@junrushao junrushao marked this pull request as draft February 15, 2026 01:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants